顯示具有 JavaScript - extjs 標籤的文章。 顯示所有文章
顯示具有 JavaScript - extjs 標籤的文章。 顯示所有文章

2010年1月11日 星期一

ExtJS應用程式設計原則


取自http://blog.joomla.org.tw/javascript/53-extjs/71-extjs-application-design-1.html

作者是 EddyChang

這篇文章是從ExtJS官網討論區中找到的,非常值得參考,原來的問題是:「application design, component creation and efficiency」。由我個人的觀點來看,Javascript發展至今,仍然是百家爭鳴的時代,有非常多不同的framework或pattern,也很難和各式的應用能完全配合,ExtJS算是非常豐富的Javascript架構,但還是要配合像PHP的Server端程式才能完成整個應用程式。以下是關於Saki(ExtJS官方支援小組)的經驗回答摘譯:

使用以下的規則在開發Ext大型的應用程式上:
  1. 盡可能地多使用緩慢實例化(xtype)  - Use lazy instantiation (xtype) as much as possible.
  2. 使用預先設定好的類別 - Use pre-configured classes (I'll explain later).
  3. 在父階層中實作關係  - Implement relations on parent level.
  4. 在開發時,保持每個類別在自己的一個檔案;在產品化時,再組合和壓縮它們  - Keep each class in its own file while developing, concat and minify for production.

1. 盡可能地多使用緩慢實例化(xtype)

這點是如果你使用xtypes的話,Ext物件只有當他們需要時才會被建立。

備註:xtype和延伸類別的分離方式,的確是對大型開發有幫助。但關於上面的說明,在討論區的另一篇:「Does xtype really support lazy instantiation?」提出了lazy instantiation的質疑,以及和lazy redener的比較。


2. 使用預先設定好的類別

上述第1點的xtype的方法要配合預先設定好的類別"pre-configured classes",這些類別是擴充自Ext的類別而來的,帶有設定選項和(或)加入的函式。

備註:有許多範例可以了解怎麼寫出預先設定好的擴充類別,例如以下的: 3.在父階層中實作關係

想像你有一個border版面中,在西(左)邊有一個表格和中間有一個表單,當選了表格中的某個項目時,表單中要顯示對應的值。那到底要怎麼寫這段程式邏輯,是放在表格裡,還是表單裡?應該都不是這兩個。這兩者互不知道對方存在,知道這兩者同時存在的是它們的父階層(可能是viewpoint或window)

因此,關係會建立在父階層裡,例如window中。這裡的程式會是監聽表格來的事件,然後在選擇改變時載入表單資料,或是當表單進出資料後,改變表格的記錄。

如果我把程式碼寫在表格裡,那這個表格就和表單不可分離了。

備註:的確是很好的原則,不過直接的想法都是寫到事件發動處,在小型的事件處理,這樣比較快而且直觀。Saki在它的範例網站中,有加了兩個範例,這兩個範例都是很好的學習資源: 4.在開發時,保持每個類別在自己的一個檔案;在產品化時,再組合和壓縮它們

心得:ExtJS原本的作法就是如此,Linux下可以用Cat指令,Windows下可用ConCat/Split的軟體來合併Javascript檔案,再利用JSBuilder之類的工具,可以去除註解檔和壓縮檔案。

最後的忠告
不需要太深思熟慮於程式的結構、版面、各種控制器、載入器、介面…太多了。而是要寫出好的可重覆利用的預先設定好的類別,然後把它們不管如何先放到一起。如果這些類別真的是不錯而且可以重覆使用的,你大可以更改你的應用程式版本,使用別的方式來作,但你的類別至少仍然都會正常工作。就像是樂高積木一樣-如果你有木塊,你可以在幾分鐘內建出一個城堡。

心得:的確是如此。預先設定好的類別是必學的一段,雖然我有看過另一種寫法 - Module Pattern,Module Pattern是通用於各Javascript框架的寫法,或許也是一個值得一學的部份。在官方討論區中的「preconfigured class vs. module pattern」一文中有一些比較資訊可以參考。


2009年9月17日 星期四

extjs - 自訂vtype


內建的vtype有email、url、alpha、apphanum等等,不過內建的vtype可能不合我們使用,所以我們就需要自訂vtype。 我們必須定義vtype的value、mask、error message以及testing function:
  • xxxVal: 輸入內容的正規表示式。
  • xxxMask: 輸入的遮罩。
  • xxxText: 錯誤訊息。
Ext.onReady(function() {
    Ext.QuickTips.init();
    Ext.form.VTypes['portListVal'] = /^([0-9]+[,\-])*[0-9]+$/;
    Ext.form.VTypes['portListMask'] = /[0-9\-,]/;
    Ext.form.VTypes['portListText'] = 'Invalid Port List';
    Ext.form.VTypes['portList'] = function (v) {
        return Ext.form.VTypes['portListVal'].test(v);
    }

    var form = new Ext.FormPanel({
        renderTo: Ext.getBody(),
        frame: true,
        items: [{
            xtype: 'textfield',
            fieldLabel: 'txt',
            vtype: 'portList'
        }]
    });
});




2009年9月8日 星期二

extjs, ux - Managed iFrame


JavaScript算是被誤解蠻深的語言,很多人都認為JavaScript很容易,卻往往寫出一堆邏輯錯誤,但是功能符合的code,進而造成許多的memory leak,這點我也避免不了,於是就有人想到用iframe來解決,目前我在使用上確實幫我解決了不少memory leak的問題,所以,也安心的讓大家亂寫一通(即便他們很認真的寫還是寫得很亂)。
最簡單的iframe就是指定xtype和defaultSrc即可,而切換頁面則透過iframe的setSrc(url)。
Ext.onReady(function() {
  var viewport = new Ext.Viewport({
    renderTo: Ext.getBody(),
    layout: 'border',
    items: [{
      region: 'west',
      title: 'west',
      collapsible: true,
      width: 100,
      items: [{
        xtype: 'button',
        text: 'google',
        handler: function () {
          viewport.getComponent('center').setSrc('http://www.google.com');
        }
      },
      {
        xtype: 'button',
        text: 'nano chicken',
        handler: function () {
          viewport.getComponent('center').setSrc('http://www.blogger.com');
        }
      }]
    },
    {
      region: 'center',
      title: 'center',
      id : 'center',
      xtype: 'iframepanel',
      defaultSrc: 'http://www.blogger.com'
    }]
  });
});


這樣每一個iframe的網頁都是一個獨立的網頁,即便,parent頁面已經有include extjs了,iframe裡面的網頁如果有用到extjs,還是需要在include一次。
參考資料:
http://www.extjs.com/learn/Extension:ManagedIframe
http://www.extjs.com/forum/showthread.php?t=71961
http://code.google.com/p/managediframe/



2009年8月12日 星期三

extjs - Ext.PagingToolbar簡介


隨著records的增加,Browser會花更多的時間在畫grid,Ext.PagingToolbar主要用來處理這樣的問題,透過傳遞參數的方式(start:第幾筆開始,limit:共要幾筆),告知Server要傳送哪些資料。
注意,Ext.PagingToolbar只是告知Server要顯示的資料為何,而Ext.PagingToolbar預設會顯示所有的資料。

因為要讀取Server的資料,所以Store.proxy改用Ext.data.HttpProxy。而讀取的格式我們打算用json,所以,store.reader要用Ext.data.JsonReader。
我們的json file(phone_num.json)如下:
{
    "success": true,
    "results": 12, // how many entry will be sent.
    "rows": [ // *Note: this must be an Array
        { "city": "台北市", "num": "02"},
        { "city": "新竹市", "num": "03"},
        { "city": "苗栗縣", "num": "037"},
        { "city": "台中市", "num": "04"},
        { "city": "南投縣", "num": "049"},
        { "city": "嘉義市", "num": "05"},
        { "city": "台南市", "num": "06"},
        { "city": "高雄市", "num": "07"},
        { "city": "屏東縣", "num": "08"},
        { "city": "台東縣", "num": "089"},
        { "city": "馬祖", "num": "0836"},
        { "city": "烏坵", "num": "082"}
    ]
}

Ext.onReady(function() {
    var cm = new Ext.grid.ColumnModel([
        new Ext.grid.RowNumberer(),
        {
            header: 'City',
            dataIndex: 'city'
        }, {
            header: 'Num',
            dataIndex: 'num'
        }
    ]);

    var store = new Ext.data.Store({
        proxy: new Ext.data.HttpProxy({url: 'phone_num.json'}),
        reader: new Ext.data.JsonReader({
            totalProperty: 'results',
            root: 'rows',
            fields: [{name: 'city'}, {name: 'num'}]
        })
    });
    store.load();

    var grid = new Ext.grid.GridPanel({
        renderTo: Ext.getBody(),
        store: store,
        cm: cm,
        sm: new Ext.grid.RowSelectionModel(),
        viewConfig: {
            forceFit: true
        },
        title: "區碼",
        loadMask: true,
        height: 200,
        width: 330,
        bbar: new Ext.PagingToolbar({
            pageSize: 5,
            store: store,
            displayInfo: true
        })
    });
});


由於,我們在Server-Side都只是把整個資料丟給client,所以,grid即便有Ext.PagingToolbar,但是還是會顯示所有由Server丟過來的資料。

Paging with Local Data
在local paging官方網站上有提到兩種方式:
  1. Ext.ux.data.PagingStore
  2. Paging Memory Proxy(examples/ux/PagingMemoryProxy.js)

根據Ext.ux.data.PagingStore作者的說法,PagingMemoryProxy缺點如下:
  1. You have to write extra code to remote load the data for the proxy.
  2. query, filter and collect only work on the current page. You have to write extra code to use the PagingMemoryProxy filter support.
  3. Local sorting works, but you need to set remoteSort:true. There is no remote sorting support.
  4. Added and removed records are only remembered for the current page.
  5. Changing the page is relatively slow (PagingMemoryProxy reprocesses all data).
檔案下載與詳細討論:
PagingStore.js for ext-js v3
PagingStore.js for ext-js v2

以下是v2的code:
Ext.onReady(function() {
    var cm = new Ext.grid.ColumnModel([
        new Ext.grid.RowNumberer(),
        {header: 'City', dataIndex: 'city'},
        {header: 'Num', dataIndex: 'num'}
    ]);

    var store = new Ext.ux.data.PagingStore({
        proxy: new Ext.data.HttpProxy({url: 'phone_num.json'}),
        reader: new Ext.data.JsonReader({
            root: 'rows',
            totalRecords: 'results',
            fields: [{name: 'city'}, {name: 'num'}]
        })
    });
    store.load({params:{start:0, limit: 4}});

    var grid = new Ext.grid.GridPanel({
        renderTo: Ext.getBody(),
        store: store,
        cm: cm,
        sm: new Ext.grid.RowSelectionModel(),
        viewConfig: {
            forceFit: true
        },
        title: "區碼",
        loadMask: true,
        height: 200,
        width: 330,
        bbar: new Ext.PagingToolbar({
            pageSize: 4,
            store: store,
            displayInfo: true
        })
    });
});


可以看到,Server一樣丟全部的資料,但是Grid已經可以以paging的方式顯示了。


2009年8月11日 星期二

extjs - Ext.grid.EditorGridPanel簡介


Ext.grid.EditorGridPanel繼承自Ext.grid.GridPanel,提供編輯Cell的能力。除了將Ext.grid.GridPanel改變成Ext.grid.EditorGridPanel外,還要設定Ext.grid.ColumnModel的欄位的editor。
Ext.onReady(function() {
    var data = [['Brook', '0921'], ['Rene', '0918']];

    var cm = new Ext.grid.ColumnModel([
        new Ext.grid.RowNumberer(),
        {header: 'Name', dataIndex: 'name',
            editor: new Ext.grid.GridEditor(new Ext.form.TextField())},
        {header: 'Tel', dataIndex: 'tel',
            editor: new Ext.grid.GridEditor(new Ext.form.TextField())}
    ]);

    var store = new Ext.data.Store({
        proxy: new Ext.data.MemoryProxy(data),
        reader: new Ext.data.ArrayReader({},[
            {name: 'name'},
            {name: 'tel'}
        ])
    });
    store.load();

    var grid = new Ext.grid.EditorGridPanel({
        renderTo: Ext.getBody(),
        store: store,
        cm: cm,
        sm: new Ext.grid.RowSelectionModel(),
        viewConfig: {
            forceFit: true
        },
        title: "Brook's address book",
        loadMask: true,
        height: 200,
        width: 330
    });
});



新增/刪除
我們在tbar新增兩個按鈕add/remove,add的流程如下:
  1. 停止grid的編輯。
  2. 接著插入一筆空白的資料到第一列的位置。
  3. 將第一行第一列變成編輯模式。

刪除的流程如下:
  1. 取得所有選取的列。
  2. 反覆的將所有選取列,移到store.removed(暫存起來,不然後面會遺失移除的列)。
Ext.onReady(function() {
    var record = Ext.data.Record.create([
        {name: 'name', type: 'string'},
        {name: 'tel', type: 'string'}
    ]);

    var data = [['Brook', '0921'], ['Rene', '0918']];

    var cm = new Ext.grid.ColumnModel([
        {header: 'Name', dataIndex: 'name', 
             editor: new Ext.grid.GridEditor(new Ext.form.TextField())},
        {header: 'Tel', dataIndex: 'tel', 
             editor: new Ext.grid.GridEditor(new Ext.form.TextField())}
    ]);

    var store = new Ext.data.Store({
        proxy: new Ext.data.MemoryProxy(data),
        reader: new Ext.data.ArrayReader({},[
            {name: 'name'},
            {name: 'tel'}
        ]),
        removed: [] /* 放置移除的列 */
    });
    store.load();

    var grid = new Ext.grid.EditorGridPanel({
        renderTo: Ext.getBody(),
        store: store,
        cm: cm,
        sm: new Ext.grid.RowSelectionModel(),
        viewConfig: {
            forceFit: true
        },
        title: "Brook's address book",
        loadMask: true,
        height: 200,
        width: 330,
        tbar: new Ext.Toolbar(['-', /* '-' 是按鈕分隔符號 */
            {
                text: 'add',
                handler: function() {
                    var r = new record({
                        name: '',
                        tel: ''
                    });
                    grid.stopEditing();
                    store.insert(0, r);
                    grid.startEditing(0,0);
                }
            }, {
                text: 'remove',
                handler: function() {
                    var rows = grid.getSelectionModel().getSelections();
                    for (var i = 0; i < rows.length; i++) {
                        store.removed.push(rows[i]); /* 暫存 */
                        store.remove(rows[i]); /* 移除 */
                    }
                }
            }
        ])
    });
});


修改
Ext.onReady(function() {
    var data = [['Brook', '0921'], ['Rene', '0918']];
    var cm = new Ext.grid.ColumnModel([
        new Ext.grid.RowNumberer(),
        {header: 'Name', dataIndex: 'name',
            editor: new Ext.grid.GridEditor(new Ext.form.TextField())},
        {header: 'Tel', dataIndex: 'tel',
            editor: new Ext.grid.GridEditor(new Ext.form.TextField())}
    ]);
    var store = new Ext.data.Store({
        proxy: new Ext.data.MemoryProxy(data),
        reader: new Ext.data.ArrayReader({},[{name: 'name'}, {name: 'tel'}])
    });
    store.load();
    var grid = new Ext.grid.EditorGridPanel({
        renderTo: Ext.getBody(),
        store: store,
        cm: cm,
        sm: new Ext.grid.RowSelectionModel(),
        viewConfig: {
            forceFit: true
        },
        title: "Brook's address book",
        loadMask: true,
        height: 200,
        width: 330,
        tbar: new Ext.Toolbar([{
            text: 'show modified',
            handler: function () {
                var records = store.getModifiedRecords();
                for (var i = 0; i < records.length; i++) {
                    alert(Ext.encode(records[i].getChanges()));
                }
            }
        }])
    });
});

Ext.grid.EditorGridPanel提供直接編輯Cell的能力,透過store.getModifiedRecords()可以取得修改過的rows,針對修改過的rows可以再透過getChanges()取得修改過的欄位,被修改的cell會出現紅色的標記。


限制輸入
cell通常都是由Ext.form.Field的Subclasses組成,而這些元件都具有regex的property可以限制使用者的輸入,比如regex:/[0-9]/就是限制只能輸入數字,一但輸入格式不對就會出現紅線啦。更多資訊可以參考一下Ext.form.Field。
Ext.onReady(function() {
    var data = [['Brook', '0921'], ['Rene', '0918']];
    var cm = new Ext.grid.ColumnModel([
        new Ext.grid.RowNumberer(),
        {
            header: 'Name', dataIndex: 'name',
            editor: new Ext.grid.GridEditor(
                    new Ext.form.TextField({allowBlank:false}))
        }, {
            header: 'Tel', dataIndex: 'tel',
            editor: new Ext.grid.GridEditor(
                    new Ext.form.TextField({regex:/[0-9]/}))
        }
    ]);
    var store = new Ext.data.Store({
        proxy: new Ext.data.MemoryProxy(data),
        reader: new Ext.data.ArrayReader(
                {},[{name: 'name'}, {name: 'tel'}])
    });
    store.load();
    var grid = new Ext.grid.EditorGridPanel({
        renderTo: Ext.getBody(),
        store: store,
        cm: cm,
        sm: new Ext.grid.RowSelectionModel(),
        viewConfig: {
            forceFit: true
        },
        title: "Brook's address book",
        loadMask: true,
        height: 200,
        width: 330
    });
});




2009年8月10日 星期一

extjs - Ext.grid.GridPanel簡介


組成元件
要能正確顯示資料,必須包含資料倉儲(Store),資料欄位的定義(ColumnModel)。
首先我們先定義資料欄位:
var cm = new Ext.grid.ColumnModel({
  {header: 'Name', dataIndex: 'name'},
  {header: 'Tel', dataIndex: 'tel'}
});

接著設定store,store包含資料取得方式(Proxy),以及分析方式(reader)。
var data = [
  ['Brook', '0921'],
  ['Rene', '0918']
];

var store = new Ext.data.Store({
  proxy: new Ext.data.MemoryProxy(data),
  reader: new Ext.data.ArrayReader({},[
    {name: 'name'},
    {name: 'tel'}
  ]);
});
store.load(); // 載入資料

最後再將他們和grid整合在一起:
var grid = new Ext.grid.GridPanel({
    renderTo: Ext.getBody(),
    store: store,
    cm: cm,
    viewConfig: {
        forceFit: true // 自動調整欄寬[註1],
    },
    title: "brook's guest book",
    width: 200,
    stripeRows: true,
    loadMask: true,
    height: 120
});


註1:forceFit在EditorGridPanel底下會出現錯誤(EditorGridPanel編輯後欄位錯亂的問題).

欄寬設定
調整欄寬除了forceFit以外,還可以設定autoExpandColumn: id,將特定欄寬expand以符合grid的大小,但如果設定了forceFit,autoExpandColumn將會被忽略。
var cm = new Ext.grid.ColumnModel([
    {
        header:'Name',
        dataIndex:'name'
    }, {
        header:'Tel',
        dataIndex:'tel',
        id: 'tel'
    }
]);

var grid = new Ext.grid.GridPanel({
    renderTo: Ext.getBody(),
    store: store,
    cm: cm,
    title: "brook's guest book",
    loadMask: true,
    height: 120,
    autoExpandColumn: 'tel'
});
因為autoExpandColumn必須指定id,所以cm欄位也必須指定id。


排序
在cm中,將sortable設定為true後,該欄位就可以進行排序。
var cm = new Ext.grid.ColumnModel([
    {
        header:'Name',
        dataIndex:'name',
        sortable: true
    }, {
        header:'Tel',
        dataIndex:'tel',
        id: 'tel'
    }
]);



也可以設定cm.defaultSortable = true,讓所有欄位都可以sort。
也可以預設某欄位排序的方向(由大到小,或由小到大),在store.sortInfo: {field: 'name', direction: "ASC"},就會將name以升冪排列,DESC則會降冪排列。
var store = new Ext.data.Store({
    proxy: new Ext.data.MemoryProxy(data),
    reader: new Ext.data.ArrayReader({}, [
        {name: 'name'},
        {name: 'bd', type: 'date', dateFormat: 'Y-m-d'}
    ]),
    sortInfo: {
        field: 'name',
        direction: 'DESC'
    }
});



日期顯示
在reader中將type設定為date,並且指定date的format,該欄位就會以date方式讀取,顯示的方式則要透過cm的renderer。
Ext.onReady(function() {
    var cm = new Ext.grid.ColumnModel([
        {
            header: 'Name',
            dataIndex: 'name'
        }, {
            header: 'Birthday',
            dataIndex: 'bd',
            renderer: Ext.util.Format.dateRenderer('Y年m月d日')
        }
    ]);

    var data = [
        ['Brook', '1999-12-03'],
        ['Rene', '1998-12-04']
    ];

    var store = new Ext.data.Store({
        proxy: new Ext.data.MemoryProxy(data),
        reader: new Ext.data.ArrayReader({}, [
            {name: 'name'},
            {name: 'bd', type: 'date', dateFormat: 'Y-m-d'}
        ])
    });
    store.load();

    var grid = new Ext.grid.GridPanel({
        renderTo: Ext.getBody(),
        store: store,
        cm: cm,
        viewConfig: {
            forceFit: true
        },
        title: "brook's guest book",
        loadMask: true,
        height: 120,
    });
});



由上例可知,只要透過cm.renderer就可以在顯示時加入一些變化。renderer函數包含value, metadata, css, record, rowIndex, colIndex, store等參數。
  1. value : Object 這個cell的資料
  2. metadata : Object 包含要套用在該CELL的CSS和HTML屬性的物件
  3. record : Ext.data.record 該列的資料物件
  4. rowIndex : Number Row index
  5. colIndex : Number Column index
  6. store : Ext.data.Store The Ext.data.Store object from which the Record was extracted. 

Check box
CheckboxSelectionModel會在該欄位上顯示check box,讓使用者勾選。預設是多選,設定singleSelect為true可以改變為單選。
Ext.onReady(function() {
    var sm = new Ext.grid.CheckboxSelectionModel();
    var cm = new Ext.grid.ColumnModel([
        sm,
        {
            header: 'Name',
            dataIndex: 'name'
        }, {
            header: 'Tel',
            dataIndex: 'tel'
        }
    ]);

    var data = [
        ['Brook', '0921'],
        ['Rene', '0918']
    ];

    var store = new Ext.data.Store({
        proxy: new Ext.data.MemoryProxy(data),
        reader: new Ext.data.ArrayReader({}, [
            {name: 'name'},
            {name: 'tel'}
        ])
    });
    store.load();

    var grid = new Ext.grid.GridPanel({
        renderTo: Ext.getBody(),
        store: store,
        cm: cm,
        viewConfig: {
            forceFit: true
        },
        title: "brook's guest book",
        loadMask: true,
        height: 120,
        width: 200
    });
});



自動顯示行號 自動顯示行號是由Ext.grid.RowNumberer()提供的功能,和CheckboxSelectionModel一樣,放入cm就可以達到自動顯示行號之功能。
var cm = new Ext.grid.ColumnModel([
    new Ext.grid.RowNumberer(),
    {
        header: 'Name',
        dataIndex: 'name'
    }, {
        header: 'Tel',
        dataIndex: 'tel'
    }
]);

Selection Model
在Ext.grid.GridPanel中,Selection Model預設是使用Ext.grid.RowSelectionModel,而且是可以多選,如果是單選就必須設定RowSelectionModel.singleSelect=true。
var grid = new Ext.grid.GridPanel({
    renderTo: Ext.getBody(),
    store: store,
    cm: cm,
    sm: new Ext.grid.RowSelectionModel({singleSelect: true}),
    viewConfig: {
        forceFit: true
    },
    title: "brook's guest book",
    loadMask: true,
    height: 120,
    width: 200
});

被選到的Row,可以透過grid.getSelectionModel().getSelections()取得。
var grid = new Ext.grid.GridPanel({
    renderTo: Ext.getBody(),
    store: store,
    cm: cm,
    sm: new Ext.grid.RowSelectionModel(),
    viewConfig: {
        forceFit: true
    },
    title: "brook's guest book",
    loadMask: true,
    height: 120,
    width: 200,
    buttons: [{
        text:'show select',
        handler: function() {
            var selections = grid.getSelectionModel().getSelections();
            for (var i = 0; i < selections.length; i++) {
                alert(selections[i].get('name') + ' is selection');
            }
        }
    }]
});

也可以選擇Cell Model,EditorGrid的預設Selection Model就是CellSelectionModel。
var grid = new Ext.grid.GridPanel({
    renderTo: Ext.getBody(),
    store: store,
    cm: cm,
    sm: new Ext.grid.CellSelectionModel(),
    viewConfig: {
        forceFit: true
    },
    title: "brook's guest book",
    loadMask: true,
    height: 120,
    width: 200
});



改變cell中的顯示(render)
圖文並茂的網頁是現在的趨勢,您可以透過Ext.grid.ColumnModel.renderer設定顯示cell時,要做哪些變化,比如,我想判斷電話號碼,顯示出電話號碼屬於哪家電信業者,程式碼如下:
Ext.onReady(function() {
    var record = Ext.data.Record.create([
        {name: 'name', type: 'string'},
        {name: 'tel', type: 'string'}
    ]);

    var data = [
        ['Brook', '0921'],
        ['Rene', '0918'],
        ['John', '0980']
    ];

    function renderTel(value, metaData, record, rowIndex, colIndex, store) {
        if ('0921' == value) {
            return '<img src="images/cht.gif"/>' + value;
        } else if ('0918' == value) {
            return '<img src="images/twm.gif"/>' + value;
        }
        return value;
    }

    var cm = new Ext.grid.ColumnModel([
        {header: 'Name', dataIndex: 'name'},
        {header: 'Tel', dataIndex: 'tel', renderer: renderTel}
    ]);

    var store = new Ext.data.Store({
        proxy: new Ext.data.MemoryProxy(data),
        reader: new Ext.data.ArrayReader({},[
            {name: 'name'},
            {name: 'tel'}
        ]),
        removed: []
    });
    store.load();

    var grid = new Ext.grid.GridPanel({
        renderTo: Ext.getBody(),
        store: store,
        cm: cm,
        sm: new Ext.grid.RowSelectionModel(),
        viewConfig: {
            forceFit: true
        },
        title: "Brook's address book",
        loadMask: true,
        height: 200,
        width: 330
    });
});

render function的參數說明如下:
  1. value: 將要顯示到cell的值。
  2. metaData:cell的屬性,包含css。
  3. record:該行資料。
  4. rowIndex:row index。
  5. colIndex:column index。
  6. store:使用中的store。




extjs - Ext.TabPanel 簡介


Ext.TabPanel中文名稱我也不知道稱為啥?不過感覺Tab應該是指標籤的意思,用於將panel分成幾個群組。
Ext.TabPanel算是個基本的container,所以可以將您想顯示的東西放入items中,就可以顯示了。
最基本的TabPanel如下:
Ext.onReady(function() {
    var tp = new Ext.TabPanel({
        renderTo: Ext.getBody(),
        // activeTab: 1,
        width:300,
        height: 200,
        items: [
            {title: 'p1', html: 'content 1'},
            {title: 'p2', html: 'content 2'}
        ]
    });
});

activeTab指出在初始化的時候顯示的頁面,預設是不顯示任何頁面的。

剛剛提到, TabPanel算是container,所以也可以放置其他元件。
Ext.onReady(function() {
    var form = new Ext.form.FormPanel({
        title: 'p2',
        defaultType: 'textfield',
        items: [{
            fieldLabel: 'name'
        }],
        buttons: [{
            text: 'send'
        }]
    });
    var tp = new Ext.TabPanel({
        renderTo: Ext.getBody(),
        activeTab: 1,
        width:300,
        height: 200,
        items: [
            {title: 'p1', html: 'content 1'},
            form // 在第二個頁面放置一個表單元件.
        ]
    });
});


也可以使用autoLoad載入URL(使用Ext.Updater.update Object),當成頁面的內容,如果有script要執行的話,必需將scripts設為true。
Ext.onReady(function() {
    var tp = new Ext.TabPanel({
        renderTo: Ext.getBody(),
        activeTab: 1,
        width:300,
        height: 200,
        items: [
            {title: 'p1', html: 'content 1'},
            {title: 'p2', autoLoad: {url: 'tab.html', scripts: true}}
        ]
    });
});


2009年8月5日 星期三

extjs - Ext.tree.TreePanel 簡介


Ext.tree.TreePanel繼承自Ext.Panel,算是個container,必須給予節點(node)才能形成完整的樹。也就是說在render之前必須設定好root這個node。

空的樹(直接用renderTo())
Ext.onReady(function() {
    var root = new Ext.tree.TreeNode({
        id: "root",
        text:'B_root'
    });
    root.appendChild(new Ext.tree.TreeNode({
        id: 'c1',
        text: 'B_C1'
    }));

    var tree = new Ext.tree.TreePanel({
        renderTo: 't', //要有個div的id為't'
        width: 150,
        height: 50,
        root: root
    });
});

或者(用el+render())
Ext.onReady(function() {
    var root = new Ext.tree.TreeNode({
        id: "root",
        text:'B_root'
    });
    root.appendChild(new Ext.tree.TreeNode({
        id: 'c1',
        text: 'B_C1'
    }));

    var tree = new Ext.tree.TreePanel({
        el: 't', //要有個div的id為't'
        width: 150,
        height: 50,
        root: root
    });
    tree.render();
});

利用setRootNode設定Root
Ext.onReady(function() {
    var root = new Ext.tree.TreeNode({
        id: "root",
        text:'B_root'
    });

    root.appendChild(new Ext.tree.TreeNode({
        id: 'c1',
        text: 'B_C1'
    }));

    var tree = new Ext.tree.TreePanel({
        el: 't', //要有個div的id為't'
        width: 150,
        autoHeight: true // 自動調整高度
    });
    tree.setRootNode(root);
    tree.render();
});

如果想動態變動root請參考https://extjs.net/forum/showthread.php?p=217227#post217227

可以利用Ext.tree.AsyncTreeNod定義樹狀結構,程式碼如下:
Ext.onReady(function() {
    var root = new Ext.tree.AsyncTreeNode({
        id: 'aRoot',
        text: 'root',
        children: [
            {id: 1, text: 'A leaf Node', leaf: true},
            {id: 2, text: 'A folder Node', children: [
                {id: 3, text: 'A child Node', leaf: true}]
            }
        ]
    });
    var tree = new Ext.tree.TreePanel({
        el: 't', //要有個div的id為't'
        width: 150,
        autoHeight: true,
        root: root
    });
    tree.render();
});

記得如果是樹葉,記得要設定leaf:true,否則會認為不是leaf所以一直處於讀取狀態。

expandPath( String path, [String attr], [Function callback] ) : void
展開TreePanel中特定的路徑,比如我們要展開id2的id3:
Ext.onReady(function() {
    var root = new Ext.tree.AsyncTreeNode({
        id: 'aRoot',
        text: 'root',
        children: [
            {id: 1, text: 'A leaf Node', leaf: true},
            {id: 2, text: 'A folder Node', children: [
                {id: 3, text: 'A child Node', leaf: true}]
            }
        ]
    });
    var tree = new Ext.tree.TreePanel({
        el: 't', //要有個div的id為't'
        width: 150,
        autoHeight: true,
        root: root
    });
    tree.render();
    tree.expandPath('/aRoot/2/3');
});

selectPath( String path, [String attr], [Function callback] ) : void
選擇特定路徑上的node,用法和expandPath相同。
Ext.onReady(function() {
    var root = new Ext.tree.AsyncTreeNode({
        id: 'aRoot',
        text: 'root',
        children: [
            {id: 1, text: 'A leaf Node', leaf: true},
            {id: 2, text: 'A folder Node', children: [
                {id: 3, text: 'A child Node', leaf: true}]
            }
        ]
    });
    var tree = new Ext.tree.TreePanel({
        el: 't', //要有個div的id為't'
        width: 150,
        autoHeight: true,
        root: root
    });
    tree.render();
    tree.selectPath('/aRoot/2/3');
});



除了上述的建構方式,Ext.tree.TreeLoader也提供透過URL讀取JSON file來建構tree。JSON file(tree.json)如下:
[{
    id: 1,
    text: 'A leaf Node',
    leaf: true
  },{
    id: 2,
    text: 'A folder Node',
    children: [{
      id: 3,
      text: 'A child Node',
      leaf: true
    }]
  }]

leaf屬性會在load的時候,判斷是否要在遞迴產生子節點。而原本的Ext.tree.TreeNode並不支援Ajax,所以也要改成Ext.tree.AsyncTreeNode.程式碼如下。
Ext.onReady(function() {
    var root = new Ext.tree.AsyncTreeNode({text: 'root'});
    var tree = new Ext.tree.TreePanel({
        el: 't',
        width: 150,
        autoHeight: true,
        root: root,
        loader: new Ext.tree.TreeLoader({dataUrl: 'tree.json'})
    });
    tree.render();
});


2009年7月31日 星期五

extjs - EditorGridPanel編輯後欄位錯亂的問題


Ext JS的Ext.grid.EditorGridPanel中有個bug,就是當使用者修改某欄位後,內容就會位移。不過這個bug只會發生在IE,FireFox不會有問題(看圖就知道我想說啥啦)。

其實問題就出在viewConfig: {forceFit: true},這是要讓grid自動設定欄寬,不用自動調整的話,自己去設定ColumnMode中每個欄位大小,bug自然就解啦。
在https://yui-ext.com/forum/showthread.php?t=69637 中有較為詳盡的說明。

2009年7月30日 星期四

ext.gird的右鍵功能簡介


Grid提供了四個與右鍵功能有關的event,包含contextmenu、cellcontextmenu、以及headercontextmenu,說明分別如下:
由於Browser本身就具有右鍵功能,所以在自訂右鍵功能時,必須使用preventDefault防止發生Browser的預設事件動作。
contextmenu(Ext.ExentObject e)
The raw contextmenu event for the entire grid.

cellcontextmenu(Grid this, Number rowIndex, Number cellIndex, Ext.EventObject e)
Fires when a cell is right clicked。
範例為顯示使用者對哪個cell按下右鍵。
var headerMenu = new Ext.menu.Menu({
    items: [{
        text: 'show index(row, col)',
        handler: function(item, event) {
            alert(headerMenu.cellSelected.row + 
                  ' ' + headerMenu.cellSelected.column);
        }
    }]
});
grid.on('cellcontextmenu', function(grid, rowIndex, columnIndex, e) {
    e.preventDefault();
    // 將點選的cell資訊存放於此
    headerMenu.cellSelected = {'row': rowIndex, 'column': columnIndex}; 
    headerMenu.showAt(e.getXY());
});

rowcontextmenu(Grid this, Number rowIndex, Ext.EventObject e)
Fires when a row is right clicked。
我將舉刪除某列為範例說明。由於menu的handler並不能直接得知目前選到的row index,所以必須在rowcontextmenu event中,先將row index存起來,在menu的handler中讀取,才能得知目前選到的row。
var rowMenu = new Ext.menu.Menu(
  items: [{
    text: 'delete',
    handler: function(item, event) {
      var rec = store.getAt(grid.rowContextIndex);
      store.remove(rec);
    }
  }]
});
grid.on('rowcontextmenu', function(grid, rowIndex, e) {
    e.preventDefault();
    grid.getSelectionModel().selectRow(rowIndex);
    grid.rowContextIndex = rowIndex; // 將點選的row資訊存放於此
    rowMenu.showAt(e.getXY());
});

headercontextmenu(Grid this, Number columnIndex, Ext.EventObject e)
Fires when a header is right clicked。
例子為顯示使用者對哪個header按下右鍵。
var headerMenu = new Ext.menu.Menu({
    items: [{
        text: 'show column index',
        handler: function(item, event) {
            alert(grid.headerContextId);
        }
    }]
});
grid.on('headercontextmenu', function(grid, columnIndex, e) {
    e.preventDefault();
    var cm = grid.getColumnModel();
    // 將點選的column資訊存放於此
    grid.headerContextId = cm.getDataIndex(columnIndex); 
    headerMenu.showAt(e.getXY());
});


2009年7月29日 星期三

Ext.grid.rownumberer顯示錯亂的問題


小弟發現造成錯亂是因為自己沒有將view做refresh,應該在新增刪除後執行grid.view.refresh()。

-- 舊文章 --
在ext-js中的Ext.grid.rownumberer()常常會因為store的新增刪除造成數字錯亂,可以在grid中執行新增刪除的地方(如insertRows()/removeRow()),修改成每次執行後都要refresh()即可解決。
不過小弟也不知道這算不算是正解。

insertRows : function(dm, firstRow, lastRow, isUpdate){
    if(!isUpdate && firstRow === 0 && lastRow >= dm.getCount()-1){
        this.refresh();
    }else{
        if(!isUpdate){
            this.fireEvent("beforerowsinserted", this, firstRow, lastRow);
        }
        var html = this.renderRows(firstRow, lastRow);
        var before = this.getRow(firstRow);
        if(before){
            Ext.DomHelper.insertHtml('beforeBegin', before, html);
        }else{
            Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
        }
        if(!isUpdate){
            this.fireEvent("rowsinserted", this, firstRow, lastRow);
            this.processRows(firstRow);
        }
    }
    this.syncFocusEl(firstRow);
},
改成
insertRows : function(dm, firstRow, lastRow, isUpdate){
    if(!(!isUpdate && firstRow === 0 && lastRow >= dm.getCount()-1)) {
        if(!isUpdate){
            this.fireEvent("beforerowsinserted", this, firstRow, lastRow);
        }
        var html = this.renderRows(firstRow, lastRow);
        var before = this.getRow(firstRow);
        if(before){
            Ext.DomHelper.insertHtml('beforeBegin', before, html);
        }else{
            Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
        }
        if(!isUpdate){
            this.fireEvent("rowsinserted", this, firstRow, lastRow);
            this.processRows(firstRow);
        }
    }
    this.refresh();
    this.syncFocusEl(firstRow);
},


2009年7月20日 星期一

Ext.data.Store之資料修改(add/modify/remove)


extjs中的grid負責資料的顯示,store則負責資料的處理,store.data.item[].dirty 則標示該record是否有被更改過,可以由store.getModifiedRecords()來讀取被更改過的資料(不包含被刪除的資料)。
var m = store.getModifiedRecords();
var jsonArray = [];
Ext.each(m, function(item) {
    jsonArray.push(item.data);
}); 
alert(Ext.encode(jsonArray)); 

有看到有人直接存取modified,沒有trace過,不過感覺應該透過Interface來存取比較妥當。
var m = store.modified.slice(0);
var jsonArray = [];
Ext.each(m, function(item) {
  jsonArray.push(item.data);
});
alert(Ext.encode(jsonArray)); 

至於remove/delete則必須自己處理,每次在執行delete之前就先儲存在某個地方(我都存在store.deleted)。
var records = grid.getSelectionModel().getSelections();
for (i = 0; i < records.length; i++) {
    store.deleted.push(records[i]);
    store.remove(records[i]);
}

Add則只需要將record插入store中即可。
/* Generate a constructor for a specific Record layout */
var R = Ext.data.Record.create({
    {name: 'name'},
    {name: 'tel'},
    {name: 'addr'}
});

var r = new R({
    name: 'Brook',
    tel: '0921',
    addr: 'tw'
});

grid.stopEditing();
store.insert(0, r);
grid.startEditing(0, 0);