1. 前言
本文探讨的是基于DayPilot Calendar 控件实现的复杂网页的拖拽式交互。 根据以往的经验, 对于元素很多的页面,不应该滥用UpdatePanel来进行Ajax的页面异步更新,考虑采用其他方法解决。首先考虑ASP.NET AJAX库, 在javascript 这一端的开发虽然有点不太习惯,但是尚可接受, 可就是开发服务器端Callback接口的时候只提供了两个选择,要么static Page Method, 要么实现Webservice 接口; 笔者不准备用Webservice 接口做实验, 可使用static 的页面方法的话开发实在是太受限(不知道微软怎么想的,据网上说是由于设计成普通的页面方法调的时候好像性能太差才这么做),于是只好转而使用.net自带的ICallbackEventHandler接口, 而static PageMethod的方式只用来完成其中的一点点工作。这是其一。 其二, 关键的Drag&Drop操作,本来以前使用YahooUI库实现的, 不错, 但这次由于DayPilot 4.3已经加入了从External element拖拽到Calendar中的功能,还不用自己写在Calendar Table中的鼠标定位功能,可以大大减少开发量,于是笔者就采用了它自带的这个js框架,只是在做从Calendar控件中把Event 拖出去的时候才对其js源代码做了一些修改。 最后, 那些自定义的Calendar服务器端回调事件, 都使用DayPilot框架中的Event Delegate在WebPage中实现,由DayPilot中的ICallbackEventHandler.RaiseCallbackEvent统一调度。
实验环境:VS2005, .net2.0, DayPilot 4.3, ASP.NET Ajax库 (MS Ajax Toolkit1.0)
2. DayPilot控件简介
该控件是一个专门的网页日历控件,集成了诸如Resource booking, resource overview, time scheduling, weekly/daily event view 等的功能,获得源代码之后可以对其进行修改,订制出用户想要的ResourceBooking / WebOrdering 系统。 本实验使用的是其最新的4.3版。
3. 待实现的功能
- Room resource calendar,会议室预订日历,横轴是时间, 纵轴为所有的room
- 多选/单选格子生成booking(上图中的蓝色方块), 可以resizing, 可以moving, 可以clicking, 异步刷新Calendar
- 每次进入该room resource calendar页面,装载当天的那些booking, 并且在正上方显示时间,时间两旁有两个按钮,分别进行前一天和后一天的翻页, 异步刷新Calendar
- calendar左边列出所有的公司的员工列表(可以根据输入的字符串自动搜素人名),可以拖拽人名的div块进入calendar,生成一个booking, 异步刷新员工列表,异步刷新Calendar
- 可以将在Calendar中的booking 拖如右边的红色方框的临时区(Temp Area), 并且可以把临时区中的booking 拖回来, 异步刷新临时区, 异步刷新Calendar
4. 技术实现
4.1建立测试网站
VS2005建立ASP.NET Ajax测试网站, 注意配置web.config文件, bin文件夹引入DayPilot.dll, 在
-> -> 中引入
4.2 数据提供层(弱化)
本实验使用了一个弱化的数据层,一个GenDataHelper类来生成一些测试数据:room resources, original bookings, 以及user list, 代码如下所示:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Text;
/**/ ///
/// Summary description for GenDataHelper
///
public class GenDataHelper
... {
-- The simulate data source for Page 'BookingPage' --#region -- The simulate data source for Page 'BookingPage' --
public static DataTable GetColumns()
...{
DataTable dt = new DataTable();
dt.Columns.Add("ColumnName", typeof(string));
dt.Columns.Add("ColumnID", typeof(int));
DataRow dr;
dr = dt.NewRow();
dr["ColumnName"] = "RoomA";
dr["ColumnID"] = 1;
dt.Rows.Add(dr);
dr = dt.NewRow();
dr["ColumnName"] = "RoomB";
dr["ColumnID"] = 2;
dt.Rows.Add(dr);
dr = dt.NewRow();
dr["ColumnName"] = "RoomC";
dr["ColumnID"] = 3;
dt.Rows.Add(dr);
dr = dt.NewRow();
dr["ColumnName"] = "RoomD";
dr["ColumnID"] = 4;
dt.Rows.Add(dr);
dr = dt.NewRow();
dr["ColumnName"] = "RoomE";
dr["ColumnID"] = 5;
dt.Rows.Add(dr);
dr = dt.NewRow();
dr["ColumnName"] = "RoomF";
dr["ColumnID"] = 6;
dt.Rows.Add(dr);
dr = dt.NewRow();
dr["ColumnName"] = "RoomG";
dr["ColumnID"] = 7;
dt.Rows.Add(dr);
return dt;
}
public static DataTable GetOriginalEvents()
...{
DataTable dt = new DataTable();
dt.Columns.Add("StartTime", typeof(DateTime));
dt.Columns.Add("EndTime", typeof(DateTime));
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("ID", typeof(string));
dt.Columns.Add("ResID", typeof(string)); /**//// the Column's id
dt.Columns.Add("AllDay", typeof(bool));
dt.Columns.Add("IsTemp", typeof(bool));
dt.PrimaryKey = new DataColumn[] ...{ dt.Columns["ID"] };
DataRow dr;
dr = dt.NewRow();
dr["StartTime"] = DateTime.Now.Date.AddHours(12); // Convert.ToDateTime("12:00");
dr["EndTime"] = DateTime.Now.Date.AddHours(13); // Convert.ToDateTime("13:00");
dr["Name"] = "Booking 1";
dr["ID"] = Guid.NewGuid().ToString();
dr["ResID"] = "3";
dr["IsTemp"] = false;
//dr["AllDay"] = true;
dt.Rows.Add(dr);
dr = dt.NewRow();
dr["StartTime"] = DateTime.Now.Date.AddHours(9); // Convert.ToDateTime("9:00");
dr["EndTime"] = DateTime.Now.Date.AddHours(14).AddMinutes(30); // Convert.ToDateTime("14:30");
dr["Name"] = "Booking 2";
dr["ID"] = Guid.NewGuid().ToString();
dr["ResID"] = "7";
dr["IsTemp"] = false;
//dr["AllDay"] = true;
dt.Rows.Add(dr);
return dt;
}
public static DataTable GetInitUserList()
...{
DataTable dt = new DataTable();
dt.Columns.Add("UserID", typeof(int));
dt.Columns.Add("UserName", typeof(string));
for (int i = 1; i <= 20; i++)
...{
DataRow dr = dt.NewRow();
dr["UserID"] = i;
dr["UserName"] = "UserName" + i.ToString();
dt.Rows.Add(dr);
}
return dt;
}
#endregion -- The simulate data source for Page 'BookingPage' --
4.3 界面设计
一个Master page, 左右两个asp:ContentPlaceHolder, 注意要在此处引入ScriptManager
如下:
测试页面BookingPage.aspx, 左边的asp:Content放置一个ul用来显示雇员名称列表,右边的asp:Content放置DayPilot控件以及临时区,如下所示:
<% @ Page Language = " C# " AutoEventWireup = " true " CodeFile = " BookingPage.aspx.cs " Inherits = " BookingPage " MasterPageFile = " ~/Main.master " %>
<% @ MasterType VirtualPath = " ~/Main.master " %>
< asp:Content ID = " leftContent " runat = " server " ContentPlaceHolderID = " leftContentHolder " >
< asp:Literal ID = " literalName " runat = " server " Text = " UserName " > asp:Literal >
< asp:TextBox ID = " txtSearchUserName " BorderColor = " black " BorderWidth = " 1px " runat = " server " > asp:TextBox >
< div id = " lodingIndicator " style = " display:none;color:White;background-color:Red; width:30px; " > Loding... div >
< br />
< div id = " leftList-div " >
< ul id = " leftList " > ul >
div >
asp:Content >
< asp:Content ID = " rightContent " runat = " server " ContentPlaceHolderID = " rightContentHolder " >
< div style = " float:left; " >
< div style = " float:left; width:80% " >
< table style = " width:100%; " >
< tr >
< td style = " text-align:center; " >
< div style = " text-align:center; margin-bottom:10px; margin-top:10px; " >
< a href = " javascript:AjaxCall_PrevDay(); " style = " text-decoration: none; background-color:White; border: 1px solid navy; padding:3px; " > Previous day < span style = " color:red " >& laquo; span > a >
& nbsp;
< span id = " lblCurrentDay " runat = " server " > span >
& nbsp;
< a href = " javascript:AjaxCall_NextDay(); " style = " text-decoration: none; background-color:White; border: 1px solid navy; padding:3px; " > Next day < span style = " color:red " >& raquo; span > a >
div >
td >
tr >
< tr >
< td width = " 80% " >
< dp:DayPilotCalendar ID = " mainCalendar " runat = " server " ClientObjectName = " dpc1 "
DataEndField = " EndTime "
DataStartField = " StartTime "
DataTextField = " Name "
DataValueField = " ID "
DataColumnField = " ResID "
DataAllDayField = " AllDay "
HourWidth = " 100 "
BusinessBeginsHour = " 10 "
BusinessEndsHour = " 17 "
Days = " 7 "
CellHeight = " 15 "
ViewType = " Resources "
CellsPerHour = " 4 "
TimeFormat = " Clock24Hours "
UseEventBoxes = " always "
EventClickHandling = " javaScript "
EventMoveHandling = " CallBack "
EventResizeHandling = " CallBack "
TimeRangeSelectedHandling = " CallBack "
OnBeforeEventRender = " mainCalendar_BeforeEventRender "
OnTimeRangeSelected = " mainCalendar_TimeRangeSelected "
OnRefresh = " mainCalendar_Refresh "
OnEventReload = " mainCalendar_Reload "
OnEventMove = " mainCalendar_EventMove "
OnEventResize = " mainCalendar_EventResize "
OnEventTemp = " mainClendar_EventTemp "
>
dp:DayPilotCalendar >
td >
tr >
table >
div >
div >
< div id = " tempAreaEvent " style = " font-size: 8pt; width: 50px; cursor: move; font-family: Tahoma; margin-top:200px; display:block;
height: 100px; border:2px solid red; z - index: 10000 ; float :left; vertical - align:middle " unselectable= " on "
onmouseup = " TempArea_MouseUp(); " >
div >
asp:Content >
4.4 初始化页面,以及装载RoomResources
页面代码完成之后, 由于还没有规定Calendar 的Columns是什么,因此在运行的时候不会画出上面黄色的table cells, 因此需要在后台代码调用数据层类GenDataHelper 获取Columns集合; 在此之前也要做一些必要的初始化工作,具体步骤如下:
- 页面类继承ICallbackEventHander接口, 以处理客户端的异步请求
public partial class BookingPage : System.Web.UI.Page, System.Web.UI.ICallbackEventHandler
- 页面的Page_PreRender事件中注册客户端的Callback方法
protected void Page_PreRender( object sender, EventArgs e)
... {
this.RegClientScript();
}
protected void RegClientScript()
... {
ClientScriptManager cs = Page.ClientScript;
string js = @"function OnPageCallback(argument){" + cs.GetCallbackEventReference(this, "argument", "onShowResult", null) + @";}";
cs.RegisterStartupScript(this.GetType(), "OnPageCallback", js, true);
}
此处的OnPageCallback可以在客户端的js代码中调用,发送异步请求到服务器端, 由ICallbackEventHandler.RaiseCallbackEvent接口进行处理,而onShowResult是代码写在客户端的回调结果处理方法。
- 在OnLoad事件中装载当前日期, Room Resources(columns), 以及original booking events, 雇员列表
protected override void OnLoad(EventArgs e)
... {
base.OnLoad(e);
if (!IsPostBack)
...{
this.mainCalendar.StartDate = DateTime.Now.Date;
CurrentDay = DateTime.Now.Date;
this.LoadTestColumns();
this.LoadTestEvents();
this.ReBindNonTempEvents();
this.SetCurrentDayShow();
this.LoadInitUserList();
}
}
public DateTime CurrentDay
... {
get
...{
if (Session["CurrentDay"] == null)
Session["CurrentDay"] = DateTime.Now.Date;
return (DateTime)Session["CurrentDay"];
}
set
...{
Session["CurrentDay"] = value;
}
}
private void LoadTestColumns()
... {
this.mainCalendar.Columns.Clear();
DataTable dtColumns = GenDataHelper.GetColumns();
if (dtColumns != null)
...{
foreach (DataRow dr in dtColumns.Rows) ...{
this.mainCalendar.Columns.Add(dr["ColumnName"].ToString(), dr["ColumnID"].ToString());
}
}
}
private void LoadTestEvents()
... {
if (Session["Events"] == null)
...{
Session["Events"] = GenDataHelper.GetOriginalEvents();
}
}
private void LoadInitUserList()
... {
if (Session["UserList"] == null)
...{
Session["UserList"] = GenDataHelper.GetInitUserList();
}
}
private void SetCurrentDayShow()
... {
this.lblCurrentDay.InnerText = this.CurrentDay.ToShortDateString();
}
另外, 有一个ReBindNonTempEvents方法来绑定当天的并且不在临时区中的booking events 到Calendar控件上:
private void ReBindNonTempEvents()
... {
if (Session["Events"] != null)
...{
DataTable dtEvents = (DataTable)Session["Events"];
string strWhere = "StartTime >= '" + CurrentDay.Date.ToShortDateString() + "'and StartTime < '" + CurrentDay.Date.AddDays(1).ToShortDateString() + "' and IsTemp=false";
dtEvents.DefaultView.RowFilter = strWhere;
Session["Events"] = dtEvents;
this.ReBindEvents();
}
}
private void ReBindEvents()
... {
if (Session["Events"] != null)
...{
this.mainCalendar.DataSource = Session["Events"];
this.mainCalendar.DataBind();
this.mainCalendar.Update();
}
}
4.5 Creating booking, Moving booking, Resizing booking, 和Clicking booking
在页面DayPilot的属性里面加入 TimeRangeSelectedHandling="CallBack" 和 OnTimeRangeSelected="mainCalendar_TimeRangeSelected", 在后台页面上添加:
protected void mainCalendar_TimeRangeSelected( object sender, TimeRangeSelectedEventArgs e)
... {
if (Session["Events"] == null)
...{
Session["Events"] = GenDataHelper.GetOriginalEvents();
}
DataTable dtEvents = (DataTable)Session["Events"];
DataRow dr = dtEvents.NewRow();
dr["StartTime"] = e.Start;
dr["EndTime"] = e.End;
dr["Name"] = "New Booking";
dr["ID"] = Guid.NewGuid().ToString();
dr["ResID"] = e.ColumnId;
dr["IsTemp"] = false;
dtEvents.Rows.Add(dr);
dtEvents.AcceptChanges();
Session["Events"] = dtEvents;
this.ReBindNonTempEvents();
}
由于TimeRangeSelected这个event delegate已经在DayPilot的后台代码(js和cs)中定义,因此开发者可以直接在Calendar控件的属性中直接获取到,然后在页面代码加上event的实现即可。
在页面DayPilot的属性里面加入 EventMoveHandling="CallBack" 和 OnEventMove="mainCalendar_EventMove", 在后台页面上添加:
protected void mainCalendar_EventMove( object sender, EventMoveEventArgs e)
... {
if (Session["Events"] == null)
...{
Session["Events"] = GenDataHelper.GetOriginalEvents();
}
DataTable dtEvents = (DataTable)Session["Events"];
DataRow dr = dtEvents.Rows.Find(e.Value);
if (dr != null) // Move
...{
if ((bool)dr["IsTemp"] == false)
...{
dr["StartTime"] = e.NewStart;
dr["EndTime"] = e.NewEnd;
dr["ResID"] = e.NewResource;
dtEvents.AcceptChanges();
Session["Events"] = dtEvents;
// this.ReBindEvents();
this.ReBindNonTempEvents();
}
else // Move from temp area to the calendar
...{
this.mainCalendar.RefreshTempArea = true;
this.mainCalendar.TempAreaHTML = "";
dr["IsTemp"] = false;
dr["StartTime"] = e.NewStart;
dr["EndTime"] = e.NewEnd;
dr["ResID"] = e.NewResource;
dtEvents.AcceptChanges();
Session["Events"] = dtEvents;
this.ReBindNonTempEvents();
}
}
else // Dragging from external
...{
dr = dtEvents.NewRow();
dr["StartTime"] = e.NewStart;
dr["EndTime"] = e.NewEnd;
dr["ResID"] = e.NewResource;
dr["Name"] = e.Text;
dr["ID"] = Guid.NewGuid().ToString();
dr["IsTemp"] = false;
dtEvents.Rows.Add(dr);
Session["Events"] = dtEvents;
// this.ReBindEvents();
this.ReBindNonTempEvents();
}
}
注意: 此处的代码处理分为3块, 分别是在Calendar中的Moving事件, 从雇员列表中拖拽来的Moving事件,以及从临时区来的moving 事件。
在页面DayPilot的属性里面加入 EventResizeHandling="CallBack" 和 OnEventResize="mainCalendar_EventMove", 在后台页面上添加:
protected void mainCalendar_EventResize( object sender, EventResizeEventArgs e)
... {
if (Session["Events"] == null)
...{
Session["Events"] = GenDataHelper.GetOriginalEvents();
}
DataTable dtEvents = (DataTable)Session["Events"];
DataRow dr = dtEvents.Rows.Find(e.Value);
if (dr != null)
...{
dr["StartTime"] = e.NewStart;
dr["EndTime"] = e.NewEnd;
dtEvents.AcceptChanges();
Session["Events"] = dtEvents;
// this.ReBindEvents();
this.ReBindNonTempEvents();
}
}
- Clicking booking 此处做弱化处理,仅弹出一个js对话框显示booking id
EventClickHandling="javaScript" , 其他的事情交给DayPilot框架自动处理。
4.6 日期翻页与显示
【要求】点击">>"和"<<" 往前一天、后一天翻页,异步更新当前时间; 重新绑定那一天的booking events到Calendar控件上去, 异步更新。
【实现】前者使用ASP.NET AJAX库异步通信层的Static Page Method 来实现异步更新, 后者用自定义的Calendar的异步事件实现异步更新。
function AjaxCall_PrevDay()
... {
var date = $get("ctl00_rightContentHolder_lblCurrentDay").innerText;
PageMethods.DayPaging_Click(date, -1, CallBack_DayPaging);
dpc1.reloadCallBack(-1);
}
function AjaxCall_NextDay()
... {
var date = $get("ctl00_rightContentHolder_lblCurrentDay").innerText;
PageMethods.DayPaging_Click(date, 1, CallBack_DayPaging);
dpc1.reloadCallBack(+1);
}
function CallBack_DayPaging(result)
... {
$get("ctl00_rightContentHolder_lblCurrentDay").innerText = result;
}
这里PageMethod.DayPageing_Click就是通过ASP.NET异步通信层注册的服务器端Static Page Method, 实现如下:
[WebMethod]
public static string DayPaging_Click( string strDate, int offsetDate)
... {
DateTime date = Convert.ToDateTime(strDate).AddDays(Convert.ToDouble(offsetDate));
return date.ToShortDateString();
}
而CallBack_DayPaging(result)则是客户端用来接收结果并更新显示的。
- Calendar 翻页动态绑定Booking, 自定义Calendar控件的Callback事件
- 在DayPilot项目文件中添加一个Partial DayPilotCalendar类,添加Event hander:
namespace DayPilot.Web.Ui
... {
public partial class DayPilotCalendar
...{
public event ReloadEventHandler EventReload;
}
}
-
- 添加自定义事件的delegate 和 参数处理类ReloadEventArgs:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using DayPilot.Utils;
namespace DayPilot.Web.Ui.Events
... {
public delegate void ReloadEventHandler(object sender, ReloadEventArgs e);
public class ReloadEventArgs: DayPilotEventArgs
...{
private string argDateOffset;
internal ReloadEventArgs(string[] arguments)
...{
if (arguments.Length != 1)
...{
throw new ArgumentException("Exception 1 parameters (supplied " + arguments.Length + ").");
}
this.argDateOffset = UrlEncoder.UrlDecode(arguments[0]);
}
public string ArgDateOffset
...{
get ...{ return argDateOffset; }
}
}
}
-
- 在ArgumentsParser类中添加自定义事件的缩写名 “REL:”
using System;
using DayPilot.Utils;
namespace DayPilot.Web.Ui.Events
... {
class ArgumentsParser
...{
internal static DayPilotEventArgs ExtractArguments(string ea, string[] fields)
...{
string type = ea.Substring(0, 4);
string[] arguments = ea.Substring(4).Split('&');
switch (type)
...{
case "CLK:":
return new EventClickEventArgs(arguments, fields);
case "RCK:":
return new EventRightClickEventArgs(arguments, fields);
case "DEL:":
return new EventDeleteEventArgs(arguments, fields);
case "HEA:":
return new HeaderClickEventArgs(arguments);
case "FRE:":
return new TimeRangeSelectedEventArgs(arguments);
case "MNU:":
return new EventMenuClickEventArgs(arguments, fields);
case "MOV:":
return new EventMoveEventArgs(arguments, fields);
case "RES:":
return new EventResizeEventArgs(arguments, fields);
case "EDT:":
return new EventEditEventArgs(arguments, fields);
case "SEL:":
return new EventSelectEventArgs();
case "REF:":
return new RefreshEventArgs(arguments);
case "TRM:":
return new TimeRangeMenuClickEventArgs(arguments);
// Added on 2007-12-20, reloading
case "REL:":
return new ReloadEventArgs(arguments);
// End Adding
// Added on 2008-01-04, dragging from calendar to temparea
case "TMP:":
return new TempEventArgs(arguments);
// End Adding
default:
throw new ArgumentException("Unknown PostBack/CallBack parameter type : '" + type + "'");
}
}
}
}
-
- 在DayPilotCalendar类的Callback接口调用的事件执行方法中添加对自定义事件的处理:
void executeEvent( string ea, EventSource src)
... {
DayPilotEventArgs e = ArgumentsParser.ExtractArguments(ea, TagFields);
e.source = src;
if (e is EventResizeEventArgs)
...{
if (EventResize != null)
EventResize(this, (EventResizeEventArgs)e);
return;
}
if (e is EventMoveEventArgs)
...{
if (EventMove != null)
EventMove(this, (EventMoveEventArgs)e);
return;
}
if (e is EventMenuClickEventArgs)
...{
if (EventMenuClick != null)
EventMenuClick(this, (EventMenuClickEventArgs)e);
return;
}
if (e is EventClickEventArgs)
...{
if (EventClick != null)
EventClick(this, (EventClickEventArgs)e);
return;
}
if (e is EventRightClickEventArgs)
...{
if (EventRightClick != null)
EventRightClick(this, (EventRightClickEventArgs)e);
return;
}
if (e is EventDeleteEventArgs)
...{
if (EventDelete != null)
EventDelete(this, (EventDeleteEventArgs)e);
return;
}
if (e is HeaderClickEventArgs)
...{
if (HeaderClick != null)
HeaderClick(this, (HeaderClickEventArgs)e);
return;
}
if (e is TimeRangeSelectedEventArgs)
...{
if (TimeRangeSelected != null)
TimeRangeSelected(this, (TimeRangeSelectedEventArgs)e);
return;
}
if (e is EventEditEventArgs)
...{
if (EventEdit != null)
EventEdit(this, (EventEditEventArgs)e);
return;
}
if (e is EventSelectEventArgs)
...{
if (EventSelect != null)
EventSelect(this, (EventSelectEventArgs)e);
//updateRequested = true;
return;
}
if (e is RefreshEventArgs)
...{
if (Refresh != null)
...{
Refresh(this, (RefreshEventArgs)e);
}
CallBackAction = CallBackAction.Refresh;
return;
}
if (e is TimeRangeMenuClickEventArgs)
...{
if (TimeRangeMenuClick != null)
TimeRangeMenuClick(this, (TimeRangeMenuClickEventArgs)e);
return;
}
// Added by Gu Tian'en, for relaoding ajax callback
if ((null != EventReload) && (e is ReloadEventArgs))
...{
EventReload(this, (ReloadEventArgs)e);
return;
}
// Added by Gu Tian'en, for dragging from calendar to temparea
if((null != EventTemp) && (e is TempEventArgs))
...{
EventTemp(this, (TempEventArgs)e);
return;
}
throw new ArgumentException("Unrecognized PostBack/CallBack command.");
}
在这里EventReload这个事件委托就直接调用了页面后台的事件处理方法"mainCalendar_Reload":
protected void mainCalendar_Reload( object sender, ReloadEventArgs e)
... {
this.CurrentDay = this.CurrentDay.AddDays(Convert.ToDouble(e.ArgDateOffset));
mainCalendar.StartDate = CurrentDay;
this.ReBindNonTempEvents();
}
至此服务器端的处理完成。
-
- 在客户端的调用: DayPilot框架的js代码部分有一个方法'callBack'封装了WebForm_DoCallback这个.net中的异步调用方法, 于是在Calendar.js文件中添加一个自定义的reloadCallback方法供页面上的控件调用,
/**/ /* Added by Gu Tian'en*/
this .reloadCallBack = function (offsetDate)
... {
var offset = parseInt(offsetDate);
if( !isNaN(offset) )
...{
var newDate = new Date();
newDate.setTime(this.startDate.getTime() + offset*24*3600*1000);
// Before call 'reloadCallBack' ajax event, setting the new Date for the 'dpColumn' of the calendar control
var table = DayPilot.$(calendar.id + '_main');
var columnCells = table.rows[0].cells;
for(var j=0; j<columnCells.length; j++)
...{
columnCells[j].dpColumnDate = newDate;
/// alert(columnCells[j].getAttribute("dpColumnDate"));
}
this.startDate = newDate;
// Then call back
this.callBack('REL:', offset);
}
}
在客户端只需要传入参数,调用这个方法,即可实现重新绑定Calendar上数据并异步更新的功能。至此客户端的代码处理完成。
4.7 对DayPilot异步回调机制的总结
通过对DayPilot框架的研究,笔者发现其回调机制也是通过使用户控件从ICallbackEventHander接口继承来实现的,只不过这个框架较大,处理的回调事件比较多,而每个不同的回调事件其传入的参数也不一样,所以这才在4.6节出现了一些看起来比较繁琐的自定义回调事件的步骤(比如那几个处理参数的类),其实在笔者看来,不外乎下面三个步骤:
- 客户端异步调用: 使用.net框架封装的WebForm_DoCallback方法,规定回传的参数以及客户端结果处理方法,其他顶多对这个方法再封装一下;
- 服务器端异步响应: ICallbackEventHandler.RaiseCallbackEvent接口方法来处理客户端的回调,分析回传参数,使用event delegate 处理数据;
- 服务器端回传结果: ICallbackEventHandler.GetCallbackResult方法回传RaiseCallbackEvent处理过的要返回的结果,字符串形式;
- 客户端接收回传结果: 即在第一步中规定的客户端结果处理方法,得到回传结果,异步更新页面。
4.8 根据输入字符串来查询并装载雇员列表
效果如下:
在textbox 中输入字符串之后异步回调,更新下面的雇员列表。采用ICallbackEventHandler接口。
4.9 拖拽雇员creating booking
在DayPilot Calendar 框架已经实现了从外部拖动元素到Calendar中并创建booking 的功能,客户端只需要在要拖动的元素上定义onmousedown事件,写上“ return DayPilotCalendar.dragStart (this, 60*60, "New Event by Dragging from external", this.innerHTML); ” 即可, 这里的this是指要拖动元素本身的obj, 60*60是指在Calendar上阴影显示的形状(此处要求显示一个小时的长度), 第三个第四个都是回传用于显示的参数。 在服务器端的处理由框架自动定位到mainCalendar_EventMove中进行处理。
最终效果如下:
4.10 拖拽booking 入临时区、从临时区拖拽booking回Calendar
- 从Calendar 拖booking 到临时区,需要自定义"TMP:"事件,方式同上面"REL:"事件一样,不再赘述。
在客户端的响应当然是临时区的 onmouseup 事件,
// Mouseup from the temp area
function TempArea_MouseUp()
... {
try
...{
DayPilotCalendar.tempArea = true;
var dpEvent;
if (DayPilotCalendar.moving.event != null)
...{
dpEvent = DayPilotCalendar.moving.event;
dpc1.tempCallBack(dpEvent.value(), dpEvent.start(), dpEvent.end());
}
}
catch(ex)
...{
DayPilotCalendar.tempArea = false;
}
}
dpc1是Calendar控件的ClientID
在Calendar中定义tempCallBack客户端回调:
// Added by Gu Tian'en, 2008-01-04, for temp event
this .tempCallBack = function (BID, startDate, endDate)
... {
if(BID != "" && startDate != "" && endDate != "")
...{
this.callBack('TMP:', BID, startDate, endDate);
}
// DayPilotCalendar.tempArea = false;
}
由于此次是要在Temp area 区更新,现在使用了Calendar控件的更新,所以需要在updateView这个Calendar的客户端回调结果处理方法中加入重新画temp area区的代码:
DayPilotCalendar.updateView = function(result, context) ... {
var result = eval("(" + result + ")");
var calendar = eval(context);
if (result.Action == "None") ...{
return;
}
// config
var vsph = document.createElement("input");
vsph.type = 'hidden';
vsph.name = calendar.id + "_vsupdate";
vsph.id = vsph.name;
vsph.value = result.VsUpdate;
calendar.$("vsph").innerHTML = '';
calendar.$("vsph").appendChild(vsph);
calendar.minEnd = result.MinEnd;
calendar.maxStart = result.MaxStart;
calendar.allDayHeaderHeight = result.AllDayHeaderHeight;
calendar.totalHeader = result.TotalHeader;
calendar.scrollUpTop = result.ScrollUpTop;
calendar.scrollDownTop = result.ScrollDownTop;
if (result.Action == "Refresh") ...{
calendar.startDate = new Date(result.StartDate);
calendar.days = result.Days;
//calendar.cellsPerHour = result.CellsPerHour;
calendar.colors = result.Colors;
calendar.columns = result.Columns;
calendar.drawHeader();
calendar.drawTable();
}
calendar.events = result.Events;
calendar.eventsAllDay = result.EventsAllDay;
calendar.drawEvents();
calendar.drawEventsAllDay();
if (calendar.timeRangeSelectedHandling != "HoldForever") ...{
calendar.cleanSelection();
}
calendar.updateScrollIndicators();
if (calendar.todo) ...{
if (calendar.todo.del) ...{
var del = calendar.todo.del;
del.parentNode.removeChild(del);
calendar.todo.del = null;
}
}
calendar.afterRender(result.CallBackData);
// Added by Gu Tian'en, 2008-01-07, for refreshing temp area html code -- start --
if( ($get('tempAreaEvent') != null) && (result.TempAreaHTML != "") )
...{
$get('tempAreaEvent').innerHTML = result.TempAreaHTML;
}
// Added by Gu Tian'en, 2008-01-07, for refreshing temp area html code -- end --
} ;
同时在服务器端DayPilotCalendar.cs添加:
string ICallbackEventHandler.GetCallbackResult()
... {
if (CallbackException != null)
...{
if (HttpContext.Current.IsDebuggingEnabled)
...{
return "$$$" + CallbackException;
}
else
...{
return "$$$" + CallbackException.Message;
}
}
try
...{
loadEventsToDays();
// CalendarCallBackResult result = new CalendarCallBackResult();
Hashtable result = new Hashtable();
result["Days"] = Days;
result["MaxStart"] = maxStart;
result["MinEnd"] = minEnd;
result["StartDate"] = JsDate.FormatDateTime(StartDate);
result["Action"] = CallBackAction;
result["Events"] = getEvents();
result["CallBackData"] = callBackData;
if (CallBackAction == CallBackAction.Refresh)
...{
result["Columns"] = getColumns();
result["Colors"] = getColors();
}
result["AllDayHeaderHeight"] = allDayHeaderHeight;
result["TotalHeader"] = totalHeader;
result["ScrollUpTop"] = scrollUpTop;
result["ScrollDownTop"] = scrollDownTop;
List<Hashtable> alldayEvents = getEventsAllDay();
result["EventsAllDay"] = alldayEvents;
using (StringWriter vs = new StringWriter())
...{
LosFormatter f = new LosFormatter();
f.Serialize(vs, ViewStateHelper.ToHashtable(ViewState));
result["VsUpdate"] = vs.ToString();
}
// Added by Gu Tian'en, 2008-01-08, for refreshing the temp area, -- start --
if (this.RefreshTempArea)
result["TempAreaHTML"] = this.TempAreaHTML;
// Added by Gu Tian'en, 2008-01-08, for refreshing the temp area, -- end --
return SimpleJsonSerializer.Serialize(result);
}
catch (Exception e)
...{
if (HttpContext.Current.IsDebuggingEnabled)
...{
return "$$$" + e;
}
else
...{
return "$$$" + e.Message;
}
}
}
event delegate 实现如下:
protected void mainClendar_EventTemp( object sender, TempEventArgs e)
... {
if (Session["Events"] != null)
...{
DataTable dtEvents = (DataTable)Session["Events"];
DataRow dr = dtEvents.Rows.Find(e.ArgBookingID);
if (dr != null)
...{
dr["IsTemp"] = true;
dtEvents.AcceptChanges();
Session["Events"] = dtEvents;
this.ReBindNonTempEvents();
// Add temp area html
this.mainCalendar.TempAreaHTML = this.DoTempEvent(e.ArgBookingID);
}
}
}
最终效果如下:
至此, 我们就全部实现了在基于DayPilotCalendar控件的网页上的所有Drag&Drop ajax操作, 并且没有使用任何UpdatePanel控件,代码量也并未增加多少, 却能更加精确地控制自己想要局部更新的那一部分数据, 何乐而不为呢?
Oliver Gu
2008-01-08, Haidian District
TestProject=[A1.ASP-NET.001]OKTW_Test