前言:最近想練習一下JS的API,通過再三思考,自認爲用原生JS寫UI組件是一個好方法,理由有:javascript
a. 熟悉大量原生API,像什麼字符串,數組,DOM操做是確定跑不了的html
b. 能夠鍛鍊邏輯思惟能力。插件的須要基本同樣,要實現什麼功能不用本身太多考慮能夠把主要精力放在功能實現上面,即代碼層面的分析和解決問題能力前端
c. 能夠增強本身的信心。寫組件就是對本身前端能力的綜合鍛鍊,涉及的知識面會很廣。java
昨天和今天花了點時間寫了個日曆組件,下面就對日曆組件作一個小結吧。chrome
一: 日曆組件需求(要實現的功能)windows
a. 界面仿window任務欄日曆(頁面構建相關的東西)數組
b. 某年某月的日期能正常顯示(某月有多少天,某一天是星期幾)瀏覽器
c. 上一月,下一月切換功能工具
d. 假日提示功能,能自定義節假日優化
最近效果以下圖:
二:問題拆分及解決過程
問題a: 界面構建問題
解決辦法:經過分析windows任務欄日曆,發現其界面就是一個 7*6 的固定的格子塊。像這樣較多數據展現固然用table了。而後thead裏面的th來展現星期,caption來展現當前年月。考慮到天天有選中,hover等效果因此在td裏面多使用了a標籤(根據經驗,是不推薦直接使用td裸標籤,由於td的display屬性是table-cell,其樣式控制不夠好)
最終的html結構(由於考慮到文章篇幅,html結構使用了Emmet[前身是zenCoding]僞代碼)
<table> <caption>2014年1月6日</caption> <thead> tr>th*7 </thead> <tbody> tr*6>td*7>a[href=javascript] </tbody> </table>
問題b: 參數設計
解決辦法:由於之前寫過一些基於jQuery的插件,因此參數上也使用了jQuery插件經常使用的默認加自定義的方式.由於沒有使用JS庫,因此在Canlendar上面掛了一些工具方法.
function Canlendar(opts) { var $ = Canlendar; var defaults = { dateInput: $.query('#j-canlendar-date-input'),// 單擊顯示日曆的trigger container:$.query('#j-canlendar-container'), //日曆的container btnPrevMonth:$.query('#j-canlendar-prev-month'), //上一月按鈕 btnNextMonth:$.query('#j-canlendar-next-month'), //下一月按鈕 dateFormatStr:'{year}年{month}月{date}日',//格式化輸出的日期 customFestival: [{ date:'3,04',//那一天,格式嚴格按此 name:'自定義假日名稱' //節日名稱 }] //自定義節假日,如某人生日等,對象數組 }; this.opts = $.extend(defaults, opts, true); //默認參數會被自定義參數覆蓋,這種設計基本在每一個jQuery插件中均可以看到 this.init(); } Canlendar.query = function(selector) { return /^\#/.test(selector) ? document.getElementById(selector.substr(1)) : document.querySelectorAll(selector); }; Canlendar.extend = function(target, source, dep) { var key; if(dep) { for(key in source) { if(source.hasOwnProperty(key)) { target[key] = source[key]; } } } else { for(key in source) { if(source.hasOwnProperty(key) && target[key] != null) { target[key] = source[key]; } } } return target; }; Canlendar.removeClass = function(element, className) { if(element.className.indexOf(className) < 0) return; element.className = element.className.replace(/(\w+)/gm, function(match) { return match === className ? '' : match; }); }; Canlendar.addClass = function(element, className) { if(element.className.indexOf(className) > 0) return; element.className = element.className === '' ? className : element.className + ' ' + className; };
問題c:若是正確顯示每個月的天數,即某月有多少天,某一天是星期幾.
解決辦法:經過分析發現:
1. 1個月最多有31天,而咱們有7*6=42個格子(最多能顯示42天),因此只須要找到一個合適的開始位置"把把每個月天數(從1連續的超過31的整數)放入格式便可.
2. 上面所提到的"合適的開始位置"即把當前月的1號與星期給對應上
3. 作一些恰當優化,由於研究windows日曆會發現,若是某月1號是星期天,那麼"合適的開始位置"實際上是第二行的第一個位置,而不是第一行的第一個位置
經過上面分析,咱們發現多出了兩個要解決的問題:
1. 某年某月有多少天;
2. 某月的1號是星期幾
問題解決代碼以下:
//獲取某月1號是星期幾。實現方法是構建一個日期對象,經過調用 getDay()方法對到當前日期是星期幾 function getStartPos() { return new Date(dateStr.replace(/(\d+)$/, '1')).getDay(); } //獲取某月有多少天。2月的潤年是29天,平年28天,還有就是1,3,5,7,8,10,12月是31天 function getMonthDaysNum() { var year = c.year; //return new Date(c.year+"/"+ (c.month+1) +"/0").getDate(); //js中的月份從0開始,現實中從1開始 if(c.month === 1) { return (year%4==0&&year%100!=0)||(year%400==0) ? 29 : 28; }else if(~'0 2 4 6 7 9 11'.indexOf(c.month+'')) { return 31; } else { return 30; } }
問題d: 若是實現自定義節日
解決辦法: 對Canlendar類(型)增一個域:festival,這個域的產生會取默認與自定義節日的一個並集,是一個對象數組.在每渲染日曆後,會根據此數組對td裏面的a標籤加一個date-festival的自定義屬性,而後使用a::after僞元素來顯示此屬性
//節假日對象數組 this.festival = [ {date:'4,01', name:'愚人節'}, {date:'5,01', name:'勞動節'}, {date:'6,01', name:'兒童節'}, {date:'8,01', name:'建軍節'}, {date:'10,01', name:'國慶節'} ].concat(this.opts.customFestival);
/*使用::after顯示元素的某個屬性值,並經過定位來顯示在當前Hover a元素的旁邊*/ .canlendar td a::after{ z-index: 10; display: none; position: absolute; white-space: nowrap; bottom:-15px; left:0; background-color: #000; color: #f00; content: attr(date-festival); font-size: 12px; line-height: 16px; height: 16px; } .canlendar td:hover a::after { display: block; }
問題e:其它
1. 每一個Canlendar對象有一個_canlendar屬性,包含了四個屬性.始終根據此對象去計算並渲染日曆
{ date: c.getDate(), //日期 day: c.getDay(), //星期幾 year: c.getFullYear(), //年份 month: c.getMonth() //月分 }
2. 此組件的核心方法是render方法,在全部的與DOM相關的操做都在這裏面.此方法會在組件初始化裏自動調用一次.後面當上一月或下一月被點擊要切換月數時也會調用此方法
3. 在放多API設計上採用了jQuery的思想,把setter,getter合到一個API上面,只有一個參數是getter,兩個參數是setter.如前面提供的month()方法
三:後記
固然因爲這只是個練習,因此也只是實現了一個簡單的日曆.像常見的雙日曆(雙日曆之間的通訊)以及其它一些Bug還須要後續不斷完善.不過到此仍是有個日曆的樣子.源文件下載地址:日曆組件練習,因爲沒有考慮瀏覽器兼容性,因此請在高版本瀏覽器裏面查看效果.推薦使用chrome,firefox。