用JavaScript实现一个Timeline
这两天在忙着修饰自己部门的Team Site,老板提出了一个期望,想在Team Site首页上放一个Timeline,这样部门有什么新的事件、日程,都能在Timeline上展现出来。这件事本身并非特别麻烦,但是我们的Team Site是放在公司Hosting的SharePoint系统之中(公司提供SharePoint Hosting服务,每个人/部门可以根据自由要求,以自助的方式申请Site来使用),而公司Hosting的系统,是不允许各个网站的所有者使用任何Server Code(服务器端代码)的。从IT管理的角度来说,这也是非常合理的,但这的确大大限制了各个网站的使用者对各自的网站进行定制的能力。换句话说,我能使用的工具,只有SharePoint内置的各个Web Part,以及SharePoint Designer。(在SharePoint 2010中,提供了一个新的特性:Sandboxed Solution,来解决这个问题,各个网站集的管理员可以通过upload的方式,部署功能与权限受限的Solution Package到网站集之中,但又不会影响其他网站集和整个服务器场的安全与稳定。)
在经过一番考虑之后,我确认在不允许服务器端代码的前提下,是可以通过JavaScript和HTML的能力,实现一个Timeline的。下面这张图是最终实现后的效果:
这个Timeline分成了3栏,从上至下分别是日、周、月视图,用户可以使用鼠标对每个栏进行滑动操作,以查看之前和之后日期的各个事件。Timeline中的事件,来自于网站中一个日历类别列表中的数据,这样网站使用者只用在列表中添加新的事件,Timeline中就会自动显示出来:
实现的关键是两点:
1、使用JS从网站列表中,获取到所需的列表项数据;
2、使用JS和HTML,在SharePoint页面上渲染出Timeline。
首先,我在网站中使用“日历”列表模板,创建了一个新列表。由于在Timeline控件上,我只希望能够显示当天前后30天之内发生的事件,为了更容易的取到当天前后30天的事件列表项,我在列表中创建了一个新的视图,在这个视图中只显示事件开始时间是位于当天前后30天的事件。为什么创建这样的一个视图就能方便我们在页面上用JS获取想要的数据,看到后面大家就明白了。
为了让这个新视图能进行查询过滤,我为列表创建了两个新的计算类型字段,"30DaysBeforeStartTime"和"30DaysAfterStartTime",下图显示了"30DaysBeforeStartTime"的定义方法:
"30DaysAfterStartTime"的定义方法也类似,只是公式变成了"=[Start Time]+30"。
很多人都不知道在计算字段中,应该如何使用公式。在这个页面上,有能够使用的所有公式和函数的说明,这里还有一些最常用的公式的示范。
有了这两个字段,我们就可以为新的视图来设置过滤条件了,通过下图中的条件,就能过滤出当天的前后30天之类的事件:
有了这个新的视图之后,我们就能保证,我们需要在Timeline中显示的数据,肯定都会被这个视图所包含。接下来我们进入到JS阶段...
如果要用JS获取SharePoint网站中的数据,比较靠谱的方法是用JS调用SharePoint的Web Services接口。SharePoint提供了不少Web Services接口,让我们可以在各种平台和语言中调用,其中就包括运行在页面上的JS。我们需要的是能够从列表中获取数据的Web Services接口,这个接口位于Lists Web Service里面,它提供了一个GetListItems()方法,让我们拿到列表项数据。其中,GetListItems()方法的第二个参数:"viewName",就可以让我们指定列表的一个视图,作为取数据的筛选条件。当然,我们也可以使用GetListItems()方法后面的参数来重新指定筛选条件,但是通过列表视图来制定筛选条件,要简单很多,而且修改起来也容易得多。
如何在JS中调用Web Services的方法在网络上能找到很多很多的文章,我就不再重复了。但我要推荐一个不错的JS库,使用这个JS库,可以免去手工构建SOAP包的麻烦,而且使用也相当的简捷。它包含许多的.js文件,将这些.js文件上载到网站的某个文档库中即可(实际上,并不一定需要复制所有的.js文件,比如,对于我的要求,我只用复制"SPAPI_Core.js"、"SPAPI_Types.js"和"SPAPI_Lists.js"即可)。我将所有这些乱七八糟的文件都放在一个名叫"SupportingFiles"的文档库中:
然后,用SharePoint Designer打开网站的母版页文件(默认是"default.master"),添加上对这几个.js文件的引用(图片上显示出还添加了对"http://simile.mit.edu/timeline/api/timeline-api.js"的引用,这个东东下面会讲到):
使用上面所介绍的那个JS库,下面所示的代码就可以让我从一个列表中,将列表项取出来:
function getCalendarListItems()
{
var lists = new SPAPI_Lists("网站URL");
var items = lists.getListItems(
"Timeline", // 要获取数据的列表的显示名称
"{14CB7B04-46AA-421C-B6B2-C5FBEEBA9F5B}", // 视图的GUID,注意两边要加上大括号
"", // 查询条件
"", // 要返回的字段
100, // 要返回的数据的最大行数
"", // 查询选项
null // 网站的GUID,null表示使用上面的SPAPI_Lists构造函数里面的网站URL所对应的网站
);
if (items.status == 200)
{
var rows = items.responseXML.getElementsByTagName("z:row");
return rows; // 如果获取数据成功,将所有数据放在一个数组中,然后返回
}
else
{
return null;
}
}
通过JS拿到所需的日历事件数据之后,接下来,就是如何在页面上用HTML+JS渲染出一个Timeline。作为一个典型的ELC(Exist Library Caller),我首先想到的是到网上找找是否已经有人做过类似的东东,果然,在Google Code上就被我找到了一个,嘿嘿嘿...
在这个名为SIMILE Widgets的工具集中,包含了一个用JS实现的Timeline。经过在文档中一阵乱翻,下面的JS代码就能够帮我实现想要的效果(第一行不是JS代码,而是JS代码里面会使用的一个div元素,Timeline就是通过它显示出来):
<div id="my-timeline" style="height: 120px; border: 1px solid #aaa; font-size: 9pt"></div>
var resizeTimerID = null;
function onResize() {
if (resizeTimerID == null) {
resizeTimerID = window.setTimeout(function() {
resizeTimerID = null;
tl.layout();
}, 500);
}
}
window.onresize = onResize;
function formatDateString(originDateStr)
{
var yearStr = originDateStr.substr(0, 4);
var monthStr = originDateStr.substr(5, 2);
var dayStr = originDateStr.substr(8, 2);
return monthStr + "/" + dayStr + "/" + yearStr + " " + originDateStr.substr(11);
}
function createTimeLineAndEvents()
{
var items = getCalendarListItems();
if (items == null)
{
alert("cannot got items from list.");
return;
}
var eventSource = new Timeline.DefaultEventSource();
for (var i = 0; i < items.length; ++i)
{
var ows_EventDate = formatDateString(items[i].getAttribute("ows_EventDate"));
var ows_EndDate = formatDateString(items[i].getAttribute("ows_EndDate"));
var ows_Title = items[i].getAttribute("ows_Title");
var ows_Location = items[i].getAttribute("ows_Location");
var eventDate = new Date(ows_EventDate);
var endDate = new Date(ows_EndDate);
var event = new Timeline.DefaultEventSource.Event(
eventDate, //start
endDate , //end
eventDate, //latestStart
endDate , //earliestEnd
false, //instant
ows_Title, //text
ows_Location //description
);
eventSource.add(event);
}
var bandInfos = [
Timeline.createBandInfo({
trackGap: 0.2,
width: "60%",
intervalUnit: Timeline.DateTime.DAY,
intervalPixels: 100,
timeZone : 8,
eventSource: eventSource
}),
Timeline.createBandInfo({
showEventText: false,
trackHeight: 0.5,
trackGap: 0.2,
width: "25%",
intervalUnit: Timeline.DateTime.WEEK,
intervalPixels: 150,
timeZone : 8,
eventSource: eventSource
}),
Timeline.createBandInfo({
showEventText: false,
trackHeight: 0.5,
trackGap: 0.2,
width: "15%",
intervalUnit: Timeline.DateTime.MONTH,
intervalPixels: 400,
timeZone : 8,
eventSource: eventSource
})
];
bandInfos[1].syncWith = 0;
bandInfos[2].highlight = true;
bandInfos[2].syncWith = 1;
var timeLine = Timeline.create(document.getElementById("my-timeline"), bandInfos);
}
_spBodyOnLoadFunctionNames.push("createTimeLineAndEvents");
唉,实在是有点长,本来不想全部贴出来,可想到也许有人要用的话,所以就...另外,别忘了在母版页里面,添加对"http://simile.mit.edu/timeline/api/timeline-api.js"的引用(如上面的截图所示)。
把上面的这些JS代码(以及一个"div"标签)都放到一个单独的.htm文件中,然后在想要显示Timeline的页面上放一个内容编辑Web部件。通过设置内容编辑Web部件的属性,告诉Web部件从那个.htm文件中去拿要显示出来的HTML源码(这种方式能让我们直接使用SharePoint Designer编辑那个.htm文件中的HTML和JS源码,而不必使用内容编辑Web部件内置的那个笨拙编辑器):
OK,完成。另外提一下,在SharePoint 2010中提供了专门的Client OM,它直接支持使用ECMAScript(标准名词解释:ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会)通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,但实际上后两者是ECMA-262标准的实现和扩展。)来访问SharePoint。