JavaScript日曆控制元件開發
概述
在開篇之前,先附上日曆的程式碼地址和演示地址,程式碼是本文要分析的程式碼,演示效果是本文要實現的效果
程式碼地址: ofollow,noindex" target="_blank">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請求程式碼即可, 日曆類本身的程式碼完全不用改動, 這極大的降低的程式碼的維護成本。
其實這中通過事件去解耦的程式碼設計方式隨處可見,比如我們點選一個按鈕彈出一個提示訊息這樣的效果
varbtn = 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
Line"/>
上圖就是 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特效更加有用。