Magicodes.WeiChat——自定义knockoutjs template、component实现微信自定义菜单
2019獨角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
本人一向比較喜歡折騰,玩了這么久的knockoutjs,總覺得不夠勁,于是又開始準(zhǔn)備折騰自己了。
最近在完善Magicodes.WeiChat微信開發(fā)框架時,發(fā)現(xiàn)之前做的自定義菜單這塊太不給力了,而各種第三方平臺在這一塊做得也比較渣,功能不全不說,界面還很不友好,于是決心重整一版,以滿足需求。
下面先上圖,新的UI界面如下所示:
?
如何實現(xiàn)這個功能呢?下面請等我一一道來吧。
左側(cè)樹形結(jié)構(gòu)綁定
HTML模板如下所示:
<div class="dd" id="nestable2"><ol class="dd-list" data-bind="foreach:Menus()"><li class="dd-item lv1"><div class="dd-handle"><span class="pull-right"><i class="fa fa-plus" data-bind="click:$root.AddClick"></i> <i class="fa fa-times" data-bind="click:$root.RemoveItem"></i> <i class="fa fa-pencil" data-bind="click:$root.ItemClick"></i></span><span><span class="label label-info"><i class="fa" data-bind="css:$root.getIconCssByType(type)"></i></span><span data-bind="text:name,click:$root.ItemClick"></span></span></div><!-- ko if:$data.sub_button !== undefined --><ol class="dd-list" data-bind="foreach:$data.sub_button"><li class="dd-item lv2" data-id="2"><div class="dd-handle"><span class="pull-right"><i class="fa fa-times" data-bind="click:$root.RemoveItem"></i> <i class="fa fa-pencil" data-bind="click:$root.ItemClick"></i></span><span class="label label-success"><i class="fa" data-bind="css:$root.getIconCssByType(type)"></i></span> <span data-bind="text:name"></span></div></li></ol><!-- /ko --></li></ol> </div>這里我解釋一下,上述模板用到了兩個foreach循環(huán),以便綁定這個兩級列表。實際上如果數(shù)據(jù)結(jié)構(gòu)支持的話,ko是可以遞歸的綁定的。ko的強(qiáng)大性是毋庸置疑的。然后注意這個注釋:“<!-- ko if:$data.sub_button !== undefined? -->”,這個真的不是注釋,這個是有用的。為了不產(chǎn)生臟元素,ko支持這種綁定寫法。這里先用if做了判斷,然后再綁定子集。其余的,就是簡單的data-bind語法了。
通過上述模板,我們注意到數(shù)據(jù)結(jié)構(gòu)中兩個關(guān)鍵點:Menus和sub_button,那我們就來看看viewModel。viewModel中定義了Menus = ko.observableArray([]),然后使用Ajax獲取數(shù)據(jù)來填充:
//初始化,加載數(shù)據(jù)this.Init = function () {mwc.ui.setBusy();self.Api.request('GET', {url: '/api/Menus',func: function (data) {mwc.ui.clearBusy();$.each(data, function (i, v) {if (v.sub_button) {$.each(v.sub_button, function (i1, v1) {v.sub_button[i1] = $.extend(self.getModelTpl(), v1);})}data[i] = $.extend(self.getModelTpl(), v);});self.Menus(ko.mapping.fromJS(data));}});};注意,因為方便,這里使用了knockout.mapping js,請注意ko.mapping.fromJS方法。
右側(cè)編輯模板綁定
這塊無疑是比較復(fù)雜的一塊,我們先進(jìn)行肢解:
我們先來看看整體的編輯模板:
<div class="ibox-title"><h5>按鈕其他參數(shù) </h5></div><div class="ibox-content" data-bind="with:EditModel" style="min-height: 600px;"><form class="form-horizontal"><!-- ko if:type() != 'empty' --><buttonschoices params="SelectsModel: $root.SelectTypes,SelectValue:type"></buttonschoices><div class="hr-line-dashed"></div><div class="form-group"><label class="col-sm-2 control-label">名稱</label><div class="col-sm-10"><input type="text" class="form-control" data-bind="value:name" required></div></div><div class="hr-line-dashed"></div><!-- /ko --><div data-bind="template:{name:$root.GetEditTemplateName,data:$root.EditModel,afterRender:$root.afterEditTemplateRender}"></div><!-- ko if:type() != 'empty' --><div><button class="btn btn-primary pull-right" type="button" data-bind="click:$root.Save"><i class="fa fa-save"></i><strong>保存</strong></button></div><!-- /ko --></form></div>由模板可知,整個編輯模塊由類型按鈕組、名稱框、動態(tài)模板、保存按鈕組成。接下來我就先介紹下類型按鈕組的定義與綁定:
類型按鈕組——knockout component
如上述代碼中,使用了html標(biāo)簽buttonschoices。而這個標(biāo)簽就是我定義的knockout compoent。使用knockout compoent能做什么呢?就如上述代碼中,我們可以知道以下幾點:
- 返回HTML模板
- 傳遞參數(shù),綁定compoent ViewModel
那么封裝knockout compoent,有助于我們封裝一些通用UI組件,就比如按鈕組類型選擇。我們先來一覽代碼:
//按鈕組選擇組件 ko.components.register('buttonschoices', {viewModel: function (params) {var self = this;//所選值this.SelectValue = ko.observable();//text:文本//value:值//icon:圖標(biāo)//des:描述this.SelectItem = ko.observable({ text: "", value: "", icon: "", des: "" });//選擇模型this.SelectsModel = ko.observableArray([]);if (params && typeof (params.SelectsModel()) != "undefined") {self.SelectsModel(params.SelectsModel());if (typeof (params.SelectValue()) != "undefined") {self.SelectValue(params.SelectValue());self.SelectItem($.grep(self.SelectsModel(), function (v, i) { return v.value == self.SelectValue() })[0]);}}this.GetActiveCss = function (item) {return item.value == self.SelectValue() ? "active btn-primary" : "";}this.buttonClick = function (item) {self.SelectValue(item.value);self.SelectItem(item);params.SelectValue(item.value);}},template: '<div class="btn-group" data-bind="foreach: SelectsModel">' +'<button class="btn btn-white" data-bind="css:$parent.GetActiveCss($data),click:$parent.buttonClick"><i class="fa" data-bind="css:icon"></i> <span data-bind="text:text"></span></button>' +'</div>' +'<div class="well" data-bind="with:SelectItem">' +'<span data-bind="text:des"></span>' +'</div>' });整個組件代碼很簡潔明了,通過ko.components.register注冊組件,buttonschoices為組件名稱,整個組件由兩部分組成:
- viewModel:視圖模型
- template:模板
其中,viewModel接收了傳入?yún)?shù),并且進(jìn)行了處理。我們來依次解析這個viewModel:
- SelectValue:所選指。這個所選指會根據(jù)傳入?yún)?shù)(還記得前面的“<buttonschoices params="SelectsModel: $root.SelectTypes,SelectValue:type"></buttonschoices>”嗎,其中SelectValue:type就是傳入了參數(shù)SelectValue)進(jìn)行賦值,如右側(cè)代碼:self.SelectValue(params.SelectValue())。
- SelectItem:所選項。項結(jié)構(gòu)為{ text: "", value: "", icon: "", des: "" },分別代表文本、值、圖標(biāo)和描述。
- SelectsModel:選擇模型,就是列表模型。有多少個按鈕,就看其有多少個項了。傳入?yún)?shù)見“SelectsModel: $root.SelectTypes”。我們來看看這個$root.SelectTypes是怎么定義的:
眾所周知,微信自定義菜單支持10中類型的按鈕,那么這里是其類型的定義。這也說明,這個按鈕組是完全通用的,你只要給予與上述結(jié)構(gòu)一致的數(shù)據(jù),其就能顯示成當(dāng)前效果。
- GetActiveCss:獲取當(dāng)前所選樣式。選中返回選中樣式,否則返回空。
- buttonClick:按鈕點擊事件,這里拿到的是數(shù)據(jù)項,ko就是這么方便。然后值得注意的是,參數(shù)是雙向的,我們可以利用“params.SelectValue(item.value);”來回寫值,這樣編輯模型的類型值才會產(chǎn)生改變。
viewModel很簡單,template也很簡單,就是將剛才所說的viewModel綁定,用到了BootStrap按鈕組樣式“btn-group”,用foreach綁定SelectsModel,然后逐個綁定。
注意:
$parent表示父級對象,即乃父,因為foreach之后,其實對象已經(jīng)指定到了乃父的兒子(SelectsModel)的某個兒子($data)上,而GetActiveCss是viewModel的女兒,自然要通過乃父來獲取了,畢竟其乃父的兒子的子孫并不是她。
$data表示當(dāng)前項,即乃父的兒子的某個兒子,用于循環(huán)中獲取當(dāng)前項數(shù)據(jù)。
with類似于using命名空間一樣,用了它,下面的元素都可以省卻改命名空間了。
是不是很簡單的樣子。我們再來說說模板:
動態(tài)加載模板
首先,我們先聚焦到以下代碼:
<div data-bind="template:{name:$root.GetEditTemplateName,data:$root.EditModel,afterRender:$root.afterEditTemplateRender}"></div>首先我們得明確以下內(nèi)容:
template語法用于綁定模板,其中name用于指定模板名稱,這里綁定了$root.GetEditTemplateName方法,data用于指定模板的viewModel。
然后我們再來看看GetEditTemplateName怎么回事?如下所示:
//根據(jù)類型獲取編輯模板this.GetEditTemplateName = function (data) {switch (data.type()) {case "empty":return "emptyTemplate";case "media_id":case "view_limited":return "media_idTemplate";case "view":return "urlTemplate"default:return "keyTemplate";}};看起來也蠻簡單的樣子,就返回了一個模板名稱,那我們再繼續(xù)來看看這些模板。
<script id="emptyTemplate" type="text/html"><div class="well"><h3>注意事項:</h3> 創(chuàng)建自定義菜單后,由于微信客戶端緩存,需要24小時微信客戶端才會展現(xiàn)出來。測試時可以嘗試取消關(guān)注公眾賬號后再次關(guān)注,則可以看到創(chuàng)建后的效果。</div> </script> <script id="keyTemplate" type="text/html"><div class="form-group" id="buttonDetails_url_area"><label class="col-sm-2 control-label">關(guān)鍵字</label><div class="col-sm-10"><input type="text" class="form-control" data-bind="value:key" /></div></div> </script><script id="urlTemplate" type="text/html"><div class="form-group" id="buttonDetails_url_area"><label class="col-sm-2 control-label">鏈接</label><div class="col-sm-10"><input type="url" class="form-control" data-bind="value:url" /></div></div> </script> <script id="media_idTemplate" type="text/html"><news-choice-button params="value: media_id"></news-choice-button> </script> <div data-bind="with:EditModel"><news-choice-modal params="value: media_id"></news-choice-modal> </div>模板的定義也蠻簡單的,id和上面的字符串是一致的,類型必須為text/html。上面模板分別為空模板,關(guān)鍵字模板,鏈接模板和素材模板。
其中素材模板里面使用了自定義的component,和之前的buttonschoices一樣,封裝了多圖文選擇代碼。
由于組件news-choice-button和news-choice-modal需要講解的篇幅比較長,這里就暫不介紹了。
至于增刪改查,對于ko來說,都是操作數(shù)據(jù)模型。比如左側(cè)樹形結(jié)構(gòu)的增刪,則是對Menus數(shù)組的增減操作,而編輯,則需要更新數(shù)組中的數(shù)據(jù)項。viewModel的修改,ko會自動重繪UI。這里就不多介紹了。
總結(jié)
通過使用knockoutjs 的動態(tài)模板,我們可以很方便的根據(jù)需要加載不同的模板進(jìn)行綁定顯示。而通過knockoutjs component的封裝,我們可以很方便的實現(xiàn)對業(yè)務(wù)或者通用UI組件的封裝,以達(dá)到重復(fù)使用的目的。
轉(zhuǎn)載于:https://my.oschina.net/u/3714286/blog/1555571
總結(jié)
以上是生活随笔為你收集整理的Magicodes.WeiChat——自定义knockoutjs template、component实现微信自定义菜单的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VUE -- Mac上解决Chrome浏
- 下一篇: TransactionScope 的基本