iT邦幫忙

DAY 9
2

進程。Processing系列 第 9

[進程。Processing] 09.表格(Grid)

近來網頁設計有一個趨勢 SPA(Single Page Application),James 覺得將資訊整合在一頁,減少畫面捲動,可以減少使用者捲動畫面的不便。
為了能夠提供未來幾個月的進銷存預估資訊,以傳統 Table 的設計方式,即使以現在的寬螢幕,勢必會超過一個頁面的寬度而產生水平捲軸。

James 研究了很多前端 UI Framework 或 Component,Ext js 提供的彈性與設計方式,諸如:Locking ColumnGrouped Header、Column Sorting 等,在視覺呈現上,都帶來很不一樣的感受。

另一個好處是,可以先做欄位的安排與設定,不需要先處理資料,就可以先預覽畫面的樣子。James 先針對超額庫存進行欄位設計,在grid_Stock.js 中進行 Grid Config。

grid_Stock.js

Ext.require([
    'Ext.grid.*',
    'Ext.data.*',
    'Ext.util.*',
    'Ext.state.*'
]);

Ext.onReady(function () {

  // Ext js Component 顯示 Hint 的標準作法
  Ext.QuickTips.init();

  // 提供給各式 Component 使用的 Data Store,JsonStore 為其中的一種
  var store = new Ext.data.JsonStore({
    // store configs
    autoDestroy: true,
    storeId: 'Stock',
    proxy: {
      type: 'ajax',
      url: 'BusinessObject/qryEISbySQL.aspx?sql=select * from vw_PO_stock_proc where part_id=\'' + part_id + '\' and SLIP_DATE=\'' + SLIP_DATE + '\' and SLIP_NO=\'' + SLIP_NO + '\'',
      reader: {
        type: 'json'
      }
    },

    //alternatively, a Ext.data.Model name can be given (see Ext.data.Store for an example)
    fields: [
   { name: 'part_id', type: 'string' },
   { name: 'qty_2', type: 'long' },
   { name: 'qty_1', type: 'long' },
   { name: 'qty', type: 'long' },
   { name: 'eq', type: 'long' },
   { name: 'eq_1', type: 'long' },
   { name: 'eq_2', type: 'long' },
   { name: 'fsq', type: 'long' },
   { name: 'fq', type: 'long' },
   { name: 'fq_1', type: 'long' },
   { name: 'fq_2', type: 'long' },
   { name: 'fq_3', type: 'long' },
   { name: 'fq_4', type: 'long' },
   { name: 'pq_d', type: 'long' },
   { name: 'pq', type: 'long' },
   { name: 'pq_1', type: 'long' },
   { name: 'pq_2', type: 'long' },
   { name: 'pq_3', type: 'long' },
   { name: 'st_4', type: 'long' },
   { name: 'st_3', type: 'long' },
   { name: 'st_2', type: 'long' },
   { name: 'st_1', type: 'long' },
   { name: 'st', type: 'long' },
   { name: 'ft', type: 'long' },
   { name: 'ft_1', type: 'long' },
   { name: 'ft_2', type: 'long' },
   { name: 'sq_1', type: 'long' },
   { name: 'slow_amt', type: 'long' }
    ]
  });

  // create the Grid
  var grid = Ext.create( 'Ext.grid.Panel', {
    store: store,  // 引用之前宣告的 JsonStore
    columnLines: false,
    enableColumnHide: false,
    // 在 Grid Panel 上設定 tool button
    tools: [{
      type: 'help',
      tooltip: '圖例說明' ,
      handler: function (event, toolEl, panel) {
        // 結合 jQuery 的使用方式
        $( "#winStockHelp").dialog("open" );
      }
    }],
    // <!-- Grid Column Model Start -->,搭配 store 中所定義的 data fields,對應到 dataIndex
    columns: [{
      text: '料號',
      locked: true, // Lock Column
      align: 'left',
      width: 200,
      dataIndex: 'part_id'
    }, {
      text: '已提列金額' ,
      align: 'right',
      width: 85,
      renderer: Ext.util.Format.numberRenderer( '0,000,000,000'),
      dataIndex: 'slow_amt'
    }, {
      text: '庫存量',
      columns: [{
        text: lqty_2,
        width: 85,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'qty_2'
      }, {
        text: lqty_1,
        width: 85,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'qty_1'
      }, {
        text: lqty,
        width: 85,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'qty'
      }]
    }, {
      text: '預估庫存量' ,
      columns: [{
        text: leq,
        width: 85,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'eq'
      }, {
        text: leq_1,
        width: 85,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'eq_1'
      }, {
        text: leq_2,
        width: 85,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'eq_2'
      }]
    }, {
      text: '銷售預測(PM)' ,
      columns: [{
        text: lfsq,
        width: 80,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'fq'
      }, {
        text: lfq_1,
        width: 80,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'fq_1'
      }, {
        text: lfq_2,
        width: 80,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'fq_2'
      }, {
        text: lfq_3,
        width: 80,
        align: 'right',
        renderer:Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'fq_3'
      }]
    }, {
      text: '至今遲交' ,
      columns: [{
        text: lpq_d,
        width: 80,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'pq_d'
      }]
    }, {
      text: '採購待交' ,
      columns: [{
        text: lpq,
        width: 80,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'pq'
      }, {
        text: lpq_1,
        width: 80,
        align: 'right',
        renderer:Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'pq_1'
      }, {
        text: lpq_2,
        width: 80,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'pq_2'
      }, {
        text: '>' + lpq_2,
        width: 80,
        align: 'right',
        renderer: Ext.util.Format.numberRenderer( '0,000,000,000' ),
        dataIndex: 'pq_3'
      }]
    }],
     // <!-- Grid Column Model End -->
    height: 112,
    width: 968,
    title: '超額庫存分析',
    renderTo: 'grid-stock'
  });

  store.load({
    scope: this,
    callback: function (records, operation, success) {
      // the operation object
      // contains all of the details of the load operation    
      if (console && console.log) {
        console.log(records);
      }
    }
  }); 
});

Ext js 在 4.0 後的版本,為解決 Performance 的問題,提出了 Ext js Require 的架構,把有需要用到的 js block 以宣告的方式 require 進來,而不需要動用到整個 framework。

Ext.require([
    'Ext.grid.*',
    'Ext.data.*',
    'Ext.util.*',
    'Ext.state.*'
]);

一切的 js 執行都在 Ext.onReady(function () {...}); 這個指令中,所建構的 Configration。

Grid 的建構方式,是透過 Ext.create( 'Ext.grid.Panel' , {Config}); 來完成,Config 中可以針對 Grid API 中的 Properties 來設定其相關的屬性值,這是一種物件導向的設計方式,某些 Properties 又可以透過 Array 的方式來指定更多的 Object,如 Columns 中,庫存欄位又指定了一次 Columns 屬性,這就是 Ext js 4.0 新增的功能 Grouping Header,在原本單一庫存量的欄位中,又塞入了三個 Columns 及其屬性。

  {
      text: '庫存量' ,
      columns: [{
        text: lqty_2,
        width: 85,
        align: 'right' ,
        renderer: Ext.util.Format.numberRenderer( '0,000' ),
        dataIndex: 'qty_2'
      }, {
        text: lqty_1,
        width: 85,
        align: 'right' ,
        renderer: Ext.util.Format.numberRenderer( '0,000' ),
        dataIndex: 'qty_1'
      }, {
        text: lqty,
        width: 85,
        align: 'right' ,
        renderer: Ext.util.Format.numberRenderer( '0,000' ),
        dataIndex: 'qty'
      }]
    }

renderer 則是 Grid 中,提供給每一個 cell 可以指定額外的格式或者可以自定函式來達成想要呈現的功能,在上述程式碼中,主要是透過 Ext.util.Format.numberRenderer 來指定數字的格式。

整個設計的方式其實很直覺,同時 4.0 又提供了 Locking Grid Column 的功能(圖1),可以設計出像 Excel 凍結視窗的樣式,這樣在一個頁面中,就可以透過 Grid 的 Scrollbar 來達到多欄位資料的顯示,如果這些 API 提供的功能還不夠使用,也可以透過 extend 的方式,來建構自己的 Component。

圖1:Sencha Example Locking Grid Column (資料來源:Sencha 網站)

James 依序又插入幾個 Grid 區塊以及相對應的 Javascript 程式碼。搭配 Windows Resizer 這個瀏覽器的外掛程式(註1),將畫面調整成1024x768,畫面呈現上大致滿意後,接下來 James 開始構思 Data Model 應該怎麼設計。

註1:Windows Resizer Chrome 外掛 https://chrome.google.com/webstore/detail/window-resizer/kkelicaakdanhinjdeammmilcgefonfh

Prev Next

本篇全系列文章


上一篇
[進程。Processing] 08.佈局(Layout)
下一篇
[進程。Processing] 10.源生(Data Store)
系列文
進程。Processing31
0
海綿寶寶
iT邦大神 1 級 ‧ 2012-10-09 08:59:42

先推再請教
讚

我想請教 jamesjan 大大實務經驗中
是否有碰過類似的問題
以及對這兩篇題目的看法
謝謝

長時間運作的動態網頁技術
長時間運作的動態網頁技術─結案報告

0
ted99tw
iT邦高手 1 級 ‧ 2012-10-09 09:05:39

iT邦幫忙MVPantijava提到:
先推再請教

我也是,先推再等答案.....毆飛

0
jamesjan
iT邦高手 1 級 ‧ 2012-10-09 10:48:17

回覆海綿大的問題

長時間監控的網頁,我沒做過,所以沒遇過 memert leak 的問題。囧

不過從 wiseguy 大的文中可以知道,是要不斷的更新監控的圖片,透過 Ext js 以 AJAX 的方式去讀取監控數據,然後在前端呈現。就像 wiseguy 大講的,網頁的生命週期,Request/Response 得到結果,就算完成了,大部分 Browser 都會關掉,所以這些問題根本不會被注意到。之於我而言,我又何曾會有去開 Chrome 的背景頁面來看我程式的使用狀態的這種念頭。嘆氣

文中有提到 <img> 造成的 memory leak 的問題,因瀏覽器會 cache 下來,而又不斷更新的結果,圖片檔案會愈來愈多,終將塞爆硬碟的空間。

有一種方式是,將圖片以 Binary 格式的方式傳輸到前端,而不是以 img src 的方式傳回來,這樣不知道是否會產生 wiseguy 大所說的問題?(這個技術我在後面的章節會提到,在這裡就先不說明了。)

用法跟 wiseguy 大文中所提 <span style="background-image: url(圖檔URL); width: 寬px; height: 高px"/> 的方式很像,只是他不是 圖檔URL

概念上的理解大致說明,沒有實際的 implement 所以沒辦法提供合適的回覆。謝謝

iT邦幫忙MVPjamesjan提到:
長時間監控的網頁,我沒做過,所以沒遇過 memert leak 的問題。

我想你誤會我的問題了
就我這個外行的旁觀者來看
「長時間監控的網頁」和您上面的「資料查詢」頁面是類似的使用情境
差別在於
「長時間監控的網頁」資料是自動更新;您的是使用者動作後更新
「長時間監控的網頁」資料量較少(也許);您的系統資料量較多(也許)
如果使用者開啟您的系統畫面後不關掉
然後每隔兩分鐘就點一次「查詢」按鈕
那不就和「長時間監控的網頁」有很類似的系統行為了

我的重點在於您說的這句話
「沒遇過 memert leak 的問題。」

這句話在某種程度上
已經驗證了您的系統架構
是較佳的且值得大家學習的
謝謝

ted99tw iT邦高手 1 級 ‧ 2012-10-09 12:09:48 檢舉

泡芙阿姨趕快發飇,記得要一次將海綿給擰乾哦~~~開心

jamesjan iT邦高手 1 級 ‧ 2012-10-09 12:20:17 檢舉

To 海綿大
我的重點在於...

iT邦幫忙MVPjamesjan提到:
我沒做過

XD

0
jamesjan
iT邦高手 1 級 ‧ 2012-10-09 10:57:49
jamesjan iT邦高手 1 級 ‧ 2012-10-09 11:11:39 檢舉

http://www.interworks.com/sites/default/files/MemoryExample.html

這裡有 memory leak 的測試範例

測試結果

我要留言

立即登入留言