今天有美女問我:可不可以在 SharePoint 的 Calendar 中給不同的日歷項以不同的顏色?我說用 Overlay,結果馬上就被否定了,她不想搞出來很多個日歷。好吧,那就只有在放置日歷的頁面上,先通過腳本找到日歷項,然后分析其內容,最后按照預先訂好的規則給這些日歷項着色了。這應該又是一個 JS(jQuery)+ CSS 的應用。
其實,我本來計划今天寫寫 Adventure 系列第 4 篇的的,不過這個應用需求也挺有趣,所以,我打算先處理這件事。
jonyzhu 的原創
最常見的面試題
“如果遇到你不會的事情,你怎么處理?”“我會先去網上或者書上找,如果沒有,就去問周圍的人,如果還是沒有,就只好自己研究了。” 多么經典的面試問答啊 :)
我就是這么實踐的。先 Google 了一下,搜索第一屏里面只有一個中文鏈接,Erucy 的 按條件決定SharePoint 2010日歷顏色。看了看,應用場景和這次的要求很匹配,基本思路就是通過 jQuery 找到 SharePoint 日歷項所特有的樣式標識(div.ms-acal-item),然后讀取放在 div 的 title 屬性中的用半角括號所包含的日歷項地點(Location)信息,再到一個預先定義的“地點:顏色”對象中取出顏色作為 div 的背景色。
移植
不過,在 copy & paste 前,我需要自己檢查一下,這是習慣。搞電子電路也是,上電前都需要檢查的么(我現在連焊接前都要檢查)。於是,我建了幾個測試用的日歷項,隨機選了幾天,其中有一天加了2個日歷項。通過分析HTML的內容,發現了有趣的事情。
如果一天只有一個日歷項,那么,div.ms-acal-item 里面會包含 div.ms-acal-sdiv,接着下面是 div.ms-acal-time 和 div.ms-acal-title。div.ms-acal-title 里面是一個鏈接,鏈接的里面是現實日歷項的標題。
但是,如果一天有多個日歷項,那么 SharePoint 組織當天的日歷項的時候,結構會有不同。最外面還是 div.ms-acal-item,里面會包含 div.ms-acal-sdiv,不過,接下去,就沒有 div.ms-acal-time 和 div.ms-acal-title 了,而是直接以文本顯示日歷項的時間,以鏈接顯示日歷項的標題。
當然,無論哪種情況,Erucy 的代碼都可以運行的,因為其查找的是 div.ms-acal-item。
不過,如果直接給 div.ms-acal-item 設置背景顏色,會有一個問題。事情是這樣的,SharePoint 的日歷項,當你把鼠標移到上面的時候,有一個高亮效果,如果覆蓋了div.ms-acal-item 的背景顏色,那么,這個高亮效果就看不到了。可是,如果只是給 div.ms-acal-item 下面的 div.ms-acal-sdiv 設置背景顏色,那么,上層的 div.ms-acal-item 會露出一點兒邊,從而使得高亮效果仍然可以保持,就像下面這樣(后面一張是鼠標移到上面的效果):
(Jonyzhu 的 博客鏈接)
好了,略微整理以后,JS 代碼如下:
1: <script type="text/javascript">
2: var _color_calendar_mapping = {
3: "Office":"#00ff00",
4: "Home":"#6CA6CD",
5: "Gim":"#FFB6C1"
6: };
7: function _color_calendar_process(){
8: var reg = new RegExp("\\(([^()]+)\\)$","gi");
9: $('div.ms-acal-item').each(function(){
10: var title = $(this).attr("title");
11: if(typeof(title) != 'undefined' && title != null){
12: var calendar_location;
13: var res = reg.exec(title); if(res==null){return;}else{ calendar_location = res[1]; }
14: $(this).children("div.ms-acal-sdiv").css('background-color', _color_calendar_mapping[calendar_location]);
15: }
16: });
17: setTimeout(_color_calendar_process,500);
18: }
19: ExecuteOrDelayUntilScriptLoaded(_color_calendar_process, "SP.UI.ApplicationPages.Calendar.js");
20: </script>
上面的代碼先找出日歷項的地點信息(在日歷項的“地點”屬性里面寫好),然后和預定義的顏色對象組(_color_calendar_mapping)進行匹配。修改 _color_calendar_mapping 以適應你自己的定義的地點和顏色的匹配。關於網頁配色,可以去 Google 一下,找一些自己覺得比較舒服的顏色。
找個 Content Editor Web Part 把上面代碼放進去看看效果,如下所示:
還行 :)
增強
下面這些要求不是美女提的,是我自己想的。做到上面那樣,美女已經可以接受了 :)
這個增強的要求主要應該怪微軟。SharePoint 2010 Calendar 日歷里面,除了地點以外,還有一個分類“Category”,里面可以選會議、休假、生日什么的,偏偏微軟自家的 Outlook 里面,日歷項是可以按照這個類型來用不同顏色呈現的。所以問題在於:為什么不是按照日歷項類型來顯示不同的顏色,而是地點啊!而且,地點信息在 SharePoint 2010 的日歷項里面是手動輸入的,這個很容易出錯以及產生在顏色映射組之外的地方的。
於是,SharePoint 2010 的客戶端對象模型就用上了。通過日歷項的ID,去日歷列表里面,把對應的類型“Category”字段值查出來,然后,就可以按照這個類型來設置日歷項的顏色了。
代碼如下:
1: <script type="text/javascript">
2: /* Script for the Color Calendar
3: I don't think you'll need any comment for the code :)
4: Created by Jony Zhu. v1.3. 4/27/2013. http://www.cnblogs.com/jonyzhu */
5:
6: /* Begin
7: * You can change settings in this section */
8: var _color_calendar_mapping = {
9: "Holiday":"#00ff00",
10: "Business":"#6CA6CD",
11: "Meeting":"#FFB6C1"
12: };
13: var _color_calendar_timeout = 2000;
14: /* End */
15:
16: /* Do no change anything in this section, unless you know what you are doing. */
17: var _color_calendar_initialized = 0;
18: var _color_calendar_context;
19: var _color_calendar_list_item;
20: function _color_calendar_process(){
21: $('div.ms-acal-sdiv a').each(function(){
22: var href = $(this).attr('href');
23:
24: var list_title,item_id;
25: var reg = new RegExp("/Lists/(.*)/DispForm\.aspx","gi");
26: var res = reg.exec(href); if(res==null){return;}else{ list_title = res[1]; }
27: reg = new RegExp("ID\=(\\d+)","gi");
28: res = reg.exec(href); if(res==null){return;}else{ item_id = res[1]; }
29:
30: _color_calendar_context = new SP.ClientContext.get_current();
31: var web = _color_calendar_context.get_web();
32: var list = web.get_lists().getByTitle(list_title);
33: this._color_calendar_list_item = list.getItemById(item_id);
34: _color_calendar_context.load(this._color_calendar_list_item,'ID','Category');
35: _color_calendar_context.executeQueryAsync(Function.createDelegate(this, _color_calendar_succeeded), Function.createDelegate(this, _color_calendar_failed));
36: });
37: if(_color_calendar_initialized==0){
38: setTimeout(_color_calendar_process,_color_calendar_timeout);
39: }
40: }
41: function _color_calendar_succeeded(sender, args){
42: var id = this._color_calendar_list_item.get_item('ID');
43: if(id!=null){
44: var category = this._color_calendar_list_item.get_item('Category');
45: $("div.ms-acal-sdiv a[href$='ID="+id+"']").each(function(){
46: $(this).parent().parent().css('background-color', _color_calendar_mapping[category]);
47: });
48: _color_calendar_initialized = 1;
49: }
50: }
51: function _color_calendar_failed(sender, args){
52: // TODO: I don't know what to do here, just do it yourself.
53: // alert(args);
54: }
55: ExecuteOrDelayUntilScriptLoaded(_color_calendar_process, "SP.UI.ApplicationPages.Calendar.js");
56: </script>
效果如下(其實沒有變化啦,但是,此時已經是根據日歷項的類型來改變現實顏色了)。修改上面代碼的 _color_calendar_mapping 數組,就可以改變日歷項類型對應的背景顏色。
嗯,好多了,至少自己心里這么覺得 :) IE、火狐都測了一遍以后,放心了。
這里還有一個小問題要說一下。Erucy 的方案里面用了 setTimeout 以便定期檢查日歷項的加載情況,在純 jQuery + CSS 的時候,這個沒有什么性能問題的。但是,如果像后面那樣用了 SCOM 去查詢日歷項的類型字段,那么,就有問題了。所以,我增加了一個 _color_calendar_initialized 變量來記錄是否已經完成了一次色彩設置了(等於1),如果完成,就不要再去讀列表了。
下面是 _color_calendar_initialized 添加前后的CPU占用記錄。前面不停振動的就是沒有加 _color_calendar_initialized 前的,最后一段很低的 CPU 占用的,就是加了 _color_calendar_initialized 以后的,差距明顯啊!(CPU 占用降低前有一個高峰,那是我重刷整個頁面引起的)
還有一個可以改進的地方。
上面的方案都是直接給 div.ms-acal-sdiv 設置 background-color 屬性,效果比較單調,如果改成 addClass 來給個樣式類的話,就能實現更豐富的效果了。這個大家結合自己的工作需要去調整把。
最后看個好玩的:FireFox 的 3D 頁面顯示。
調休的這一天也就過去了,Adventure 系列只能后面再找時間寫了。
■