欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

實(shí)例詳解AngularJS實(shí)現(xiàn)無限級(jí)聯(lián)動(dòng)菜單

 更新時(shí)間:2016年01月15日 11:42:09   作者:ralph_zhu  
這篇文章主要介紹了實(shí)例詳解AngularJS實(shí)現(xiàn)無限級(jí)聯(lián)動(dòng)菜單的相關(guān)資料,需要的朋友可以參考下

多級(jí)聯(lián)動(dòng)菜單是常見的前端組件,比如省份-城市聯(lián)動(dòng)、高校-學(xué)院-專業(yè)聯(lián)動(dòng)等等。場景雖然常見,但仔細(xì)分析起來要實(shí)現(xiàn)一個(gè)通用的無限分級(jí)聯(lián)動(dòng)菜單卻不一定像想象的那么簡單。比如,我們需要考慮子菜單的加載是同步的還是異步的?對(duì)于初始值的回填發(fā)生在前端還是后端?如果異步加載,是否對(duì)于后端API的返回格式有嚴(yán)格的定義?是否容易實(shí)現(xiàn)同步、異步共存?是否可以靈活的支持各類依賴關(guān)系?菜單中是否有空值選項(xiàng)?……一系列的問題都需要精心處理。

帶著這些需求搜索了一圈,不太出乎意料,并沒有能在AngularJS的生態(tài)中找到一個(gè)很適合的插件或者指令。于是只好嘗試自己實(shí)現(xiàn)了一個(gè)。

本文的實(shí)現(xiàn)基于AngularJS,但是思路通用,熟悉其他框架類庫的同學(xué)也可以閱讀。

首先重新梳理了一下需求,由于AngularJS的渲染發(fā)生在前端,以前在后端根據(jù)已有值獲取各級(jí)菜單的option并在模板層進(jìn)行渲染的方案并不是很適合,而且和很多同學(xué)一樣,我個(gè)人并不喜歡這樣實(shí)現(xiàn)方式:很多時(shí)候,即使在后端完成了第一次對(duì)option選項(xiàng)的拉取和對(duì)初始值的回填,但由于子級(jí)菜單的加載依賴于api,前端也需要監(jiān)聽onchange事件并進(jìn)行ajax交互,換言之,一個(gè)簡單的二級(jí)聯(lián)動(dòng)菜單竟然需要把邏輯撕裂在前、后端,這樣的方式并不值得推崇。

關(guān)于同步、異步的加載方式,雖然大多數(shù)時(shí)候整個(gè)步驟是異步的,但是對(duì)于部分選項(xiàng)不多的聯(lián)動(dòng)菜單,也可以由一個(gè)api拉取所有數(shù)據(jù),進(jìn)行處理、緩存后供子級(jí)菜單渲染使用。因此同步、異步的渲染方式都應(yīng)該支持。

至于api返回格式的問題,如果正在進(jìn)行的是一個(gè)新的項(xiàng)目,或者后端程序員可以快速響應(yīng)需求變動(dòng),或者前端同學(xué)本身就是全棧,這個(gè)問題可能不那么重要;但是很多時(shí)候,我們交互的api已經(jīng)被項(xiàng)目的其他部分所使用,出于兼容性、穩(wěn)定性的考慮,調(diào)整json的格式并非是一個(gè)可以輕松做出的決定;因此在本文中,對(duì)于子級(jí)菜單option數(shù)據(jù)的獲取將從directive本身解耦出來,由具體業(yè)務(wù)邏輯處理。

那如何實(shí)現(xiàn)對(duì)靈活依賴關(guān)系的支持呢?除了最常見的線性依賴以外,也應(yīng)支持樹狀依賴、倒金字塔依賴甚至復(fù)雜的網(wǎng)狀依賴。由于這些業(yè)務(wù)場景的存在,將依賴關(guān)系硬編碼到邏輯較為復(fù)雜。經(jīng)過權(quán)衡,組件間將通過事件進(jìn)行通信。

需求整理如下:

* 支持在前端完成初始值回填
* 支持子集菜單選項(xiàng)的同步、異步獲取
* 支持菜單間靈活的依賴關(guān)系(比如線性依賴、樹狀依賴、倒金字塔依賴、網(wǎng)狀依賴)
* 支持菜單空值選項(xiàng)(option[value=""])
* 子集菜單的獲取邏輯從組件本身解耦
* 事件驅(qū)動(dòng),各級(jí)菜單在邏輯上相互獨(dú)立互不影響

由于多級(jí)聯(lián)動(dòng)菜單對(duì)于AngularJS中select標(biāo)簽的原有行為侵入性較大,為了之后編程方便,減少潛在沖突,本文將采用<option ng-repeat="item in items" value="{{item.value}}">{{item.text}}</optoin>的樸素方式,而非ngOptions。

1. 首先來思考第一個(gè)問題,如何在前端進(jìn)行初始值的回填

多級(jí)聯(lián)動(dòng)菜單最明顯的特點(diǎn)是,上一級(jí)菜單更改后,下一級(jí)菜單會(huì)被(同步或異步地)重新渲染。在回填值的過程中,我們需要逐級(jí)回填,無法在頁面加載時(shí)(或路由加載或組件加載等等)時(shí)瞬間完成該過程。尤其在AngularJS中,option的渲染過程應(yīng)該發(fā)生在ngModel的渲染之前,否則即使option中有對(duì)應(yīng)值,也會(huì)造成找不到匹配option的情況。
解決方案是在指令的link階段,首先保存model的初始值,并將其賦為空值(可以調(diào)用$setViewValue),并在渲染完成后再異步地對(duì)其賦回原值。

2. 如何解耦子選項(xiàng)獲取的具體邏輯,并同時(shí)支持同步、異步的方式

可以使用scope中的"="類屬性,將一個(gè)外部函數(shù)暴露到directive的link方法中。每次在執(zhí)行該方法后,判斷其是否為promise實(shí)例(或是否有then方法),根據(jù)判斷結(jié)果決定同步或異步渲染。通過這樣的解耦,使用者就可以在傳入的外部函數(shù)中輕松地決定渲染方式了。為了使回調(diào)函數(shù)不那么難看,我們還可以將同步返回也封裝為一個(gè)帶then方法的對(duì)象。如下所示:

// scope.source為外部函數(shù)
var returned = scope.source ? scope.source(values) : false;
!returned || (returned = returned.then ? returned : {
then: (function (data) {
return function (callback) {
callback.call(window, data);
};
})(returned)
}).then(function (items) {
// 對(duì)同步或異步返回的數(shù)據(jù)進(jìn)行統(tǒng)一處理
}

3. 如何實(shí)現(xiàn)菜單間基于事件的通信

大體上還是通過訂閱者模式實(shí)現(xiàn),需要在directive上聲明依賴;由于需要支持復(fù)雜的依賴關(guān)系,應(yīng)該支持一個(gè)子集菜單同時(shí)有多個(gè)依賴。這樣在任何一個(gè)所依賴的菜單變化時(shí),我們都可以通過如下方式進(jìn)行監(jiān)聽:

scope.$on('selectUpdate', function (e, data) {
// data.name是變化的菜單,dependents是當(dāng)前菜單所聲明的依賴數(shù)組
if ($.inArray(data.name, dependents) >= 0) {
onParentChange();
}
});
// 并且為了方便上文提到的source函數(shù)對(duì)于變動(dòng)值的調(diào)用,可以對(duì)所依賴的菜單進(jìn)行遍歷并保存當(dāng)前值
var values = {};
if (dependents) {
$.each(dependents, function (index, dependent) {
values[dependent] = selects[dependent].getValue();
});
}

4. 處理兩類過期問題

容易想到的是異步過期的問題:設(shè)想第一級(jí)菜單發(fā)生變化,觸發(fā)對(duì)第二級(jí)菜單內(nèi)容的拉取,但網(wǎng)速較慢,該過程需要3秒。1秒后用戶再次改變第一級(jí)菜單,再次觸發(fā)對(duì)第二級(jí)菜單內(nèi)容的拉取,此時(shí)網(wǎng)速較快,1秒后數(shù)據(jù)返回,第二級(jí)菜單重新渲染;但是1秒后,第一次請(qǐng)求的結(jié)果返回,第二級(jí)菜單再次被渲染,但事實(shí)上第一級(jí)菜單此后已經(jīng)發(fā)生過變化,內(nèi)容已經(jīng)過期,此次渲染是錯(cuò)誤的。我們可以用閉包進(jìn)行數(shù)據(jù)過期校驗(yàn)。
不容易想到的是同步過期(其實(shí)也是異步,只是未經(jīng)io交互,都是緩沖時(shí)間為0的timeout函數(shù))的問題,即由于事件隊(duì)列的存在,稍不謹(jǐn)慎就可能出現(xiàn)過期,代碼中會(huì)有相關(guān)注釋。

5. 支持空值選項(xiàng)的細(xì)節(jié)問題

對(duì)于空值的支持本來覺得是一個(gè)很簡單的問題,<option value="" ng-if="empty">{{empty}}</option>即可,但實(shí)際編碼中發(fā)現(xiàn),在directive的link中,由于此option的link過程并未開始,option標(biāo)簽被實(shí)際上移除,只剩下相關(guān)注釋占位。AngularJS認(rèn)為該select不含有空值選項(xiàng),于是報(bào)錯(cuò)。解決方案是棄用ng-if,使用ng-show。這二者的關(guān)系極其微妙有意思,有興趣的同學(xué)可以自己研究~

以上就是編碼過程中遇到的主要問題,歡迎交流~

directive('multiLevelSelect', ['$parse', '$timeout', function ($parse, $timeout) {
// 利用閉包,保存父級(jí)scope中的所有多級(jí)聯(lián)動(dòng)菜單,便于取值
var selects = {};
return {
restrict: 'CA',
scope: {
// 用于依賴聲明時(shí)指定父級(jí)標(biāo)簽
name: '@name',
// 依賴數(shù)組,逗號(hào)分割
dependents: '@dependents',
// 提供具體option值的函數(shù),在父級(jí)change時(shí)被調(diào)用,允許同步/異步的返回結(jié)果
// 無論同步還是異步,數(shù)據(jù)應(yīng)該是[{text: 'text', value: 'value'},]的結(jié)構(gòu)
source: '=source',
// 是否支持控制選項(xiàng),如果是,空值的標(biāo)簽是什么
empty: '@empty',
// 用于parse解析獲取model值(而非viewValue值)
modelName: '@ngModel'
},
template: ''
// 使用ng-show而非ng-if,原因上文已經(jīng)提到
+ '<option ng-show="empty" value="">{{empty}}</option>'
// 使用樸素的ng-repeat
+ '<option ng-repeat="item in items" value="{{item.value}}">{{item.text}}</option>',
require: 'ngModel',
link: function (scope, elem, attr, model) {
var dependents = scope.dependents ? scope.dependents.split(',') : false;
var parentScope = scope.$parent;
scope.name = scope.name || 'multi-select-' + Math.floor(Math.random() * 900000 + 100000);
// 將當(dāng)前菜單的getValue函數(shù)封裝起來,放在閉包中的selects對(duì)象中方便調(diào)用
selects[scope.name] = {
getValue: function () {
return $parse(scope.modelName)(parentScope);
}
};
// 保存初始值,原因上文已經(jīng)提到
var initValue = selects[scope.name].getValue();
var inited = !initValue;
model.$setViewValue('');
// 父級(jí)標(biāo)簽變化時(shí)被調(diào)用的回調(diào)函數(shù)
function onParentChange() {
var values = {};
// 獲取所有依賴的菜單的當(dāng)前值
if (dependents) {
$.each(dependents, function (index, dependent) {
values[dependent] = selects[dependent].getValue();
});
}
// 利用閉包判斷io造成的異步過期
(function (thenValues) {
// 調(diào)用source函數(shù),取新的option數(shù)據(jù)
var returned = scope.source ? scope.source(values) : false;
// 利用多層閉包,將同步結(jié)果包裝為有then方法的對(duì)象
!returned || (returned = returned.then ? returned : {
then: (function (data) {
return function (callback) {
callback.call(window, data);
};
})(returned)
}).then(function (items) {
// 防止由異步造成的過期
for (var name in thenValues) {
if (thenValues[name] !== selects[name].getValue()) {
return;
}
}
scope.items = items;
$timeout(function () {
// 防止由同步(嚴(yán)格的說也是異步,注意事件隊(duì)列)造成的過期
if (scope.items !== items) return;
// 如果有空值,選擇空值,否則選擇第一個(gè)選項(xiàng)
if (scope.empty) {
model.$setViewValue('');
} else {
model.$setViewValue(scope.items[0].value);
}
// 判斷恢復(fù)初始值的條件是否成熟
var initValueIncluded = !inited && (function () {
for (var i = 0; i < scope.items.length; i++) {
if (scope.items[i].value === initValue) {
return true;
}
}
return false;
})();
// 恢復(fù)初始值
if (initValueIncluded) {
inited = true;
model.$setViewValue(initValue);
}
model.$render();
});
});
})(values);
}
// 是否有依賴,如果沒有,直接觸發(fā)onParentChange以還原初始值
!dependents ? onParentChange() : scope.$on('selectUpdate', function (e, data) {
if ($.inArray(data.name, dependents) >= 0) {
onParentChange();
}
});
// 對(duì)當(dāng)前值進(jìn)行監(jiān)聽,發(fā)生變化時(shí)對(duì)其進(jìn)行廣播
parentScope.$watch(scope.modelName, function (newValue, oldValue) {
if (newValue || '' !== oldValue || '') {
scope.$root.$broadcast('selectUpdate', {
// 將變動(dòng)的菜單的name屬性廣播出去,便于依賴于它的菜單進(jìn)行識(shí)別
name: scope.name
});
}
});
}
};
}]);

相關(guān)文章

  • Angular實(shí)現(xiàn)雙向折疊列表組件的示例代碼

    Angular實(shí)現(xiàn)雙向折疊列表組件的示例代碼

    本篇文章主要介紹了Angular實(shí)現(xiàn)雙向折疊列表組件的示例代碼,分為左右兩組,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • AngularJS表單編輯提交功能實(shí)例

    AngularJS表單編輯提交功能實(shí)例

    這篇文章主要介紹了AngularJS表單編輯提交功能實(shí)例,本文講解如何修改表單的默認(rèn)值,需要的朋友可以參考下
    2015-02-02
  • Angular Material Icon使用詳解

    Angular Material Icon使用詳解

    這篇文章主要介紹了Angular Material Icon使用詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • Angular.js中上傳指令ng-upload的基本使用教程

    Angular.js中上傳指令ng-upload的基本使用教程

    這篇文章主要給大家介紹了關(guān)于Angular.js中上傳指令ng-upload的基本使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-07-07
  • AngularJS中重新加載當(dāng)前路由頁面的方法

    AngularJS中重新加載當(dāng)前路由頁面的方法

    下面小編就為大家分享一篇AngularJS中重新加載當(dāng)前路由頁面的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • AngularJS ui-router刷新子頁面路由的方法

    AngularJS ui-router刷新子頁面路由的方法

    這篇文章主要介紹了AngularJS ui-router刷新子頁面路由的方法,網(wǎng)上雖然有很多種方法,但是都不適合小編,今天小編給大家分享一個(gè)還不錯(cuò)的方法,需要的朋友可以參考下
    2018-07-07
  • AngularJS基礎(chǔ) ng-include 指令簡單示例

    AngularJS基礎(chǔ) ng-include 指令簡單示例

    本文主要介紹AngularJS ng-include 指令,這里對(duì)ng-include的基本知識(shí)做了整理,并附有代碼實(shí)例,有需要的朋友可以參考下
    2016-08-08
  • AngularJS基于ngInfiniteScroll實(shí)現(xiàn)下拉滾動(dòng)加載的方法

    AngularJS基于ngInfiniteScroll實(shí)現(xiàn)下拉滾動(dòng)加載的方法

    這篇文章主要介紹了AngularJS基于ngInfiniteScroll實(shí)現(xiàn)下拉滾動(dòng)加載的方法,結(jié)合實(shí)例形式分析AngularJS下拉滾動(dòng)插件ngInfiniteScroll的下載、功能、屬性及相關(guān)使用方法,需要的朋友可以參考下
    2016-12-12
  • angularjs創(chuàng)建彈出框?qū)崿F(xiàn)拖動(dòng)效果

    angularjs創(chuàng)建彈出框?qū)崿F(xiàn)拖動(dòng)效果

    這篇文章主要為大家詳細(xì)介紹了angularjs創(chuàng)建彈出框?qū)崿F(xiàn)拖動(dòng)效果的相關(guān)資料,angularjs modal模態(tài)框創(chuàng)建可拖動(dòng)的指令,感興趣的小伙伴們可以參考一下
    2016-01-01
  • 說說AngularJS中的$parse和$eval的用法

    說說AngularJS中的$parse和$eval的用法

    本篇文章主要介紹了說說AngularJS中的$parse和$eval的用法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09

最新評(píng)論