近來網頁設計有一個趨勢 SPA(Single Page Application),James 覺得將資訊整合在一頁,減少畫面捲動,可以減少使用者捲動畫面的不便。
為了能夠提供未來幾個月的進銷存預估資訊,以傳統 Table 的設計方式,即使以現在的寬螢幕,勢必會超過一個頁面的寬度而產生水平捲軸。
James 研究了很多前端 UI Framework 或 Component,Ext js 提供的彈性與設計方式,諸如:Locking Column、Grouped 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
回覆海綿大的問題
長時間監控的網頁,我沒做過,所以沒遇過 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 的問題。」
這句話在某種程度上
已經驗證了您的系統架構
是較佳的且值得大家學習的
泡芙阿姨趕快發飇,記得要一次將海綿給擰乾哦~~~
To 海綿大
我的重點在於...
iT邦幫忙MVPjamesjan提到:
我沒做過
查到幾篇文章的討論,關於 memory leak 的解法
Avoiding Memory Leaks and JavaScript best practices
http://forums.ext.net/showthread.php?19142-CLOSED-Memory-leak