概述
在開篇之前,先附上日歷的代碼地址和演示地址,代碼是本文要分析的代碼,演示效果是本文要實現的效果
代碼地址:https://github.com/aspwebchh/javascript-control/tree/master/calendar
演示地址: https://www.chhblog.com/html/demo/calendar.html
本文的目的除了詳細說明開發一款具備基本功能的網頁日歷的方法與細節以外,還附加說明了如何合理的組織日歷特效的代碼和因此帶來的好處。
按照本文的教程開發出來的效果如下
他具有選擇年月日、選擇今天、清空文本框這些日歷的基本功能,能滿足日常項目中出現的普通日期選擇需求, 算的上是五臟俱全的小麻雀。
本文主要描述JavaScript實現的細節,日歷的CSS布局細節將被省略,有興趣的同學可閱讀calendar.css中的css代碼獲知實現方法。
此日歷特效由原生JavaScript代碼寫成,並不依賴jQuery等第三方框架。它的JavaScript代碼由三個文件構成
common.js
公用函數庫文件, 里面的函數都是通用型的,並不僅僅和特效相關,在任何網頁特效中都可以使用它們
calendar_core.js
一個純粹的、通用的日歷特效的所有代碼,更任何其它頁面元素沒有關系,比如說用來放置日期的文本框
calendar.js
合理調用calendar_core.js中的代碼來構建一個真正可以使用的日歷特效。
關於calendar_core.js和calendar.js的說明,似乎有點令人犯迷糊,不過這不要緊,通過下面的詳細講解,會使讀者了解到這兩個文件中代碼作用與區別。
代碼規約
因為JavaScript在一些常規的編程概念上沒有統一的實現方法的緣故,在介紹日歷的核心實現邏輯之情,先介紹下代碼中所有使用的容易分散讀者注意力或者造成讀者出現理解偏差的語法細節。
防止全局變量污染名稱空間
此案列的大部分代碼會被這樣一段代碼包圍起來
(function(){
//功能代碼
})();
其實這么做的主要目的是為了讓變量名稱和函數的名稱全局名稱空間, 換句話說就是讓用不到它的地方看不到它。
那為什么這個function要被一個括號括起來,而且在這個括號后面再加上一個括號。 括號的作用很簡單,跟小學數學中所學的括號作用一樣,是用來提升運算優先級的,比如說(1+2)*3,其中(1+2)會被優先與乘法運算,返回的結果就是3。可JavaScript沒有規定,括號中必須放置四則運算表達式,括號中也可以放別的東西,比如說函數。這么一說就好理解了
(function(){
//功能代碼
})();
這段代碼可一被分解為兩步, 第一個括號的作用是返回括號中的函數,第二個括號的作用是調用第一個括號返回的函數,這跟下面這段代碼是一個意思,只是合在一起可以省略函數名。
var func = (function(){
//功能代碼
});
func();
類的實現
非ES6的JavaScript語法不支持類,但是類是不可缺少的編程元素,所幸JavaScript可以通過function關鍵字模擬類的實現。
常規的模擬方法是使用function和function的它的prototype屬性,可這么做無法實現面向對象中private關鍵字的效果,所以我在這個案列中並沒有采用這種方法,而是使用了
function Klass(){
this.publicFunc1 = function(){}
this.publicFunc2= funciton(){}
var privateFunc1 = funciton(){}
var privateFunc2 = function(){}
}
var klass = new Klass();
Klass.publicFunc1();
Klass.publicFunc2();
這種方式模擬類的實現。
實現細節
公用函數庫 - common.js
此文件中有4個函數
addEventHandler
為DOM對象綁定事件。因為要兼容低版本IE,所以特地封裝成函數
removeEventHandler
移除DOM對象綁定的事件
getOffset
獲得html元素在頁面中的位置。使用場景如點擊輸入框彈出日歷時將日歷定位到文本框下方就要用到這個函數。
checkDate
檢查日期字符串格式是否合法
這4個函數的代碼在文中略去,有需要的讀者可直接查看源代碼
日歷核心類 - calendar_core.js
此文件包含日歷特效的核心功能,其中有一個函數和一個類。
函數 newCalendarID
函數代碼如下
var instanceCount = 0;
function newCalendarID(){
return 'calender_' + ( ++instanceCount );
}
這個函數的作用是生成代表日歷DOM元素的ID。 很多時候, 一個頁面上不會只有一個日歷,如下圖
所以必須要一個不重復的值作為不同日歷HTML元素的ID,以防止JavaScript操作日歷html元素時造成沖突。 newCalendarID通過自增一個數值變量並結合一個字符串來生成日歷的ID,生成的ID格式如下
calender_1
calender_2
calender_3
calender_4
這個函數在日歷的構造函數中被調用,每次實例化一個日歷時為日歷html元素賦予一個ID。
類 Calender
類 Calender 封裝了實現日歷功能的代碼,包括生成日歷、年份月份切換、 選擇清空日期等等。
Calender 類的公共接口如下
function Calender() {
//事件
this.onClear = function() {};
this.onSetToday = function() {};
this.onSelected = function( y, m, d ) {};
//方法
this.render = function( placeholder ) {}
this.setDate = function( y, m, d ) {}
this.position = function( left, top ) {}
this.hide = function() {}
this.contains = function( target ) {}
}
我們通過從外到內的模式講解日歷類的實現,先講解日歷的接口,再講解代碼的細節。
首先,有有點讀者要明白,Calender類表示的就是日歷,是那個在網頁上實現日期選擇功能的日歷特效。
當實例化Calender對象並調用對象的render方法,一個日歷就被顯示在網頁上了。 代碼如下
var c = new Calender();
c.render();
render方法就是用來生成日歷特效的html元素,並將元素添加到頁面上,執行后的效果如下圖
setDate 方法用來設置日歷的日期 ,接受年月日三個參數。日歷初始化時持有的日期是當前的日期,因此日歷界面上當前日期的位置被設為選中狀態。然而,有時候我們希望被選中的日期是任意的而非只能是今天,這個時候setDate方法就能派上用場。
position 方法用來設置日歷在頁面上的位置,接受left、top兩個參數。 比如說,當調用render方法初始化日歷后,我們想讓日歷顯示在頁面中間,可以這樣做
var c = new Calender();
c.render();
c.position(window.innerWidth / 2, window.innerHeight / 2)
代碼執行效果如下圖
hide方法用來隱藏日歷,contains方法用來檢查html元素是否包含在日歷元素之中,這兩個方法在接下來的功能實現剖析中有使用的場景。
this.onSelected = function( y, m, d ) {};
this.onClear = function() {};
this.onSetToday = function() {};
這三個方法其實並不是方法,而是事件,就像html元素的onclick事件一樣,會在特定的時候被觸發。
onSelected事件在選中日期時被觸發
onClear事件在點擊日歷右下角的「清空」按鈕時被觸發
onSetToday事件在點擊日歷右下角的「今天」按鈕時被觸發
以一個最基本的最常見的日期選着並填充文本框為例,我們可以通過結合這三個事件和上面講解的部分方法來實現, 代碼如下
//獲得文本框元素
var dateInput = document.getElementById("date");
//實例化日歷對象
var calender = new Calender();
//綁定onSelected事件,當選中日期后被執行
calender.onSelected = function(y,m,d) {
//填充選中的日期至文本框
dateInput.value = [y,m,d].join("-");
//填充后隱藏日歷
this.hide();
}
//綁定onSetToday事件,當點擊今天按鈕后被執行
calender.onSetToday = function() {
var now = new Date();
//填充當前日期至文本框
dateInput.value = now.getFullYear() + '-' + ( now.getMonth() + 1 ) + '-' + now.getDate();
//填充后隱藏日歷
this.hide();
}
//綁定onClear事件,當點擊清空按鈕后被執行
calender.onClear = function() {
//清空文本框
dateInput.value = "";
//填充后隱藏日歷
this.hide();
}
//初始化日歷
calender.render();
//因為初始化后的日歷會顯示在頁面上,所以需要事先隱藏
calender.hide();
//但文本框獲得焦點時顯示日歷
dateInput.onfocus = function() {
//獲得文本框在頁面中的位置,getOffset方法在之前講解過
let offet = getOffset(this);
//讓日歷現實在文本框的下方
calender.position(offet.left, offet.top + 20);
}
效果如圖
不知道讀者們有沒有從這段代碼中發現,日歷特效本身和輸入框之間是沒有任何關聯,它們之間的交互是通過那三個事件間接進行的,這正是軟件工程中「低耦合」設計原則的體現。在日歷和輸入框之間有一個銜接層,這個銜接層就是那三個事件, 這三個事件是可以動態設置的, 假如需求改變,我們點擊日歷時不想將值填充到文本框,而是想直接將日期發送至服務器,那么我們只需要將onSelected事件中的代碼改為發送數據的ajax請求代碼即可, 日歷類本身的代碼完全不用改動, 這極大的降低的代碼的維護成本。
其實這中通過事件去解耦的代碼設計方式隨處可見,比如我們點擊一個按鈕彈出一個提示消息這樣的效果
var btn = document.getElementById("btn");
btn.onclick = function() {
alert("hello world");
}
如上面的代碼,用的也是同樣的思路,html按鈕元素和其它JavaScript效果是沒有聯系的,然而它必然要和外部交互,比如點擊的時候執行某個動作,否者就沒有存在的意義了。如何做到既不與外部元素綁死又能與外部元素交互?答案就是增加一個銜接層,這個銜接層就是「事件」。我們的日歷特效不正也是采用這種做法嗎。
如果了解設計模式的讀者應該能看的出來,這其實是策略模式的應用,如果更貼切一點也可以說是觀察者模式的應用。
接下來我們再講講Calender類內部的構建。
Calender類有6個私有的成員變量
var calendarID = newCalendarID();
var self = this;
var calendarEl;
var selectedYear;
var selectedMonth;
var selectedDate;
calendarID ,日歷html元素的ID, 調用newCalendarID方法生成, 關於此函數的細節在前文有過介紹。
self,保存Calender的this指針,供程序上下文中有需要的地方使用,因為JavaScript中this指針不確定的原因,要在類中正確的使用this指針,必須在某個this值還指向類自身的地方將它保存下來,以供應后面的代碼使用。
calendarEl,日歷html元素的根元素。日歷是動態生成的html元素,此變量指向的就是日歷html元素的DOM對象。
selectedYear,日歷選中日期的年份
selectedMonth,日歷選中日期的月份
selectedDate,日歷選中日期的天
Calender類中除了有這六個私有變量以外,還有一系列私有方法
getStartDate
getEndDate
getContentItemHtml
getContentHtml
getCalendarHtml
getElement
genCalanderElementID
monthChangeAction
yearChangeAction
initCalendar
refreshCalender
這些方法不是Calender類對外公布接口的一部分,但是他們參與了實現日歷的功能。 在這里我們不一個一個的介紹方法的作用,我們根據日歷初始化代碼的執行順序來介紹他們,輪到誰就介紹誰。
日歷類被實例化后render方法首先被調用。
var calender = new Calender();
calender .render();
newCalender()實例化的過程很簡單,無非就是聲明和初始化部分成員變量的值,真正的大戲是render方法被調用。
this.render = function( placeholder ) {
var now = new Date();
selectedYear = now.getFullYear();
selectedMonth = now.getMonth();
selectedDate = now.getDate();
initCalendar( selectedYear, selectedMonth, selectedDate, placeholder );
}
rander方法接受一個placeholder參數,這個參數是一個html元素的ID,表示日歷初始化后所在的位置,也就是說當表示日歷的html元素生成后,會成為ID為這個參數的值的元素的子元素,假如調用render方法時不指定這個參數, 那么日歷html成為body子元素。
接着,render方法會將類的三個表示選中的年月日的成員變量設置為當前的年月日,然后在調用私有方法initCalendar初始化日歷, initCalendar承載着生成日歷的主要工作。
var initCalendar = function( placeholder ) {
calendarEl = document.createElement( 'div' );
calendarEl.id = calendarID;
calendarEl.className = 'aspwebchh';
calendarEl.innerHTML = getCalendarHtml();
placeholder = placeholder ? document.getElementById( placeholder ) : document.body;
placeholder.appendChild( calendarEl );
refreshCalender(selectedYear, selectedMonth, selectedDate);
monthChangeAction();
yearChangeAction();
dateSelectedChangeAction();
}
我們知道 calendarEl 成員變量表示日歷的html元素,在initCalendar方法中,它被初始化了。 從代碼中可一看出,它是一個div元素,被設置一個唯一id,被設置一個class, 日歷的html結構由 getCalendarHtml 方法生成, 並被設為 id 為placeholder的值的子元素,如果id為placeholder的元素不存在,那么由body元素代替它。
現在,我們來重點看看 getCalendarHtml 這個方法,日歷的html結構是由它動態生成的。
var getCalendarHtml = function() {
var html = ' <div class="calendar_tool" id="'+ genCalanderElementID("tool") +'">'+
' <div class="calendar_month">'+
' <select id="'+ genCalanderElementID("month_select") +'"><option value="0">1月</option>'+
' <option value="1">2月</option>'+
' <option value="2">3月</option>'+
' <option value="3">4月</option>'+
' <option value="4">5月</option>'+
' <option value="5">6月</option>'+
' <option value="6">7月</option>'+
' <option value="7">8月</option>'+
' <option value="8">9月</option>'+
' <option value="9">10月</option>'+
' <option value="10">11月</option>'+
' <option value="11">12月</option></select>'+
' </div>'+
' <div class="calendar_year">'+
' <input type="button" value="<" class="calendar_year_left" id="'+ genCalanderElementID("year_prev") +'"><input'+
' type="text" class="calendar_year_input" id="'+ genCalanderElementID("year_input") +'"><input type="button"'+
' value=">" class="calendar_year_right" id="'+ genCalanderElementID("year_next") +'">'+
' </div>'+
' </div>';
html += '<div class="calendar_content" id="'+ genCalanderElementID("date_list") +'"></div>';
html += '<div class="calendar_action">' +
'<input type="button" value="清空" id="'+ genCalanderElementID("clear") +'">' +
'<input type="button" value="今天" id="'+ genCalanderElementID("today") +'">'+
'</div>';
return '<div class="calendar_body">' + html + '</div>';
}
由代碼可以看出,getCalendarHtml 方法就是通過動態拼接JavaScript字符串生成日歷的html的。在此方法中, 還是一個 genCalanderElementID 方法被頻繁的調用,這個方法的代碼如下
var genCalanderElementID = function( id ) {
return calendarID + "_" + id;
}
他的作用就是用來生成日歷的一些子元素的ID, 當然,生成的ID全局唯一的, 因為它的前綴就是標識日歷唯一性的calendarID。
標紅的就是用這個方法生成的ID
這個時候生成的日歷html元素還並不完整,日期部分處於缺失狀態, 如下圖
我們再回到處於調用棧上一層的initCalendar方法中來,當日歷的外圍html結構生成完畢以后,接着會調用 refreshCalender 方法。
refreshCalender方法的作用是刷新日歷的界面, 它接受年月日三個參數, 根據這三個參數來更新日歷的界面。
上面兩長圖片是分別給refreshCalender傳遞2018,5,16和2018,6,13兩組參數的執行結果,可以看出,此方法是整個日歷特效的核心方法,日歷界面的更新變化都要靠它。
refreshCalender方法做三件事請。
- 設置日歷界面上年份輸入框的值。
- 設置日歷界面上月份選擇框的值。
- 生成日歷界面日期部分的html。這一步是最重要的一步,通過調用 getContentHtml 來完成。
getContentHtml 方法接受年和月兩個參數,生成整一個月份的html
上圖就是 getContentHtml 方法生成的內容。 方法的開頭有這樣兩行代碼用來獲得日期范圍。
var startDate = getStartDate( y, m );
var endDate = getEndDate( y, m );
這個日期范圍是必須的。日歷效果的一個特點是要做到星期和日期對應,望一眼日期就能知道是星期幾。此外,日歷界面還要保持工整, 因此, 我們必須要知道日歷的第一周的開始時間是幾號,日歷的最后一周結束日期是幾號, 要知道為保持日歷界面的工整,每一頁日歷展示的日期都是需要跨月的,上面的兩行代碼就是獲得每一頁日歷的開始日期和結束日期的。
以上圖為例,一個5月份的日歷,那么這一頁的開始日期是4月29日,周日;結束日期是6月5日,周二。
之后的代碼就是根據這個時間范圍生成html,並通過判斷日期給每個日期元素加上對應的css class屬性, 因為我們要讓非本月份的日期顯示成灰色, 本月份的日期顯示成藍色,當前日期擁有藍色背景。具體的實現細節可以通過閱讀下面的代碼清單獲知,在這里就不贅述了。
var getStartDate = function( y, m ) {
var dt = new Date( y, m, 1 );
var week = dt.getDay();
dt.setDate( dt.getDate() - week );
return dt;
}
var getEndDate = function( y, m ) {
var dt = new Date( y, m ,1 );
dt.setMonth( dt.getMonth() + 1 );
dt.setDate( 0 );
return dt;
}
var getContentItemHtml = function( date, currMonth ) {
var content = '';
if( date.getDate() == selectedDate && date.getMonth() == selectedMonth && date.getFullYear() == selectedYear ) {
content += '<li class="selected">';
} else if( currMonth == date.getMonth() ) {
content += '<li class="c">'
} else {
content += '<li>';
}
content += '<a href="javascript:;">' + date.getDate() +'</a>';
content += '</li>';
return content;
}
var getContentHtml = function( y, m ) {
var startDate = getStartDate( y, m );
var endDate = getEndDate( y, m );
var title = '<dl class="calendar_title"><dd>日</dd><dd>一</dd><dd>二</dd><dd>三</dd><dd>四</dd><dd>五</dd><dd>六</dd></dl>';
var content = '<ul>';
for( var i = 0; i < 38; i++ ) {
content += getContentItemHtml(startDate, m);
if( ( i + 1 ) % 7 == 0 ) {
content += '</ul><ul>';
}
startDate.setDate( startDate.getDate() + 1 );
}
content += '</ul>';
return title + content;
}
讓我們再回到 initCalendar 方法中來,調用 refreshCalender 方法后, 接下是
monthChangeAction();
yearChangeAction();
dateSelectedChangeAction();
這三個方法的調用。
這三個方法的作用是給日歷中的元素綁定操作效果事件的。
monthChangeAction方法用於當日歷的月份選擇的值改變時刷新日歷的日期部分內容
var monthChangeAction = function() {
var monthSelect = getElement( 'month_select' );
var yearInput = getElement( 'year_input' );
addEventHandler( monthSelect, 'change', function() {
var month = this.value;
var year = yearInput.value;
getElement( 'date_list' ).innerHTML = getContentHtml( year, month );
} );
}
yearChangeAction方法用於當日歷的年份改變時刷新日歷的日期面板
var yearChangeAction = function() {
var monthSelect = getElement( 'month_select' );
var yearInput = getElement( 'year_input' );
var yearPrev = getElement( 'year_prev' );
var yearNext = getElement( 'year_next' );
addEventHandler( yearInput, 'blur', function() {
if( /[^\d]+/.test( this.value ) ) {
this.value = this.value.replace( /[^\d]+/g, '' );
}
getElement( 'date_list' ).innerHTML = getContentHtml( yearInput.value, monthSelect.value );
} );
addEventHandler( yearPrev, 'click', function() {
var year = yearInput.value;
var month = monthSelect.value;
getElement( 'date_list' ).innerHTML = getContentHtml( --year, month );
yearInput.value = year;
} );
addEventHandler( yearNext, 'click', function() {
var year = yearInput.value;
var month = monthSelect.value;
getElement( 'date_list' ).innerHTML = getContentHtml( ++year, month );
yearInput.value = year;
} );
}
yearChangeAction相對monthChangeAction較為復雜,因為它不但要處理年份輸入框的事件, 還要處理“上一年”和 “下一年”兩個按鈕的的事件處理。
dateSelectedChangeAction用於處理日期選擇事件、“清空”按鈕事件、“今天”按鈕事件, 從方法的代碼結構中就可以看出方法的功能由這三部分構成。
var dateSelectedChangeAction = function() {
//日期選中處理
addEventHandler( getElement( 'date_list' ), 'click', function( e ) {
e = e || window.event;
var t = e.target || e.srcElement;
if( t.tagName != 'A' ) {
return;
}
var year = getElement( 'year_input' ).value;
var month = getElement( 'month_select' ).value;
var date = t.innerHTML;
if( typeof( self.onSelected ) == 'function' ) {
self.onSelected( parseInt( year ), parseInt( month ), parseInt( date ) );
}
} );
//“清空”按鈕點擊處理
addEventHandler( getElement( 'clear' ), 'click', function() {
if( typeof( self.onClear ) == 'function' ) {
self.onClear();
}
} );
//“今天”按鈕點擊處理
addEventHandler( getElement( 'today' ), 'click', function() {
if( typeof( self.onSetToday ) == 'function' ) {
self.onSetToday();
}
} );
}
這三部分事件處理代碼其實本身不執行具體的功能, 它們的真正作用是觸發另一個事件。具體一點說,這個方法做了這么三件事情
- 當選擇日歷的具體日期時,Calender類實例的onSelected事件被觸發
- 當點擊日歷的“清空”按鈕的時,Calender類實例的onClear事件被觸發
- 當點擊日歷的“今天”按鈕時,Calender類實例的onSetToday事件被觸發
這三個事件我們在前面講過是用於解除日歷本身與使用日歷的頁面的耦合的,如此能使日歷更加通用化。
至此calendar_core.js中的Calender類的內部結構已經解析完畢,一款功能完善的日歷特效呈現在了我們面前
window.Calender = Calender;
通過這行代碼導出日歷類,我們就可以在外部使用它了。
接下來我們說說如何去使用它。
使用日歷核心類 - calendar.js
通常,日歷特效的使用都會伴隨着輸入框,如下圖所示
當日歷上的日期被選中時,着個日期會別填充到輸入框里。 同時,這個日歷是但實例的,不管頁面上有多少個輸入框需要輸入日期,同一時刻,頁面上只能有一個日歷, 一個日歷服務與多個不同的文本框。
calendar.js文件中的代碼示例就是以此模式實現。
(function(){
var single;
var element;
function closeHandler( e ) {
e = e || window.event;
var t = e.target || e.srcElement;
if( single.contains( t ) ) {
return;
}
if( t == element ) {
return;
}
single.hide();
}
function checkElementValue() {
if( !element.checkDateAction ) {
addEventHandler( element, 'blur', function() {
if( this.value != '' && this.value != undefined && !checkDate( this.value ) ) {
alert( '日期格式不正確' );
this.value = '';
}
} );
element.checkDateAction = true;
}
}
function renderCalendar() {
if( !single ) {
single = new Calender();
single.onSelected = function( y, m, d ) {
var datestr = y + '-' + ( m + 1 ) + '-' + d;
element.value = datestr;
this.hide();
}
single.onSetToday = function() {
var now = new Date();
element.value = now.getFullYear() + '-' + ( now.getMonth() + 1 ) + '-' + now.getDate();
this.hide();
}
single.onClear = function() {
element.value = '';
this.hide();
}
single.render();
}
var offset = getOffset( element );
single.position( offset.left, offset.top + element.offsetHeight );
}
function initCalendarSelectedValue() {
var date = element.value;
if( checkDate( date ) ) {
var date = date.replace(/\-|\/|\./g,"/");
var ymd = date.split("/");
var y = parseInt(ymd[0]);
var m = parseInt(ymd[1]) - 1;
var d = parseInt(ymd[2]);
single.setDate( y, m, d );
}
}
function calendar() {
var e = calendar.caller.arguments[0] || window.event;
element = e.target || e.srcElement;
removeEventHandler( document.documentElement, 'click', closeHandler );
checkElementValue();
renderCalendar();
initCalendarSelectedValue();
addEventHandler( document.documentElement, 'click', closeHandler );
}
window.calendar = calendar;
})();
calendar.js中有2個全局變量和5個函數
var single;
var element;
closeHandler()
checkElementValue()
renderCalendar(element)
initCalendarSelectedValue()
calendar()
變量single是日歷的實例,它只被初始化一次,可以把它看成一個單列。
變量element是調用日歷的輸入框,它會在calendar函數調用時被重復賦值,引用當前input輸入框的DOM對象。
calendar是主函數,唯一一個被導出到頁面使用的函數,其它的函數是calendar函數功能的部分,為了使代碼易於維護才將他們提煉成為函數。
我們看到,其它4個函數都會在calendar函數中的某個位置出現
closeHandler 是一個工具函數, 用於實現點擊頁面上除日歷本身以外的任何位置便隱藏日歷的效果的。
checkElementValue 用於檢查文本框中默認有值的情況下值的格式是否正確,假如不正確則給予提示。
renderCalendar用於實例化日歷,並設置相應的事件,被初始化的實例是唯一的, 與此同時, 日歷將被顯示在輸入框的下方。
initCalendarSelectedValue用於將輸入框中的默認值設置為日歷的當前日期。
最后, calendar函數被導出
window.calendar = calendar;
在頁面中使用即可,使用方式很簡潔
<input type="text" onfocus="calendar()" id="begin_time" />
觸發輸入框的focus事件即能使用日歷。
至此,一款完整的日歷的所有細節展現在了我們面前。這款日歷功能很簡單, 可它有一個優點,它的代碼結構清晰,類和函數之間,方法與方法之間,職責異常清晰。 日歷本身與頁面之間是解耦的,互相之間通過事件進行通信, 這使得日歷的代碼復用能力變強了,如果我們哪天想把這個日歷挪作他用, 只需要提取出calendar_core.js中的代碼,稍微改動即可,至於calender.js中的代碼完全可以忽視。這是高內聚低耦合軟件設計思想的體現,以被業界證明是有效的提升代碼可維護性的思想,除了日歷,也適合在任何程序設計環境中使用。所以, 這篇文章與其說是在講解日歷特效的編寫,還不如說是在講解如何設計出結構優良的代碼的方法,從某種角度來講,這比寫出炫麗的JavaScript特效更加有用。