AJAX式資料清單的新選擇-Kendo UI Grid

這幾年在專案中,針對表格式資料的呈現,我多已摒棄PostBack寫法,改用AJAX動態提取方式處理換頁、排列、重新查詢等資料查詢需求,如此可避免傳統PostBack時畫面會閃一下的缺點,提供使用者較流暢的操作感受。

前陣子寫過一篇筆記: Telerik RadGrid AJAX更新範例,介紹的便是應用Telerik的RadControls for ASP.NET AJAX元件庫,實現純AJAX式資料來源的GridView。不過,該元件基於ASP.NET AJAX Client Library並非jQuery,加上元件為ASP.NET控制項,不能安插於純HTML網頁中,限制較多;之前還介紹過另一套Telerik Extensions for ASP.NET MVC,是以jQuery為基礎打造的Client-Side Library,但它主要設計在ASP.NET MVC cshtml中被呼叫,若要在非ASP.NET MVC環境中使用,需要花功夫自己包Plug-In。(是的,這事兒我也幹了...)

本月初(2011/12),Telerik推出了Kendo UI Framework,一套以HTML5 + jQuery打造的精緻UI元件組,依循如Telerik RadControls for ASP.NET的傳統,照例網羅了日期選擇器、頁籤、選單、Grid、TreeView... 等網頁開發會用到的大小控制項,一方面善用HTML5 + CSS3的威力,另一方面繼續保持跨平台相容(跨IE, Firefox, Chrome, Safari, Opera等瀏覽器, 但HTML5支援度較差的IE7/IE8也OK,還支援WP7, iOS及Android等行動裝置),看起來是純Client-Side元件的一項不錯選擇。

在授權上,Kendo UI跟Extensions for ASP.NET MVC一樣採雙授權方式: 商業版本可享有技術支援,每月更新,並可包在商品中發行販售;GPL授權則提供每季更新的開放源碼,若只應用在自己開發的網站中,不會再將網站程式本體作為商品販售散佈,理論上均可自由取得及修改,即使網路對外營運也在合法授權範圍內。(網站引用GPL元件授權議題可參見上回討論)

從此,要在網站專案中實現AJAX式的GridView,又多了一項選擇。這裡就依著上回Telerik RadGrid AJAX更新範例的規格,改用Kendo UI Grid來實做驗證一番! 如下圖,分頁、排序、關鍵字查詢功能都不是問題,看來足以滿足專案的基本需求。

Grid1.htm程式碼如下:

<!DOCTYPE html>
 
<html>
<head>
    <title>Grid Lab 1</title>
    <!--In the header of your page, paste the following for Kendo styles-->
    <link href="../styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <link href="../styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
 
    <!--Then paste the following for Kendo scripts-->
    <script src="../js/jquery.min.js" type="text/javascript"></script>
    <script src="../js/kendo.all.min.js" type="text/javascript"></script>
    <style>
        body { font-size: 9pt; }
        #dvGrid { width: 500px; }
        span.hi-lite { color: red; }
        #dvGrid th.k-header { text-align: center }
    </style>
    <script>
        $(function () {
            //建立資料來源物件
            var dataSrc = new kendo.data.DataSource({
                transport: {
                    read: {
                        //以下其實就是$.ajax的參數
                        type: "POST",
                        url: "JsonDataSrc.ashx",
                        dataType: "json",
                        data: {
                            //額外傳至後方的參數
                            keywd: function () {
                                return $("#tKeyword").val();
                            }
                        }
                    }
                },
                schema: {
                    //取出資料陣列
                    data: function (d) { return d.Data; },
                    //取出資料總筆數(計算頁數用)
                    total: function (d) { return d.TotalCount; }
                },
                pageSize: 10,
                serverPaging: true,
                serverSorting: true
            });
            //JSON日期轉換
            var dateRegExp = /^\/Date\((.*?)\)\/$/;
            window.toDate = function (value) {
                var date = dateRegExp.exec(value);
                return new Date(parseInt(date[1]));
            }
 
            $("#dvGrid").kendoGrid({
                dataSource: dataSrc,
                columns: [
                    { field: "UserNo", title: "會員編號" },
                    { field: "UserName", title: "會員名稱",
template: '#= "<span class=\\"u-name\\">" + UserName + "</span>" #'
                    },
                    { field: "RegDate", title: "加入日期",
template: '#= kendo.toString(toDate(RegDate), "yyyy/MM/dd")#'
                    },
                    { field: "Points", title: "累積點數" },
                ],
                sortable: true,
                pageable: true,
                dataBound: function () {
                    //AJAX資料Bind完成後觸發
                    var kw = $("#tKeyword").val();
                    //若有設關鍵字,做Highlight處理
                    if (kw.length > 0) {
                        var re = new RegExp(kw, "g");
                        $(".u-name").each(function () {
                            var $td = $(this);
                            $td.html($td.text()
                           .replace(re, "<span class='hi-lite'>$&</span>"));
                        });
                    }
                }
            });
            //按下查詢鈕
            $("#bQuery").click(function () {
                //要求資料來源重新讀取(並指定切至第一頁)
                dataSrc.read({ page: 1, skip: 0 });
                //Grid重新顯示資料 2013-07-19更正,以下可省略
                //$("#dvGrid").data("kendoGrid").refresh();
            });
        });
    </script>
</head>
<body>
<div style="padding: 10px;">
    關鍵字: <input id="tKeyword" /><input type="button" value="查詢" id="bQuery" />
</div>
<div id="dvGrid"></div>
</body>
</html>

後端我寫了一個JsonDataSrc.ashx來負責資料供給,其中程式邏輯與上回RadGrid版本幾乎完全相同。(只要能正確傳回JSON即可,要改寫成PHP、Ruby應該也不會是難事)

<%@ WebHandler Language="C#" Class="JsonDataSrc" %>
 
using System;
using System.Web;
using System.Web.Script.Serialization;
using System.Collections.Generic;
using System.Drawing;
using System.Reflection;
using System.Linq;
 
public class JsonDataSrc : IHttpHandler {
    
    //模擬資料物件
    public class SimMemberInfo
    {
        public string UserNo; //會員編號
        public string UserName; //會員名稱
        public DateTime RegDate; //註冊日期
        public int Points; //累積點數
    }
    static List<SimMemberInfo> _SimuDataStore = null;
    //結果物件
    public class ResultData 
    {
        public object Data;
        public int TotalCount;
    }
    
    public void ProcessRequest (HttpContext context) {
        if (_SimuDataStore == null)
        {
            Random rnd = new Random();
            //借用具名顏色名稱來產生隨機資料
            string[] colorNames = typeof(Color)
                .GetProperties(BindingFlags.Static | BindingFlags.Public)
                .Select(o => o.Name).ToArray();
            _SimuDataStore =
                colorNames
                .Select(cn => new SimMemberInfo()
                {
                    UserNo = string.Format("C{0:00000}", rnd.Next(99999)),
                    UserName = cn,
                    RegDate = DateTime.Today.AddDays(-rnd.Next(1000)),
                    Points = rnd.Next(9999)
                }).ToList();
        }
        string keywd = context.Request["keywd"];
        string sortField = context.Request["sort[0][field]"];
        string sortDir = context.Request["sort[0][dir]"];
 
        //指定關鍵字時,使用Contains()對UserName進行比對
        var res = _SimuDataStore.Where(o =>
            string.IsNullOrEmpty(keywd) || o.UserName.Contains(keywd));
        if (!string.IsNullOrEmpty(sortField))
        {
            //宣告一個函數可傳回SimMemberInfo之指定屬性值用於依動態欄位排序
            Func<SimMemberInfo, string, string> GetColString =
                (o, c) =>
                {
                    switch (c)
                    {
                        case "UserNo": return o.UserNo;
                        case "UserName": return o.UserName;
                        case "RegDate": return o.RegDate.ToString("yyyyMMdd");
                        case "Points": return o.Points.ToString();
                        default: throw new ArgumentException();
                    }
                };
            if (sortDir == "asc")
                res = res.OrderBy(o => GetColString(o, sortField));
            else
                res = res.OrderByDescending(o => GetColString(o, sortField));
        }        
        
        
        JavaScriptSerializer jss = new JavaScriptSerializer();
        context.Response.ContentType = "text/plain";
        int pageSize = 10, take = 10, skip = 0;
        int.TryParse(context.Request["pageSize"], out pageSize);
        int.TryParse(context.Request["take"], out take);
        int.TryParse(context.Request["skip"], out skip);
        var paged = res.Skip(skip).Take(take);
        context.Response.Write(jss.Serialize(new ResultData()
        {
            Data = paged.ToList(),
            TotalCount = res.Count()
        }));
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
 
}

想馬上實際動手玩的朋友可以試試上述範例程式的線上版

寫完2011年最後一篇文囉! 準備迎接2012,祝大家新年快樂~~

歡迎推文分享:
Published 31 December 2011 06:23 PM 由 Jeffrey
Filed under:
Views: 59,236



意見

# demo said on 31 December, 2011 05:28 AM

黑大新年快樂

我最近深受 控制項的困擾中 =.=

# veronica said on 01 January, 2012 06:16 AM

黑大有用過 ExtJS 嗎?如果有的話,想請教您和這套相比的感想:)

# 佑翔 said on 01 January, 2012 09:18 PM

ajax的確是王道!!

不過用在資料清單上,有時會造成user的困擾~

舉一個我實際發生的例子:

前年,在某專案上,我很開心的把本來的分頁從post改ajax

上線後user卻要求改回來

原因是他們會習慣用browser的"上一頁"看他們上次選到的頁數(或filter的條件)

若沒有特別在這點下功夫(利用anchor或改掉"上一頁"按鈕)

網頁都會整個跑掉(跑到真正的上一頁)

況且,user認為改成這樣又沒有比較快(可能本來就很快)

所以對他們來說一點幫助都沒有

為了我們工程師的自high,還得改變他們的操作習慣!!

儘管當時我想朝anchor方面著手

但礙於許多因素(其他專案也很趕、PM覺得改的CP值==0...etc)

迫於無奈下最後還是改回來了

所以之後我就沒再用相關的ajax table套件

因為目前我沒有找到連上面這個問題都一併解決的好東西

至於實務上,ajax table還是有必要的

只是實作上我們會利用anchor(#)

可以參考:

www.asp.net/.../iteration-7-add-ajax-functionality-cs

我的經驗大概是這樣^^

# 佑翔 said on 01 January, 2012 09:28 PM

我發現reply的時間是錯的耶!!

我是1/2早上10點多回的

上面的時間卻顯示01 January, 2012 09:18 PM

# Jeffrey said on 01 January, 2012 11:21 PM

to 佑翔, 在AJAX模式下要做到按回上頁退回前一個UI是可能的,但要花些心思處理就是了,我在一些專案上有如此實作,使用jQuery BBQ套件處理Hash的部分(參考:

blog.darkthread.net/post-2011-09-22-ajax-goback.aspx), 但回上頁後頁面內容的還原得自行處理。另外,留言時間誤差問題跟網站主機在美國有時區差異有關,之前偷懶沒花時間徹底解決(目前仍打算繼續裝死下去 XD),請大家多多包涵 :P

# Jeffrey said on 01 January, 2012 11:29 PM

to veronica, 目前JavaScript的Framework蠻多的,Dojo, YUI, ExtJS等也各有愛好者,只能說這是一個蘿蔔青菜各有所好的議題,每一套都有其強處與弱點,也看它有沒有剛好敲中各個開發者的心絃。當年有Survey過一點點YUI,但自從跟jQuery一見鍾情加上微軟也接受其作為Client端主力後就沒再正眼瞧過其他Framework XD... Stackoverflow上有篇不錯的比較討論 stackoverflow.com/.../which-javascript-framework-jquery-vs-dojo-vs 可以參考。(由投票數來看,jQuery也是目前最多人愛的選項)

# KKBruce said on 02 January, 2012 02:51 AM

之前一直想用Telerik for MVC套件,但必須與C#綁在一起,小弟無能又只會寫VB,所以就放著,也有上他們的論壇反應過,可以出for VB嗎,一直無下文。

現在看來,綁"C#"or"VB"都不殺,這個純Client的Solution很殺。

# 小黑 said on 06 January, 2012 03:47 AM

這真是好物

# todd said on 26 August, 2012 03:32 AM

是真的很好用很強大但是License很貴啊 .....

# Jeffrey said on 26 August, 2012 10:19 AM

to todd, Kendo UI有GPL跟商業版兩個版本,商業版多了圖表套件、行動裝置套件及技術支援,且更新頻率較高。如果評估這些額外資源的成本效益比不合,使用免費的GPL版也是個辦法(我目前網站上的範例都可使用GPL版完成)。

# 水煎包 said on 28 December, 2012 05:43 AM

版主,我想請問一下  當Kendo grid在Update時,(Server side error)有錯誤時,例如:SP提示:重複加入Key值,有Kedno gird event可以接到錯誤嗎?

# Jeffrey said on 29 December, 2012 06:05 AM

to 水煎包, 你如果是使用DataSource.transport.update去更新,基本上它是個jQuery.ajax呼叫,應可透過ajax()標準success或error事件去捕捉伺服器端錯誤。

# wangben12 said on 28 February, 2013 11:19 PM

黑大对前端框架有什么推荐,例如easyUI,extjs

# Jeffrey said on 28 February, 2013 11:49 PM

to wangben12, 我不是前端方面的專家,Survey過的Framework有很,目前只試過jQuery UI及Kendo UI,就輕巧精美度而言,我是比較偏好Kendo UI的,個人見解供您參考。

# Arter Yu said on 14 August, 2013 03:14 AM

與 Kendo UI 初次相遇,感謝您的分享。

關於AJAX中網址的變更,操作瀏覽器歷史清單是個解決方案。

可以參考:http://goo.gl/533rrj

# Lai32290 said on 20 April, 2014 09:29 PM

請問如果只是要自己要用的話,可以下載網站上的Free Trial版安心用嗎?

不會發生剩餘X天試用或是版權問題吧?

# Jeffrey said on 20 April, 2014 11:38 PM

to Lai32290, Telerik前陣子調整了Kendo UI的產品線,Core為開源版本(但Grid被抽掉),免費但無官方支援,基本上單純用於網站沒問題,商業版則有試用天數限制。詳情請參考: www.facebook.com/.../490992954362233

# CWKU said on 25 August, 2015 02:57 AM

請問黑大是否有用過export pdf的功能?

小弟無能在碰到中文時,匯出PDF就會變成亂碼,有試過換字型http://goo.gl/y8Ka29 但是還是失敗,不知道黑大有沒有遇過這問題?

# Jeffrey said on 25 August, 2015 06:04 AM

to CWKU,我沒用過。請教有用過的同事:

*中文字型可以顯示(他在網站上放了中文字型檔,但尺寸太大,嚴格來說並不算可用解決方案)

*輸出時格式會跑掉,一直沒能克服,最後放棄KendoGrid轉PDF的功能

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<December 2011>
SunMonTueWedThuFriSat
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication