這兩天在忙著修飾自己部門的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。