我直接把jquery datepicker的結構抄過來,類名也照搬。於是一個換膚的日歷就誕生了。
<div ms-controller="datepicker">
<div id="ui-datepicker-div" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" style="display:block">
<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix ui-corner-all">
<a class="ui-datepicker-prev ui-corner-all" title="Prev"
ms-click="prevMonth"
ms-hover="ui-state-hover"
ms-hover-1="ui-datepicker-prev-hover"
>
<span class="ui-icon ui-icon-circle-triangle-w">Prev</span></a>
<a class="ui-datepicker-next ui-corner-all" title="Next"
ms-click="nextMonth"
ms-hover="ui-state-hover"
ms-hover-1="ui-datepicker-next-hover"
>
<span class="ui-icon ui-icon-circle-triangle-e">Next</span></a>
<div class="ui-datepicker-title">
<select ms-each-month="$months" ms-if="changeMonth" ms-model="currentMonth" >
<option value="{{month}}" ms-selected="currentMonth == month">{{month+1}}月</option>
</select>
<select ms-each-year="candidateYears" ms-if="changeYear" ms-model="currentYear" >
<option value="{{year}}" ms-selected="currentYear == year">{{year}}年</option>
</select>
{{title}}
</div>
</div>
<table class="ui-datepicker-calendar" >
<thead>
<tr ms-each-date="$weeks">
<th ms-class-ui-datepicker-week-end="$first">
<span title="星期{{date}}">{{date}}</span>
</th>
</tr>
</thead>
<tbody ms-each-week="currentWeeks" ms-click="selectDay">
<tr ms-each-day="week">
<td ms-class-ui-datepicker-other-month="Number(day.split('-')[1]) != currentMonth"
ms-class-ui-datepicker-week-end="$first || $last"
ms-class-ui-state-disabled="day.split('-')[3] == 1"
ms-class-ui-datepicker-unselectable="day.split('-')[3] == 1"
>
<a class="ui-state-default" ms-title='currentMonth' href="#"
ms-if="showOtherMonths || Number(day.split('-')[1]) == currentMonth"
ms-hover="ui-state-hover"
ms-class-ui-state-highlight="isToday"
>{{day.split('-')[2]}} </a>
</td>
</tbody>
</table>
<div class="ui-datepicker-buttonpane ui-widget-content" ms-if="showButtonPanel">
<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all"
ms-hover="ui-state-hover"
ms-click="backToday"
>Today</button>
<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all"
ms-hover="ui-state-hover"
>Done</button>
</div>
</div>
<link rel="stylesheet" ms-href="http://code.jquery.com/ui/1.10.3/themes/{{theme}}/jquery-ui.css" >
<div><input type="radio" ms-model="changeMonth" />可選擇月份</div>
<div><input type="radio" ms-model="changeYear" />可選擇年份</div>
<div><input type="radio" ms-model="showButtonPanel" />顯示按鈕面板</div>
<div><input type="radio" ms-model="showOtherMonths" />顯示其他月份的日期</div>
<div><select ms-model="theme">
<option value='start'>start</option>
<option value='smoothness'>smoothness</option>
<option value='flick'>flick</option>
<option value='sunny'>sunny</option>
<option value='excite-bike'>excite bike</option>
<option value='black-tie'>black tie</option>
<option value='trontastic'>trontastic</option>
<option value='swanky-purse'>swanky purse</option>
<option value='le-frog'>le frog</option>
<option value='blitzer'>blitzer</option>
<option value='dot-luv'>dot luv</option>
<option value='mint-choc'>mint-choc</option>
<option value='hot-sneaks'>hot sneaks</option>
<option value='south-street'> south street</option>
<option value='humanity'>humanity</option>
<option value='vader'>vader</option>
<option value='eggplant'>eggplant</option>
<option value='cupertino'>cupertino</option>
<option value='overcast'>overcast</option>
</select>你喜歡的皮膚</div>
<p>你選擇的日期為 {{selectedDate | date('yyyy-MM-dd')}}</p>
</div>
上面的結構分兩部分,最上的日歷,下面的一些表單元素用於控制日歷的配置。
avalon.ready(function() {
avalon.define("datepicker", function(vm) {
//配置
vm.changeYear = false
vm.changeMonth = false
vm.minDate = new Date(2013, 3, 25);
//vm.maxDate
vm.showOtherMonths = false;
vm.showButtonPanel = false;
//當前時間
vm.selectedDate = new Date;
vm.currentDate = new Date;
vm.currentMonth = vm.currentDate.getMonth();
vm.currentYear = vm.currentDate.getFullYear();
vm.currentWeeks = getWeeks(vm.currentDate);
//顯示頂部的年份與月份
vm.title = {
get: function() {
var format = "";
if (!this.changeYear && this.changeMonth) {
format = "yyyy年";
} else if (this.changeYear && !this.changeMonth) {
format = "MMMM";
} else if (!this.changeYear && !this.changeMonth) {
format = "MMMM yyyy年";
}
return format && avalon.filters.date(this.currentDate, format);
}
};
//星期顯示
vm.$weeks = "日一二三四五六".split("");
//月份下拉菜單
vm.$months = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
//當月的日期
function isDisabled(time) {
var disabled = false;
if (vm.minDate) {
disabled = time < vm.minDate;
}
if (disabled && vm.maxDate) {
disabled = time > vm.maxDate;
}
return disabled - 0;
}
function getWeeks(cur) {
vm.currentDate = cur;
var year = cur.getFullYear();
var month = cur.getMonth();//得到今天是幾月(0 ~ 11)
var date = cur.getDate(); //得到今天是幾號 (1 ~ 31)
cur.setMonth(month + 1);//改為下一個月,
//由於日期是1 ~ 31, 0則是退到上一個月的最后一天,於是得到這個月的總天數
cur.setDate(0);
var num = cur.getDate();
var next = 6 - cur.getDay();
var dates = avalon.range(1, num + 1);
dates = dates.map(function(d) {
var time = new Date(year, month, d)
return [year, month, d, isDisabled(time)].join("-");
});
cur.setMonth(month);
cur.setDate(1);//得到當月的第一天
var prev = cur.getDay();//0 ~ 6
cur.setDate(date);//還原
for (var i = 0; i < prev; i++) {//補上上一個月的日期
cur = new Date(year, month, -1 * i)
dates.unshift([year, cur.getMonth(), cur.getDate(), isDisabled(cur)].join("-"))
}
for (var i = 0; i < next; i++) {//補上下一個月的日期
cur = new Date(year, month + 1, i + 1)
dates.push([year, cur.getMonth(), cur.getDate(), isDisabled(cur)].join("-"))
}
var ret = [];
while (dates.length) {//每行七個分組
ret.push(dates.splice(0, 7));
}
return ret;//一個三維數組
}
//取得當年的前后20年
function getYears() {
var y = vm.currentYear;
return avalon.range(y - 10, y + 10);
}
vm.candidateYears = getYears();
//點擊事件
vm.theme = "start";
vm.nextMonth = function(e) {
e.preventDefault()
var d = vm.currentDate;
var m = d.getMonth();
d.setMonth(m + 1);
m = d.getMonth();
if (m === 0) {
var y = d.getFullYear();
vm.currentYear = y;
}
vm.currentMonth = m;
};
//點擊事件
vm.prevMonth = function(e) {
e.preventDefault()
var d = vm.currentDate;
var m = d.getMonth();
d.setMonth(m - 1);
m = d.getMonth();
if (m === 11) {
var y = d.getFullYear();
vm.currentYear = y;
}
vm.currentMonth = m;
};
//偵聽
vm.$watch("currentMonth", function(val) {
var d = vm.currentDate;
d.setMonth(val);
vm.currentWeeks = getWeeks(d);
vm.title = NaN;
});
vm.$watch("currentYear", function(val) {
var d = vm.currentDate;
d.setFullYear(val)
vm.currentWeeks = getWeeks(d);
vm.title = NaN;
});
//高亮當前選中的日期
vm.selectDay = function(e) {
var el = e.target;
e.preventDefault()
if (el.tagName === "A" && el.parentNode.tagName === "TD" && !/disabled/.test(el.className)) {
var d = el.$scope.day.split('-')
vm.selectedDate = new Date(d[0], d[1], d[2]);
}
};
//高亮今天的日期
var today = new Date;
var atoday = [today.getFullYear(), today.getMonth(), today.getDate()];
vm.isToday = function() {
var day = this.$scope.day;
return day.slice(0, day.lastIndexOf("-")) === atoday.join("-");
};
vm.backToday = function() {
vm.currentMonth = atoday[1];
vm.currentYear = atoday[0];
}
});
avalon.scan();
});
這個JS代碼比起先前的更清晰,放棄使用$fire這個危險的操作,建議大家也不要用它。因為如果沒有嚴格的值變動檢測, 這很容易引起無限遞歸。 現在avalon已經完全重用現有的節點,不會像過去那樣每次都清空然后又添加。因此性能更好。
可選擇月份
可選擇年份
顯示按鈕面板
顯示其他月份的日期
選擇你喜歡的皮膚(注:這些樣式都受到博客園染指了)
你選擇的日期為 {{selectedDate | date('yyyy-MM-dd')}}
所有JS代碼不到150行,就能涵蓋jquery ui datepicker(2000多行)的絕大多數功能。如果努力一點,把模板也封裝一下,其他功能也跟進,最多也是500行的規模。可謂MVVM的威力。而且這樣寫JS,可讀性非常好,思路不會像着jQuery那樣跟着CSS表達式——“這個元素是在哪里,該添加類名還是移除類名……”
用了MVVM后,我們寫代碼的思路將是這個樣子
- 先把HTML搞出來;
- 把重復的部分轉換為一個each綁定,VM中對應一個數組;
- 把頁面視情況要顯示隱藏,要切換類名的東西,在VM中由一個布爾屬性來控制;
- 事件回調里再來也不進行DOM操作,而是對這些數組進行shift, pop操作,布爾屬性進行反轉;
- 把這些邏輯封到一個VM中
- 最后對視圖進行綁定
