extjs: Grid paging & Asp.Net 資料分頁技術

花了兩天的時間,找了資料分頁的相關技術資料 當DataGrid遇見100萬筆資料
再將 extjs grid paging 的 sample 好好看了一遍並測試
分頁展示的部份終於完成了

在進入程式說明之前,我們先來看一下整體運作的架構
首先我們讓User去查詢 po_finished.aspx 將已完成簽核的單據給撈出來
並以分頁的方式來呈現,當然Ajax及分頁的處理都交給 pgfinished.js
這個以 extjs library所撰寫的 javascript




image

由於之前提到我們是以 JsonStore 來擷取後端資料並以Json String的方式傳輸到前端
所以我們定義了一個 qryfinished2.aspx 來當作 pgfinished.js 跟 qryfinished.asmx 這個 Web Service 中間的橋樑

資料分頁的部份,除了前端操作介面傳遞 Page Number 與 Page Size 外
我們在資料庫的部份是以 Store Procedure 來接收前端傳過來的這兩個參數值

依據這樣的架構,我們就可以很簡單的享有資料分頁的功能

接下來我們進入程式說明的部份

qryMasterPage.master





<%@ Master CodeFile="qryMasterPage.master.vb" Inherits="qryMasterPage" Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd>
<html xmlns="http://www.w3.org/1999/xhtml>
<meta content="300" http-equiv="refresh" />
<head id="Head1" runat="server">
<title>採購簽核系統</title>
<link href="js/ext-2.0/resources/css/ext-all.css" rel="stylesheet" type="text/css" />
<link href="js/ext-2.0/resources/css/xtheme-gray.css" rel="stylesheet" type="text/css" />

<script src="js/ext-2.0/adapter/ext/ext-base.js" type="text/javascript"></script>

<script src="js/ext-2.0/ext-all.js" type="text/javascript"></script>

<link href="menus.css" rel="stylesheet" type="text/css" />
<link href="forms.css" rel="stylesheet" type="text/css" />
<!-- Common Styles for the examples -->
<link href="js/ext-2.0/examples/examples.css" rel="stylesheet" type="text/css" />
<style type="text/css">
html, body {
font:normal 12px verdana;
margin:0;
padding:0;
border:0 none;
overflow:hidden;
height:100%;
}
</style>

<script type="text/javascript">
function doSign(unid,uid,po_no,version,s){
s=(typeof(s)=="undefined") ? s="":s;
if (s=="finished")
window.open("po_read.aspx?unid="+unid+"&isApproval=0&stage="+s+"&po_no="+po_no+"&version="+version+"&uid="+uid,"PO","");
else
window.open("po_check.aspx?unid="+unid+"&stage="+s+"&po_no="+po_no+"&version="+version+"&uid="+uid,"PO","");
}

</script>

</head>
<body class="x-aero">

<script src="js/ext-2.0/examples/examples.js" type="text/javascript"></script>

<br />
<table align="center" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<div id="form-ct"> <!-- extjs Form Panel -->
<form id="form1" class="x-form">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</form>
</div>
</td>
</tr>
</table>
</body>
</html>


po_finished.aspx
因為有多個畫面都共用一個 Master Page,所以將個別使用的部份寫在
ContentPlaceHolder 裡面


<%@ Page AutoEventWireup="false" CodeFile="po_finished.aspx.vb" Inherits="po_finished"
Language="VB" MasterPageFile="~/qryMasterPage.master" %>

<asp:Content ID="Content1" runat="Server" ContentPlaceHolderID="ContentPlaceHolder1">

<script type="text/javascript">
var WF_UserID="<%=WF_UserID %>";
var poTitle="已完成簽核查詢";
</script>

<script src="combos.js" type="text/javascript"></script>

<link href="combos.css" rel="stylesheet" type="text/css" />

<script src="pgfinished.js" type="text/javascript"></script> <!-- extjs Library -->

<div id="pb_master">
<form id="qryFinished" class="x-form">
<table align="center" border="0" cellpadding="0" cellspacing="2" width="100%">
<tr>
<td>
採購單號:</td>
<td>
<div>
<input id="po_no" size="20" type="text" />
</div>
</td>
<td>
料號:</td>
<td>
<div>
<input id="part_no" size="20" type="text" />
</div>
</td>
<td>
供應商:</td>
<td>
<div>
<input id="vendor" size="20" type="text" />
</div>
</td>
</tr>
<tr>
<td>
部門:
</td>
<td>
<div>
<input id="cboDept" size="20" type="text" />
</div>
</td>
<td>
產品線:
</td>
<td>
<div>
<input id="cboBrand" size="20" type="text" />
</div>
</td>
<td>
PM:</td>
<td>
<div>
<input id="cboPM" size="20" type="text" />
</div>
</td>
</tr>
<tr>
<td>
採購日期:</td>
<td>
<div>
<input id="pod1" size="20" type="text" /></div>
</td>
<td>
~</td>
<td>
<div>
<input id="pod2" size="20" type="text" />
</div>
</td>
<td>
&nbsp;
</td>
<td>
&nbsp;
</td>
</tr>
<tr>
<td>
URD:</td>
<td>
<div>
<input id="urd1" size="20" type="text" /></div>
</td>
<td>
~</td>
<td>
<div>
<input id="urd2" size="20" type="text" />
</div>
</td>
<td>
&nbsp;
</td>
<td>
<div id="btnQuery">
</div>
</td>
</tr>
<tr>
<td>
VSD:</td>
<td>
<div>
<input id="vsd1" size="20" type="text" /></div>
</td>
<td>
~</td>
<td>
<div>
<input id="vsd2" size="20" type="text" />
</div>
</td>
<td>
&nbsp;
</td>
<td>
&nbsp;
</td>
</tr>
</table>
</form>
</div>
</asp:Content>


pgfinished.js





/*
* Ext JS Library 2.0
* Copyright(c) 2006-2007, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/

Ext.onReady(function(){

Ext.QuickTips.init();
// turn on validation errors beside the field globally
Ext.form.Field.prototype.msgTarget = 'side';
// create the Data Store
var store = new Ext.data.JsonStore({
url: 'http://xxx/PO/qryfinished2.aspx?uid=,
root: 'data',
totalProperty: 'totalCount',
id: 'ReferenceNo',
fields: [
'sno','ReferenceNo', 'Version', 'UserName', 'Actions', 'StartTime', 'SignTime', 'Diff'
]
});

// pluggable renders
function renderReferenceNo(value, p, record){
return String.format(
'<a href="javascript:void(0);" onclick="doSign(\'{0}\',\'{1}\',\'{2}\',\'{3}\',\'finished\');" title="點此開啟採購單">{2}</a>',
record.id, WF_UserID, value, record.data['Version']);
}
function renderDiff(value, p, record){
var dd, hh, mm, ss;
var t;

t=record.data.SignTime-record.data.StartTime;
dd = t.getDay();
hh = t.getHours() - dd * 24
mm = t.getMinutes() - dd * 24 * 60 - hh * 60
ss = t - dd * 24 * 60 * 60 - hh * 60 * 60 - mm * 60
return String.format('{0}天又{1}小時{2}分{3}秒', dd, hh, mm, ss);
}

var fs = new Ext.FormPanel({
id: 'frmFinished',
collapsible:true,
frame: true,
title:'採購簽核-'+poTitle,
labelAlign: 'right',
labelWidth: 85,
width:800,
waitMsgTarget: true,
tbar: [''],
items: [new Ext.form.FieldSet({
title: '查詢條件',
collapsible: true,
animCollapse: true,
autoHeight: true,
defaultType: 'textfield',
contentEl: 'pb_master'
}),new Ext.grid.GridPanel({
id: 'grdFinished',
store: store,
frame: true,
//autoHeight: true,
autoScroll: true,
loadMask: true,
sm: new Ext.grid.RowSelectionModel({singleSelect:true}),
stripeRows: true,
columns: [
{ header: '', dataIndex: 'sno', width: 30, align: 'right' },
{ id: 'ReferenceNo', header: '採購單號', dataIndex: 'ReferenceNo', width: 80, renderer: renderReferenceNo, align: 'center', sortable: true },
{ header: '申請者', dataIndex: 'UserName', width: 50, align: 'center', sortable: true },
{ header: '簽核結果', dataIndex: 'Actions', width: 70, align: 'center', sortable: true },
{ header: '申請時間', dataIndex: 'StartTime', width: 150, align: 'center', sortable: true },
{ header: '完成時間', dataIndex: 'SignTime', width: 150, align: 'center', sortable: true },
{ header: '簽核歷時', dataIndex: 'Diff', width: 150, sortable: true }
],
width: 788,
height: 350,
bbar: new Ext.PagingToolbar({
pageSize: 25,
store: store,
displayInfo: true,
displayMsg: '{0} - {1} of {2}',
emptyMsg: "查無資料"
})
})]
});

fs.render('form-ct');

store.load({params:{start:0, limit:25}}); // 必要參數

fs.getTopToolbar().add('->', {
text: '',
tooltip: '重新整理',
iconCls: 'breload',
handler: function(){
location.reload();
}
});

});


在 pgfinished.js 裡面由上到下,我們先定義一個 Ext.data.JsonStore 將 qryfinished2.aspx 中所傳回的 Json String
存在 store 變數中,供 Grid Panel 使用

在 Form Panel 上我們定義了一個 Field Set 以及一個 Grid Panel,在 Grid Panel 中我們將 data store 指向我們剛定義的
store 變數,並在底下加上一個 PageToolBar (bbar 參數),PageSize設為 25 即一頁顯示25筆 Record

Define 完 UI 部份,我們將 Form Panel render 在 qryMasterPage.master 中的 <DIV> tag "form-ct"
即 Form 的 Container,同時開始進行資料的擷取 store.load()

由於我們要做分頁,所以必須將參數傳遞給 store 中 define 的 url

qryfinished2.aspx


<%@ Page Language="VB" AutoEventWireup="false" CodeFile="qryfinished2.aspx.vb" Inherits="qryfinished2" %>
<%=JSONString%>


qryfinished2.vb


Partial Class qryfinished2
Inherits System.Web.UI.Page
Public JSONString As String

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim myJSONObject As New qryfinished ' 建立 Web Service 物件
Dim uid As String = Request.Params("uid")
Dim start As Long = CLng(Request.Params("start")) '必須使用 Request.Params 來接參數
Dim limit As Integer = CInt(Request.Params("limit")) ' Request.QueryString 接不到
Dim pg As Integer

If start = 0 Then
pg = 1
Else
pg = (start / limit) + 1 '由於 extjs 中 start 指的是 開始的列數, 所以要透過計算轉換成頁數 (重要!!)
End If

JSONString = myJSONObject.ToJSONString(uid, pg, limit) ' 將 JSONString 傳至前端
End Sub
End Class


接下來我們來看 qryfinished.asmx的程式


Imports System.Web
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports Newtonsoft.Json
Imports CommonModule
Imports Audits

<WebService(Namespace:="http://xxx/webservices> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class qryfinished
Inherits System.Web.Services.WebService

Dim connERP As System.Data.Odbc.OdbcConnection = Nothing
Dim connWorkFlow As System.Data.SqlClient.SqlConnection = Nothing
Dim connWorkFlow1 As System.Data.SqlClient.SqlConnection = Nothing

<WebMethod()> _
Public Function ToJSONString(ByVal uid As String, ByVal pg As Long, ByVal limit As Integer) As String
Dim strSQL As String
Dim sqlCommand As System.Data.SqlClient.SqlCommand
Dim drWorkflow As System.Data.SqlClient.SqlDataReader = Nothing
Dim iCount As Integer = 1, totalcount As Long
Dim starttime As String = "", signtime As String = Now().ToString
Dim oAudits() As Audits
Dim po_no As String, version As String

ReDim oAudits(0)

Try

Init_Connection()

strSQL = "select count(*) as n from vw_po_finished" ' 計算總筆數 store 中的 totalProperty 使用

sqlCommand = New System.Data.SqlClient.SqlCommand(strSQL, connWorkFlow)

drWorkflow = sqlCommand.ExecuteReader()

If drWorkflow.Read() Then
totalcount = CLng(drWorkflow.GetValue(drWorkflow.GetOrdinal("n")))
End If

drWorkflow.Close()

strSQL = "exec sp_po_finished @page=" + CStr(pg) + ",@limit=" + CStr(limit) + ",@userid='" + uid + "'"

sqlCommand = New System.Data.SqlClient.SqlCommand(strSQL, connWorkFlow)

drWorkflow = sqlCommand.ExecuteReader()

Do While drWorkflow.Read() '組織 Json Array
po_no = drWorkflow.GetString(drWorkflow.GetOrdinal("ReferenceNo"))
version = drWorkflow.GetString(drWorkflow.GetOrdinal("Version"))

ReDim Preserve oAudits(iCount - 1)

starttime = drWorkflow.GetDateTime(drWorkflow.GetOrdinal("StartTime")).ToString()

oAudits(iCount - 1) = New Audits()
oAudits(iCount - 1).sno = iCount
oAudits(iCount - 1).UserID = uid
oAudits(iCount - 1).StartTime = starttime
oAudits(iCount - 1).UserName = drWorkflow.GetString(drWorkflow.GetOrdinal("UserName"))
'oAudits(iCount - 1).InstanceID = drWorkflow.GetString(drWorkflow.GetOrdinal("InstanceID"))
oAudits(iCount - 1).ReferenceNo = po_no
oAudits(iCount - 1).Version = version
signtime = drWorkflow.GetDateTime(drWorkflow.GetOrdinal("SignTime")).ToString()
oAudits(iCount - 1).SignTime = signtime
oAudits(iCount - 1).Actions = drWorkflow.GetString(drWorkflow.GetOrdinal("Actions"))

oAudits(iCount - 1).Diff = getSignTime(CDate(starttime), CDate(signtime))
iCount += 1

Loop

drWorkflow.Close()
Catch ex As System.Data.Odbc.OdbcException
My.ErrorHandling.ExceptionHandler(ex, Resources.Settings.smtphost, Resources.Settings.From, Resources.Settings.CopyTo)
Finally
If Not drWorkflow Is Nothing Then drWorkflow = Nothing

Close_Connection()
End Try

Return "{""totalCount"":" + CStr(totalcount) + ",""data"":" + JavaScriptConvert.SerializeObject(oAudits) + "}" ' 回傳 Json String
End Function

Private Sub Init_Connection()
getConnectionString()

connWorkFlow = getSqlConnection(connWorkFlowString)
connWorkFlow.Open()

End Sub

Private Sub Close_Connection()
If connWorkFlow.State = Data.ConnectionState.Open Then connWorkFlow.Close()

If Not connWorkFlow Is Nothing Then connWorkFlow = Nothing
End Sub
End Class


最後我們再說明一下 SQL Server 2000 中如何透過 T-SQ來達到資料分頁的作法


CREATE PROCEDURE [dbo].[sp_po_finished]
@page as int,
@limit as int,
@userid as varchar(10)=''
AS
begin
declare @strSQL varchar(5000)

set @strSQL='SELECT TOP '+convert(varchar(10),@limit)+' * from vw_po_finished '
set @strSQL=@strSQL+'WHERE ReferenceNo+Version not in (SELECT TOP '+convert(varchar(10),(@page-1)*@limit)+' c.ReferenceNo+c.Version from vw_po_finished c '
set @strSQL=@strSQL+'ORDER BY c.SignTime desc, c.ReferenceNo, c.Version) '
set @strSQL=@strSQL+'ORDER BY SignTime desc, ReferenceNo, Version'

print @strSQL

exec (@strSQL)

end
GO


說穿了,就是透過兩個 select TOP statement 來完成,舉例說 我們要看 26~50 筆的記錄
就是 select TOP 25 ... NOT IN (select TOP (2-1)*25 ...),因為 26~50 筆在第二頁 :)

完成圖

image

程式碼

po_finished.rar

extjs: 使用JsonStore & .Net Web Service讀取資料庫(Smaple Code)

應網友要求提供Sample Code

extjs: 使用JsonStore & .Net Web Service讀取資料庫


extjs 在 2.0 之後針對資料擷取的部份,做了大幅度的改善
透過很直覺的定義方式,可以很快的建構出以Grid為base的資料呈現方式

底下要示範的是,如何透過.Net的Web Service以及Ext.data.JsonStore將資料庫中的已完成簽核紀錄快速讀取出來

首先我們先建構.Net的Web Service
qryfinished.asmx 他會在 App_Code下產生一個 qryfinished.aspx
在這邊我們還會需要使用一個 Audits class 來建構我們所需的 Json Array





Imports Microsoft.VisualBasic

Public Class Audits
Public sno As Integer
Public InstanceID As String
Public ReferenceNo As String
Public Version As String
Public FTime As String
Public StartTime As String
Public SignTime As String
Public UserID As String
Public UserName As String
Public ApproverID As String
Public ApproverName As String
Public Actions As String
Public Diff As String
Public vendor As String
Public dept As String
End Class


上述的class就是我們要產生的Json Array的格式





Imports System.Web
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports Newtonsoft.Json
Imports CommonModule
Imports Audits ' define一個Json Array所需的 class

<WebService(Namespace:="http://xxx/webservices> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class qryfinished
Inherits System.Web.Services.WebService

<WebMethod()> _
Public Function ToJSONString(ByVal uid As String) As String
Dim strSQL As String
Dim connWorkFlow1 As System.Data.SqlClient.SqlConnection = Nothing
Dim sqlCommand As System.Data.SqlClient.SqlCommand, sqlCommand1 As System.Data.SqlClient.SqlCommand
Dim drWorkflow As System.Data.SqlClient.SqlDataReader = Nothing, drWorkflow1 As System.Data.SqlClient.SqlDataReader = Nothing
Dim cmdWMS As System.Data.SqlClient.SqlCommand
Dim drWMS As System.Data.SqlClient.SqlDataReader = Nothing
Dim iCount As Integer = 1
Dim WF_UserID As String = uid
Dim starttime As String = "", signtime As String = Now().ToString
Dim oAudits() As Audits ' define Json Array
Dim pb_no As String = ""

ReDim oAudits(0)

Try

strSQL = "select distinct SLIP_NO from CELL_TMP_TB where status='C' order by SLIP_NO"

If connWMS Is Nothing Then
Connection(False, True, False)
End If

If connWorkFlow Is Nothing Then
Connection(False, False, True)
End If

If connWMS.State <> Data.ConnectionState.Open Then
connWMS.Open()
End If

cmdWMS = New System.Data.SqlClient.SqlCommand(strSQL, connWMS)

drWMS = cmdWMS.ExecuteReader()

Do While drWMS.Read()
pb_no = drWMS.GetString(drWMS.GetOrdinal("SLIP_NO"))
strSQL = "select top 1 * from Audits NOLOCK where ReferenceNo='" + pb_no + "' order by StartTime"

If connWorkFlow.State <> Data.ConnectionState.Open Then
connWorkFlow.Open()
End If

sqlCommand = New System.Data.SqlClient.SqlCommand(strSQL, connWorkFlow)

drWorkflow = sqlCommand.ExecuteReader()
If drWorkflow.Read() Then
ReDim Preserve oAudits(iCount - 1)

starttime = drWorkflow.GetDateTime(drWorkflow.GetOrdinal("StartTime")).ToString()

oAudits(iCount - 1) = New Audits()
oAudits(iCount - 1).sno = iCount
oAudits(iCount - 1).UserID = WF_UserID
oAudits(iCount - 1).StartTime = starttime
oAudits(iCount - 1).UserName = drWorkflow.GetString(drWorkflow.GetOrdinal("UserName"))

GetERP(oAudits(iCount - 1), pb_no)

strSQL = "select top 1 * from Audits NOLOCK where ReferenceNo='" + pb_no + "' order by SignTime desc"

connWorkFlow1 = getSqlConnection("cnnWorkFlow")
sqlCommand1 = New System.Data.SqlClient.SqlCommand(strSQL, connWorkFlow1)
connWorkFlow1.Open()

drWorkflow1 = sqlCommand1.ExecuteReader()
If drWorkflow1.Read() Then
oAudits(iCount - 1).InstanceID = drWorkflow1.GetString(drWorkflow1.GetOrdinal("InstanceID"))
oAudits(iCount - 1).ReferenceNo = drWorkflow1.GetString(drWorkflow1.GetOrdinal("ReferenceNo"))
signtime = drWorkflow1.GetDateTime(drWorkflow1.GetOrdinal("SignTime")).ToString()
oAudits(iCount - 1).SignTime = signtime
oAudits(iCount - 1).Actions = drWorkflow1.GetString(drWorkflow1.GetOrdinal("Actions"))
End If
drWorkflow1.Close()
connWorkFlow1.Close()

oAudits(iCount - 1).Diff = getSignTime(CDate(starttime), CDate(signtime))
End If
iCount += 1
drWorkflow.Close()
Loop

drWMS.Close()
Catch ex As System.Data.Odbc.OdbcException
My.ErrorHandling.ExceptionHandler(ex, Resources.Settings.smtphost, Resources.Settings.From, Resources.Settings.CopyTo)
Finally
If Not drWorkflow Is Nothing Then drWorkflow = Nothing
If Not drWorkflow1 Is Nothing Then drWorkflow1 = Nothing
If Not drWMS Is Nothing Then drWMS = Nothing
If Not connWorkFlow1 Is Nothing Then connWorkFl ow1 = Nothing
End Try
' 將 Json Array 透過 JavaScriptConvert.SerializeObject 轉換成序列化的String資料以利Internet傳輸
Return "{""totalCount"":" + CStr(iCount - 1) + ",""data"":" + JavaScriptConvert.SerializeObject(oAudits) + "}"
End Function

End Class


回傳的 Json String




{"totalCount":1,"data":[{"sno":1,"InstanceID":"7C038193C70EBC50482573C500328A3B","ReferenceNo":"XX -7C0358","Version":null,"FTime":null,"StartTime":"2008/1/3 下午 05:12:02","SignTime":"2008/1/3 下午 05:52:00","UserID":"nnnnnn","UserName":"AAA","ApproverID":null,"ApproverName":null,"Actions":"確認","Diff":"39分58秒","vendor":"XXXX","dept":"XXX"}]}

由於JasonStore要回傳的是一個格式化過的Json Array資料
所以我們要另外建構一個qryfinished2.aspx透過它來呼叫qryfinished這個Web Service
並透過Web Service中的ToJSONString將Json Array以序列化的方式回傳到前端網頁中





Partial Class qryfinished2
Inherits System.Web.UI.Page
Public JSONString As String

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim myJSONObject As New qryfinished ' 呼叫 Web Service
Dim uid As String = Request.QueryString("uid")

JSONString = myJSONObject.ToJSONString(uid)
End Sub
End Class


qryfinished2.aspx, 只呈現 Json String





<%@ Page Language="VB" AutoEventWireup="false" CodeFile="qryfinished2.aspx.vb" Inherits="qryfinished2" %>
<%=JSONString%>


建構好Web Service及呈現Json String的網頁後,接下來我們要建構 extjs 的UI部分





/*
* Ext JS Library 2.0
* Copyright(c) 2006-2007, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/

Ext.onReady(function(){

Ext.QuickTips.init();

// turn on validation errors beside the field globally
Ext.form.Field.prototype.msgTarget = 'side';

// create the Data Store
// 透過qryfinished2.aspx去讀取qryfinished.asmx這個Web Service抓出已簽核完成的資料
// 作為 Grid 的資料來源
var store = new Ext.data.JsonStore({
url: 'http://xxx/DateCode/qryfinished2.aspx?uid=,
root: 'data',
id: 'InstanceID',
fields: [
'sno','ReferenceNo', 'UserName', 'Actions', 'StartTime', 'SignTime', 'Diff', 'dept', 'vendor'
]
});

// 呼叫 define 在 HTML 中的 Javascript 讓第一個欄位可以點擊後開啟資料頁
function renderReferenceNo(value, p, record){
return String.format(
'<a href="javascript:void(0);" onclick="doSign(\'{0}\',\'{1}\',\'{2}\',\'worklist\');" title="點此開啟入庫單">{2}</a>',
record.id, WF_UserID, value);
}

// define 一個 FormPanel 上面擺 Grid
var fs = new Ext.FormPanel({
collapsible:true,
frame: true,
title:'Date Code 逾期簽核-'+dcTitle,
labelAlign: 'right',
labelWidth: 85,
width:800,
waitMsgTarget: true,
tbar: [''],
// 顯示在FormPanel上的元件
items: [new Ext.form.FieldSet({
title: '查詢條件',
collapsible: true,
animCollapse: true,
autoHeight: true,
defaultType: 'textfield'/*,
contentEl: 'pb_master'*/
}),new Ext.grid.GridPanel({
store: store,
frame: true,
//autoHeight: true,
autoScroll: true,
loadMask: true,
sm: new Ext.grid.RowSelectionModel({singleSelect:true}),
stripeRows: true,
columns: [
{ header: '', dataIndex: 'sno', width: 30, align: 'right' },
{ id: 'ReferenceNo', header: '入庫單號', dataIndex: 'ReferenceNo', width: 80, renderer: renderReferenceNo, align: 'center', sortable: true },
{ header: '部門', dataIndex: 'dept', width: 50, align: 'center', sortable: true },
{ header: '申請者', dataIndex: 'UserName', width: 50, align: 'center', sortable: true },
{ header: '廠商', dataIndex: 'vendor', width: 80, align: 'center', sortable: true },
{ header: '簽核結果', dataIndex: 'Actions', width: 70, align: 'center', sortable: true },
{ header: '申請時間', dataIndex: 'StartTime', width: 150, align: 'center', sortable: true },
{ header: '完成時間', dataIndex: 'SignTime', width: 150, align: 'center', sortable: true },
{ header: '簽核歷時', dataIndex: 'Diff', width: 150, sortable: true }
],
width: 788,
height: 400
})]
});

fs.render('form-ct'); // 將 FormPanel render 在 "form-ct" 這個 div tag 中

store.load(); // 讀取資料

fs.getTopToolbar().add('->', {
text: '',
tooltip: '重新整理',
iconCls: 'breload',
handler: function(){
location.reload();
}
});
});


在給前端查詢的頁面中,我們只要將 define 好的 pgfinished.js include 進來即可





<%@ Page Language="VB" MasterPageFile="~/qryMasterPage.master" AutoEventWireup="false" CodeFile="dc_finished.aspx.vb" Inherits="dc_finished" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<script type="text/javascript">
var WF_UserID="<%=WF_UserID %>";
var dcTitle="已完成簽核查詢";
</script>
<script src="pgfinished.js" type="text/javascript"></script>
</asp:Content>


部份程式在Master Page 中





<%@ Master CodeFile="qryMasterPage.master.vb" Inherits="qryMasterPage" Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd>
<html xmlns="http://www.w3.org/1999/xhtml>
<!--<meta content="30" http-equiv="refresh" />-->
<head id="Head1" runat="server">
<title>Date Code逾期簽核系統</title>
<link href="js/ext-2.0/resources/css/ext-all.css" rel="stylesheet" type="text/css" />
<link href="js/ext-2.0/resources/css/xtheme-gray.css" rel="stylesheet" type="text/css" />
<script src="js/ext-2.0/adapter/ext/ext-base.js" type="text/javascript"></script>

<script src="js/ext-2.0/ext-all.js" type="text/javascript"></script>

<link href="menus.css" rel="stylesheet" type="text/css" />
<link href="forms.css" rel="stylesheet" type="text/css" />
<!-- Common Styles for the examples -->
<link href="js/ext-2.0/examples/examples.css" rel="stylesheet" type="text/css" />
<style type="text/css">
html, body {
font:normal 12px verdana;
margin:0;
padding:0;
border:0 none;
overflow:hidden;
height:100%;
}
</style>

<script type="text/javascript">
function doSign(unid,uid,pb_no,s){
s=(typeof(s)=="undefined") ? s="":s;
if (s=="finished")
window.open("dc_read.aspx?unid="+unid+"&stage="+s+"&pb_no="+pb_no+"&uid="+uid,"DC","");
else
window.open("dc_check.aspx?unid="+unid+"&stage="+s+"&pb_no="+pb_no+"&uid="+uid,"DC","");
}

</script>

</head>
<body class="x-aero">

<script src="js/ext-2.0/examples/examples.js" type="text/javascript"></script>

<br />
<form id="form1" class="x-form">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</form>
<table align="center" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<div id="form-ct">
<div id="pb_master">
</div>
</div>
</td>
</tr>
</table>
</body>
</html>


完成圖

新圖片

建構 extjs 的 Application 也是符合 MVC 的規範
Model: Web Service
View: HTML
Controller: extjs
以這樣的方式去思考,就比較容易清楚掌握該如何撰寫 extjs 的 Application 了

extjs 1.1 & 2.0 設計風格比較

趕在去年底用一個禮拜的時間將一個系統以 extjs 2.0 的 library 建構起來

由於有之前 1.1 的開發經驗,在學習與習慣 2.0 的API確實節省了許多時間

就整個 framework 來看,2.0 的物件功能更為精簡與直覺

以Container為基礎,在上面去建構新的Component元件或是Container

這樣就組合出多樣化的Web UI,在資料的展現上更為快速與便利(開發的時間也大幅降低)

以下是 1.1 與 2.0 所開發出來的畫面,供參考







1.1
2.0
新圖片 (1)
新圖片 (2)
新圖片 (4)
新圖片 (3)
新圖片 (5)
新圖片 (7)

extjs the No. 1 Javascript Library you must have


從1.0版之前就開始接觸,到現在已到了2.0 Alpha Release

將Web UI發揮到極致的一套Library就屬extjs莫屬

 

最近的一個專案,套用了extjs 1.1.1的版本就快結案了

誰知他2.0版超炫目的豋場,等上線run一陣再改用2.0的Library吧

 

過幾天再將成果Post上來

Ajax Domino Controller v0.5.0 released

New features of Ajax Domino Controller version 0.5.0 (NTF):


  • getAllEntriesByKey
  • runAgent
  • getDocumentByUNID
  • RaiseError - XML error handling
  • DocumentToXML - unified the XML output for each Function

 You can get the error message raised on Domino server, by parsing the returned XML document with the tagName "ajax-error",
to see what happened on server.
And it is more convinient to out put XML by put your result in the variable xml,
after you assembly your xml output, just call DocumentToXML(xml)
It will format your xml result to a formal XML document.

Try it. :)

Ajax Domino Controller

Ajax Domino Controller is a Domino web development framework by using AJAX javascript and Domino Agent.

It provides web developers to retirve Domino data with no COM object nor CORBA framework, just javascript function call and XML for communication.

 

Now, the released version is 0.1.0 and also many functions need to be developed and tested. You can download the Ajax Domino Controller template and ajaxdomino.js for using on your web page with Domino environment.

 

Also, you need to download the prototype.js on http://prototypejs.org/download, and used on your web pages by adding the following scripts.

 

  <script src="path/to/prototype.js" type="text/javascript"></script>
  <script src="path/to/ajaxdomino.js" type="text/javascript"></script>

In ajaxdomino.js, you can use dblookup to retrive XML data like Formula @DBLookup does.

You provide server,database,view,key,field and onComplete function to handle the XML data with your page.

In the parameter field, you can provide ColumnNumer or fieldName to do with, and not only one column or one field can do,

you can retrive two or more field value by useing comma with it.

 

e.g. dblookup('myserver','mydb','myview','mykey','field1,field2,field3','myCompletefunction');

 

// Change the bold block to your Domino IP/domain name


function dblookup(server,database,view,key,field,onComplete){
  var pars='action=dblookup'+
           '&server='+escape(server)+
           '&database='+escape(database)+
           '&view='+escape(view)+
           '&key='+escape(key)+
           '&field='+escape(field);
  //alert(pars);
  var myAjax=new Ajax.Request(ajaxdominocontrollerurl, {
    method: 'get',
    parameters: pars,
    asynchronous: true,          
    onComplete: eval(onComplete),
    onSuccess: function(resp) {
      //alert("The response from the server is: " + resp.responseText);
    },
    onFailure: function(resp) {
      alert('Sorry. There was an error.');
    },
    onException: function(resp, exception) {
      var description = '';
      for (var property in exception) {
        description += property + '=' + exception[property] + '\n';
      }
      alert('Exception: ' + exception + '\n' + description);
    }
  });        
}

 

The prototype AJAX object will take handle with the Ajax Domino Controller agent "ActionListener" and pass the parameters through Domino web container.

As the ActionListener agent received the request, it will parse the action field and switch to the properly function to do with.

 

The Ajax Domino Controller also provide an error handling function call to send email on the error raised event.

You should also change the constant variable "Administrator" in the ErrorHandling script library to your desired email,

and testing the agent on your Domino server.

 

Be remided, you should create a new Database with the template "AjaxDominoController.ntf" on your Domino server,

sign the database you created with your Adminstration account and change the ACL of "default" from Administrator to Reader.

 

After the responsed XML returned, it will call the onComplete function you provided with dblookup function call, it will look like this

 

    function showResponse(resp) {
      //alert(resp.responseText);
      var xmlDoc=resp.responseXML;
      var x
      
      x=xmlDoc.getElementsByTagName(Field);
      $('Result').innerHTML="<select id='lst'><option></option></select>";
      for (var i=0;i<x.length;i++)
      {       
        var value = x[i].childNodes[0].text;
        $('lst').options[i]=new Option(value,value);
      }
    }

    .....

 

    <div id="Result"></id>

 

Very straight forward, enjoy it.

 

 

 

客戶信用評等查詢系統

忙了一陣子, 終於把客戶信用評等查詢系統的操作說明文件給生出來了

http://jamesjantw.googlepages.com/credibility.pdf

這個使用 Ajax 技術開發的系統(後端是 JSP+Informix DB<-Tiptop ERP),

在概念上除了 non postback 的操作方式之外

最重要的是前端介面與後端資料的切割可以更容易與釐清

讓 User 在操作上有如操作一般的 Windows AP

並且省去傳輸一大堆 HTML tag 的作法與限制

是 Ajax 的主要內涵

也因此帶來了網頁技術的革命里程碑

邁向 Web 2.0 的時代

讓意念可以更自由的發揮

Google Web Toolkit Blog

50个和AJAX有关的工具和框架

今天在 Google Group 裡, 有一篇名為 50个和AJAX有关的工具和框架
介紹了許多跟 Ajax 開發有關的網站與 framework
蠻值得收藏的!!

快瞧瞧去!

Ajax 完成客戶財報查詢

完成了進階查詢的畫面了

 

這個畫面跟之前的那一個是在同一個 page 喔

 

並沒有 reload 過

 

只是運用了 javascript + css 的技巧將查詢條件的 block 作顯示跟隱藏

 

基本的架構還是一樣 透過 XHTTP 去要後端的資料

只不過這次的資料是經過繁複計算的

 

但是只要你查過的資料就會被 cache 住, 這樣別人再查時

資料就是即時出現囉

 

同時在 XHTTP 去要資料時, 可以在 Client 端作一些像 progress bar 等的動態顯示

讓 user 不致以為沒有在運作

 

如果你有仔細看圖片 可以發現 這些資料是 scrollable 的

就像 Excel 凍結視窗一樣

而且在一個 browser 的可是範圍內就可以將所有的資料展現出來

不會破壞掉畫面的結構

 

關於 scrollable table 的作法 下次再介紹囉!!

Ajax 完成 AutoComplete

Ajax 真的令人著迷

 

為了方便使用者的輸入, 提供了類似 Google Suggest & Gmail/Yahoo Mail 所提供的功能

讓使用者輸入一個字及可帶出相關的選項供選擇

 

其做法也是在 Page 中定義一個 <div> Tag 來供 XHttpRequest 將回傳的 responseText

加入其 innerHTML 屬性中, 再透過 Javascript 與 DOM 的操作來控制該 <div> Tag 顯示的位置

 

後端要注意的是, 輸出的 HTML (目前我還是習慣用 HTML 的方式輸出, 因為 XML 還需搭配 XSLT 來呈現

還沒學到那邊), 要能與原先的 Page 互動. (可以在主 Page 上 define 一個 JS Function 供呼叫)

 

程式片段:

 

CSS 與 DIV





 

 <style type="text/css">
 DIV{
  scrollbar-DarkShadow-Color:#9FB7D7;
  scrollbar-Track-Color:#F7F7F7;
  scrollbar-Face-Color:#C7CFFF;
  scrollbar-Shadow-Color:#FFFFFF;
  scrollbar-Highlight-Color:#FFFFFF;
  scrollbar-3dLight-Color:#C7CFF7;
  scrollbar-Arrow-Color:#4F5F87;
 }
 .autocomplete {
  background:#E0E0E0;
  position: absolute;
  border: solid 1px;
  overflow-y:scroll;
  overflow-x:auto;
  height:100px;
  display: none;
 }

</style> 
<div id="autocomplete" class="autocomplete"></div>

 

 

Script






 <script type="text/javascript">
  var oauto=new AjaxService();
  oauto.callback=autoCompleteResponse;  


  
  function autoCompleteField(cust_code) {
   event.cancelBubble=true;
    var key=window.event.keyCode;
   switch (key){
     case 9,13,27: // Tab, Enter, Esc 鍵
      return;
   }
  
   var querystring="cust_code="+encodeURI(cust_code);
   var url = "/Authority/autoComplete.jsp?"+querystring;
   oauto.loadXMLDoc(url);
   return false;
  }
   
  function autoCompleteResponse() {
   // only if req shows "loaded"
   if (oauto.xhttp.readyState == 4) {
    // only if "OK"
    if (oauto.xhttp.status == 200) {            
     $('autocomplete').innerHTML = oauto.xhttp.responseText;
     $('autocomplete').style.display="block";
     $('autocomplete').style.width=$('cust_code').offsetWidth;
     var point = fGetXY($('cust_code'));
     $('autocomplete').style.top=point.y+$('cust_code').offsetHeight+15;
     $('autocomplete').style.left=point.x+10;
    } else {
     alert(errormsg + oauto.xhttp.statusText);
    }
   }
  }    


  function Point(iX, iY){
   this.x = iX;
   this.y = iY;
  }  
  
  function fGetXY(aTag){
   var oTmp = aTag;
   var pt = new Point(0,0);
   do {
    pt.x += oTmp.offsetLeft;
    pt.y += oTmp.offsetTop;
    oTmp = oTmp.offsetParent;
   } while(oTmp.tagName!="BODY");
   return pt;
  }  
  
  function ChangeSelect(){
   event.cancelBubble=true;
    var key=window.event.keyCode;
   switch (key){
     case 9,13,27: // Tab, Enter, Esc 鍵
      $('autocomplete').style.display="none";
   }
  }
  
  function setValue(v){
    $('cust_code').value=v;
   $('autocomplete').style.display="none";
   query(1,v);
  }
 </script>


控制項






<input autocomplete="off" type="text" name="cust_code" id="cust_code" size="20" onkeyup="if (frmQuery.chkAutoComplete.checked) autoCompleteField(this.value);" onkeydown="return ChangeSelect();" /> <input type="checkbox" value="1" name="chkAutoComplete" checked="checked" />啟用自動完成


 


在控制項的 onkeyup event 中去呼叫 autoCompleteField() 與後端 autoComplete.jsp 進行 Ajax 互動


encodeURI 是為了中文碼(雙位元碼)在 XMLHTTP 傳輸中 Unicode 轉換問題


在後端的 jsp 程式需使用






String cust_code=new String(request.getParameter("cust_code").getBytes("ISO-8859-1"),"UTF8");


來讀取 QueryString 中傳遞的參數.


主頁面編碼設為






<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />


這樣就不會有編碼的問題


後端 jsp 輸出






while (rs.next()){
  out.print("<a href='#' onclick='setValue(\""+rs.getString("occ01").trim()+"\")' style='cursor:default'>"+rs.getString("occ01").trim()+" ("+rs.getString("occ02").trim()+")</a><br />");
}


透過呼叫主頁面 setValue() 將選取的值回存至控制項中, 即大功告成. :)


可以將 script 的部份 define 成 .js 並修改參數的傳遞 及可當作 library 來使用囉.

我的第一個 Ajax 系統

終於練功練成

 

我的第一支以 Ajax 方式寫的程式可以上線了!!

 

透過 XMLHttpRequest 這個好幫手 (Firefox 內建物件, IE 則要透過 ActiveXObject("Microsoft.XMLHTTP"))

就可以以非同步的方式去將後端查到的資料 Show 在使用者查詢的文件上

並且透過 Cache 的機制, 可以快速的與後端進行溝通

 

Ajax(Asynchronous JavaScript+CSS+DOM+XMLHttpRequest) 不是一項新的技術, 而是一種 Web 2.0 時代的新的開發方式, 由 Jesse James Garrett 於 2005 年2 月所提出的一個新的名詞.

 

如果您使用過 Google Suggests 或 Google Maps, 您一定對於網頁可以這樣使用的方式眼睛為之一亮.

 

根據 Jesse 的定義

 

Ajax isn’t a technology. It’s really several technologies, each flourishing in its own right, coming together in powerful new ways. Ajax incorporates:


·               standards-based presentation using XHTML and CSS;


·               dynamic display and interaction using the Document Object Model;


·               data interchange and manipulation using XML and XSLT;


·               asynchronous data retrieval using XMLHttpRequest;


·               and JavaScript binding everything together.


 


XHTML 是以 XML 的撰寫方式來編寫 HTML 網頁(也就是 Tag 的寫法要求比較嚴謹)

CSS 是美化網頁所不可或缺的東西

DOM(Document Object Model) 則是 Browser 解析網頁的物件模型, 也就是 DHTML(Dynamic HTML)的核心

新的東西只有 XMLHttpRequest 這一個物件, 伴隨 IE 5.x 之後所提供的一個物件呼叫模式(其實就是 Microsoft.XMLHTTP 這個 Dll), Firefox 則是內建在 Browser 物件中

所有上述的東西, 最後透過 Javascript 把它組合在一起

讓你瀏覽網頁時, 不再需要不斷的 refresh 以取得最新的資料, 而是類似 Windows Application 一般以一個畫面就可以完成所有的事.

 

Gmail 就是一個很好的例子.

 

我的第一支程式就是讓 Sales 能夠查詢客戶的信用評等資料, 以判斷是否爭取客戶的信用額度.

公司的徵信人員定期將上市/櫃公司的財報資料鍵入後端系統

業務人員在網頁上輸入客戶代號/客戶名稱, 即可取得各季的徵信資訊

 

在網頁瀏覽上, 完全沒有 refresh 的動作

只有一個簡單的輸入欄位與查詢按鈕

 

使用者按下查詢鈕後, XMLHttpRequest 去 request 相對應的 JSP 程式, 透過後端 JSP 分頁技術將資料處理好時, 會將資料以 XML Data Stream 的方式傳回前端(此動作是背景處理, 使用者感覺不出來)

 

前端透過 DOM 將資料展示在所設定的位置, 並且將資料 Cache

 

這樣使用者就可以很快速的更新資料(幾乎沒有停頓)

 

原始碼

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE"> 
  <meta http-equiv="Content-Type" content="text/html; charset=big5" />
  <meta http-equiv="Content-Language" content="zh-tw" />
  <script type="text/javascript" src="/js/util.js"></script>
  <script type="text/javascript" src="/js/AjaxService.js"></script> 

  <title>公司信用評等查詢系統</title>
 
  <script type="text/javascript">
    var ajaxobj=new AjaxService();

 function query(page,cust_code) {
  // This demo uses simple Html/JS only, so we load a static html page
        $('result').innerHTML="載入中...";
  var url = "/Authority/auth_all.jsp?page="+page;
  var url1 = "/Authority/auth_cust.jsp?cust_code="+cust_code;
  //var callback = processAjaxResponse;
     ajaxobj.executeXhr((cust_code=="") ? url : url1);   
 }
  </script>
 </head>
 <link rel="stylesheet" href="/css/styles.css" type="text/css">
 <body>
 <form name="frmQuery" method="post">
 <table width="100%" border="0" cellspacing="2" cellpadding="2" style="border: solid 1px; padding-left:10px;">
  <tr>
    <td>請輸入客戶代碼/客戶簡稱</td>
    <td><input type="text" name="cust_code" size="20" /></td>
    <td><input type="button" value="查詢" onclick="query(1,frmQuery.cust_code.value)" /></td> 
  </tr>
</table>
</form>
<p>
   <div id="result">
   </div> 
</p>  

 </body>
</html>

 

藍色字 <div id="result"></div> 即是資料顯示的地方