輕前端筆記 - 開發 Vue.js 元件封裝 Kendo UI 日期選擇器
4 |
這兩年我的網頁開發主要走「輕前端」路線,以 ASP.NET MVC 或 ASP.NET Core .cshtml 為主體,直接 <script > 載入 jQuery、KendoUI 等程式庫處理 UI,若元素間互動較複雜的則用 Vue.js 實踐 MVVM。這有些背離當代重度依賴 npm、webpack、TypeScript 等編譯程序的主流前端開發方式,但它簡單輕巧、學習門檻低,對於具備 ASP.NET WebForm 背景的開發人員來說,比從頭學習整套前端框架及截然不同開發程序好上手很多。(我一直覺得 React.js/Angular 這類大框架需要「對它有愛」才容易上手,而且這還蠻吃緣份的,不是人人都能修成正果)
在 Kendo UI 與 Vue.js 的 MVVM 整合上,我一直有個小困擾。之前 Knockout.js 時代有現成的 Knockout-Kendo.js協助處理資料繫結,AngularJS 時代則有 angular-kendo.js。到了今天,Kendo UI 官方已直接支援 Vue.js,可以直接寫成:
<div id="vueapp" class="vue-app">
<h5>Select date: </h5>
<kendo-datepicker :min="minDate"
:max="maxDate"
:value="currentDate"
:format="'yyyy/MMMM/dd'"></kendo-datepicker>
</div>
但有個問題,要用它需回歸主流前端開發方式 - npm install、import Packages,再用 webpack 編譯的那一整套做法,我想走輕前端這條路,得自己想辦法。之前我的處理方式是 Kendo UI 輸入部分不繫結到 Vue 屬性,另寫 $("#txtDateInput").data("kendoDatePicker").value() 抓值,下場是 Vue 方法會不時出現 $("#...").data("kendoXXXX"),雖然用起來沒什麼問題,但一來程式碼變得雜亂,二來是要雙向繫結得加寫一堆邏輯,有點回到沒有 MVVM 前的蠻荒時代。
Vue.js Component 允許你寫出 <v-kendo-date-picker> 這類自訂元件,轉換產生的 HTML 元素、繫結屬性、事件全部都能客製,這篇文章就以 Kendo UI DatePicker 為例,來寫一個支援 v-model 雙向繫結的 Kendo UI 日期選擇器元件。
Vue.js 的 Component 說明文件寫得蠻清楚的,簡單整理本次案例相關重點:
- 使用 Vue.component('component-element-name', ) 註冊元件, 的內容跟 new Vue() 差不多,一樣有 data、watch、computed、methods 等(當然,沒有 el)。
- Component 的 data 必須為函式
data: function() { return { propName: propValue }; }
(確保重複使用該 Component 時能各自有自己的 data) - 使用 props 定義跟外界溝通的屬性,有兩種格式:1) props: ['attr1', 'attr2'] 2) props: { attr1: String, attr2: Boolean, attr3: Number }
- template 為產生 HTML 元素的樣版,其中的 v-bind、v-on 與一般 Vue 寫法相同
- Vue Component 可實現 v-model="..." 雙向繫結,方法是定義一個 prop: ['value'] 接受外部傳入值,傳值出去時則呼叫 this.$emit('input', theValue)。
- mounted、destroy,用來放 HTML 元素產生後的設定邏輯及消滅前的清理工作
掌握以上技巧,我寫了一個簡單範例,自訂 <v-kendo-date-picker> 元件以建立日期或日期選擇器,同時支援 KendoDatePicker 及 KendoDateTimePicker (由 Attribute time-picker="true" 切換):線上展示
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>KendoDataPicker Vue Component</title>
<link href="https://da7xgjtj801h2.cloudfront.net/2015.2.624/styles/kendo.common.min.css" rel="stylesheet"
type="text/css" />
<link href="https://da7xgjtj801h2.cloudfront.net/2015.2.624/styles/kendo.silver.min.css" rel="stylesheet"
type="text/css" />
<style>
html,
body {
font-size: 9pt;
}
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app" v-cloak>
<fieldset>
<legend>KendoDatePicker</legend>
<v-kendo-date-picker v-model="SelDate"></v-kendo-date-picker>
<div>{{SelDate.toLocaleString()}}</div>
</fieldset>
<fieldset>
<legend>KendoDateTimePicker</legend>
<v-kendo-date-picker v-model="SelDateTime" time-picker="true"></v-kendo-date-picker>
<div>{{SelDateTime.toLocaleString()}}</div>
</fieldset>
</div>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://da7xgjtj801h2.cloudfront.net/2015.2.624/js/kendo.ui.core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<script>
//實際應用時,元件會另存成 vue-components-filename.js 方便重複利用
Vue.component("v-kendo-date-picker", {
props: ['value', 'time-picker'],
data: function() {
return {
kendoObject: null
};
},
template: '<input type="text" />',
watch: {
value: function (val) {
this.kendoObject.value(val);
}
},
mounted: function () {
var self = this;
var format = self.timePicker ? "yyyy-MM-dd HH:mm" : "yyyy-MM-dd";
var widgetName = self.timePicker ? "kendoDateTimePicker" : "kendoDatePicker";
$(self.$el)[widgetName]({
value: self.value,
format: format,
change: function () {
self.$emit('input', this.value());
}
});
self.kendoObject = $(self.$el).data(widgetName);
},
destroy: function () {
this.kendoObject && this.kendoObject.destroy();
}
});
</script>
<script>
var vm = new Vue({
el: "#app",
data: {
SelDate: kendo.date.today(),
SelDateTime: new Date()
}
});
</script>
</body>
</html>
測試成功! 日期選擇器的初始值由 SelDate 及 SelDateTime 屬性決定,選取結果也會即時反映到 SelDate 及 SelDateTime 屬性上。
學會寫 Vue Component 後,我打算把一些常用的輸入欄位包成元件,未來再陸續分享。
Vue.js component turtorial and an example of KendoDatePicker warpper.
Comments
# by Milkker
這方法我有有用過,不過 component 一多會變很雜 後來是透過 partial view 管理每個 component 的註冊。
# by Jeffrey
to Milkker,是指把 View.component() 寫在 Partial View,一個元件一個 cshtml 嗎?
# by Lik
我的想法和黑大一樣。 後端產生的cshtml, 加上 vue 帶來的MVVM的好處。 但是,如果用vue.js 想要重用component的話,我看到一面倒的是 vue 單文件做法,一個component一個vue文件。 最終難免要進入到npm, vue/cli。然後被逼到了SPA。 然後就是這個SPA 怎麼和 asp.net mvc才能結合呢。。。看到網上大部分人的說法就是捨棄 razor cshtml,用 asp.net webapi + vue.js。 似乎路是偏離得原來想法越來越遠。
# by Milkker
對呀,讓每個組件有自己的 partial view _counter.chtml <script type="text/x-template" id="counterTemplate"> <span>{{ Count }}</span> </script> <script> Vue.component('counter', { template: '#counterTemplate', data: function() { return { Count: 0 }; } }); </script>