帶你走近AngularJS系列:
------------------------------------------------------------------------------------------------
之前我們已經介紹了所有的AngularJS 基礎知識,下面讓我們通過實例來加深記憶,體驗自定義指令的樂趣。
手風琴指令
我們展示的第一個例子是手風琴效果指令:
效果圖如下:
在線實例地址:手風琴指令
不使用AngularJS的純HTML源碼如下:
<div class="accordion" id="accordion2"> <div class="accordion-group"> <div class="accordion-heading"> <a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseOne"> Collapsible Group Item #1 </a> </div> <div id="collapseOne" class="accordion-body collapse in"> <div class="accordion-inner"> Anim pariatur cliche... </div> </div> </div> <div class="accordion-group"> <div class="accordion-heading"> <a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseTwo"> Collapsible Group Item #2 </a> </div> <div id="collapseTwo" class="accordion-body collapse"> <div class="accordion-inner"> Anim pariatur cliche... </div> </div> </div> </div>
以上純 HTML源碼也可以實現手風琴效果,但是它僅僅是一些標記,包含了大量的鏈接和id,不利于維護。
使用AngularJS自定義指令結合以下HTML源碼同樣可以得到預期效果:
<body ng-app="btst"> <h3>BootStrap手風琴指令</h3> <btst-accordion> <btst-pane title="<b>基本功能</b>" category="{name:'test'}"> <div>AngularJS......</div> </btst-pane> <btst-pane title="<b>創建自定義指令</b>"> <div>使用過 AngularJS ......</div> </btst-pane> <btst-pane title="<b>體驗實例</b>"> <div>之前我們已經介紹了所有的AngularJS......</div> </btst-pane> </btst-accordion> </body>
這一版使用的HTML標記更少,看起來清晰且易維護。
下面,讓我們看下指令寫法。
首先,我們定義模塊"btstAccordion" 指令:
var btst = angular.module("btst", []); btst.directive("btstAccordion", function () { return { restrict: "E", transclude: true, replace: true, scope: {}, template: "<div class='accordion' ng-transclude></div>", link: function (scope, element, attrs) { // 確保 accordion擁有id var id = element.attr("id"); if (!id) { id = "btst-acc" + scope.$id; element.attr("id", id); } // set data-parent and href attributes on accordion-toggle elements var arr = element.find(".accordion-toggle"); for (var i = 0; i < arr.length; i++) { $(arr[i]).attr("data-parent", "#" + id); $(arr[i]).attr("href", "#" + id + "collapse" + i); } // set collapse attribute on accordion-body elements // and expand the first pane to start arr = element.find(".accordion-body"); $(arr[0]).addClass("in"); // expand first pane for (var i = 0; i < arr.length; i++) { $(arr[i]).attr("id", id + "collapse" + i); } }, controller: function () {} }; });
由于擁有內部HTML內容,所以設置指令的transclude 屬性為true。模板使用ng-transclude 指令來聲明對應的顯示內容。由于模板中只有一個元素,所以沒有設置其他選項。
代碼中最有趣的部分是link 方法。它在參數element具有id時啟作用,如果沒有,會依據指令的 Scope自動創建ID。一旦元素擁有了ID值,方法將通過jQuery來選擇具有"accordion-toggle"類的子元素并且設置它的 "data-parent" 和 "href" 屬性。最后,通過尋找“accordion-body” 元素,并且設置"collapse" 屬性。
指令同時聲明了一個擁有空方法的controller 。聲明controller 是必要的,因為Accordion會包含子元素,子元素將檢測父元素的類型和controller 。
下一步需要定義手風琴選項卡的指令。
這一步比較容易,大多數操作將在這個模板中發生,但是它僅僅需要少量的代碼:
btst.directive('btstPane', function () { return { require: "^btstAccordion", restrict: "E", transclude: true, replace: true, scope: { title: "@" }, template: "<div class='accordion-group'>" + " <div class='accordion-heading'>" + " <a class='accordion-toggle' data-toggle='collapse'>{{title}}</a>" + " </div>" + "<div class='accordion-body collapse'>" + " <div class='accordion-inner' ng-transclude></div>" + " </div>" + "</div>", link: function (scope, element, attrs) { scope.$watch("title", function () { // NOTE: this requires jQuery (jQLite won't do html) var hdr = element.find(".accordion-toggle"); hdr.html(scope.title); }); } }; });
require 屬性值為"btstPane" ,所以該指令必須用于指令"btstAccordion"中。transclude 屬性為true表明選項卡包含HTML標簽。scope 下的 "title" 屬性將會被實例所替代。
這個例子中的模板比較復雜。注意我們通過ng-transclude 指令來標記元素接收文本內容。
模板中"{{title}}" 屬性將會顯示標簽名稱。目前我們僅僅實現了純文本顯示,沒有定義其樣式。我們使用link 方法可以替換標題為HTML源碼從而得到更豐富的樣式。
就這樣,我們完成了第一個具有實用價值的指令。它功能并不復雜但是足以展示一些AngularJS的重要知識點和技術細節:如何定義嵌套指令,如何生成唯一的元素ID,如何使用jQuery操作DOM以及如何使用$watch 方法監聽scope變量的變化。
Google Maps 指令
下一個例子是創建Google地圖的指令:
在我們創建指令之前,我們需要添加Google APIs 引用到頁面中:
<!-- required to use Google maps -->
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?sensor=true">
</script>
接下來,我們創建指令:
var app = angular.module("app", []); app.directive("appMap", function () { return { restrict: "E", replace: true, template: "<div></div>", scope: { center: "=", // Center point on the map markers: "=", // Array of map markers width: "@", // Map width in pixels. height: "@", // Map height in pixels. zoom: "@", // Zoom level (from 1 to 25). mapTypeId: "@" // roadmap, satellite, hybrid, or terrain },
center 屬性進行了雙向綁定。這個應用可以改變地圖中心和交互地圖(當用戶通過鼠標按鈕選擇地圖位置時)。同時,地圖也會在用戶通過滾動選擇地圖位置時通知應用更新當前顯示位置。
markers 屬性被定義為引用因為它是數組形式,把它序列化為字符串比較耗時。link 方法可以實現以下功能:
1. 初始化地圖
2. 在用戶視圖變量更改時更新地圖
3. 監聽事件
以下是實現代碼:
link: function (scope, element, attrs) { var toResize, toCenter; var map; var currentMarkers; // listen to changes in scope variables and update the control var arr = ["width", "height", "markers", "mapTypeId"]; for (var i = 0, cnt = arr.length; i < arr.length; i++) { scope.$watch(arr[i], function () { if (--cnt <= 0) updateControl(); }); } // update zoom and center without re-creating the map scope.$watch("zoom", function () { if (map && scope.zoom) map.setZoom(scope.zoom * 1); }); scope.$watch("center", function () { if (map && scope.center) map.setCenter(getLocation(scope.center)); });
監測方法正如我們在文章開始時描述的,變量發生變化后,它將調用updateControl 方法。updateControl 方法實際上使用selected 選項創建了新的地圖。
"zoom" 和 "center" 變量將被分別處理,因為我們不希望每次在用戶選擇或縮放地圖時都重新創建地圖。這兩個方法檢測地圖是否重新創建還是僅僅是簡單的更新。
以下是updateControl 方法的實現方法:
// update the control function updateControl() { // get map options var options = { center: new google.maps.LatLng(40, -73), zoom: 6, mapTypeId: "roadmap" }; if (scope.center) options.center = getLocation(scope.center); if (scope.zoom) options.zoom = scope.zoom * 1; if (scope.mapTypeId) options.mapTypeId = scope.mapTypeId; // create the map and update the markers map = new google.maps.Map(element[0], options); updateMarkers(); // listen to changes in the center property and update the scope google.maps.event.addListener(map, 'center_changed', function () { if (toCenter) clearTimeout(toCenter); toCenter = setTimeout(function () { if (scope.center) { if (map.center.lat() != scope.center.lat || map.center.lng() != scope.center.lon) { scope.center = { lat: map.center.lat(), lon: map.center.lng() }; if (!scope.$$phase) scope.$apply("center"); } } }, 500); }
updateControl 方法首先需要接收Scope設置相關參數,接著使用options 創建和初始化地圖。這是創建JavaScript指令的常見模式。
創建地圖之后,方法會在更新標記的同時添加檢測事件,以便監視地圖中心位置的變化。該事件會監測當前的地圖中心是否和Scope中的相同。如果不同,即會更新scope,調用$apply 方法通知AngularJS屬性已經更改。這種綁定方式為雙向綁定。
updateMarkers 方法十分的簡單,幾乎和AngularJS分離,所以我們在這里就不介紹了。
除了這個地圖指令特有的功能,這個例子還展示了:
1. 兩個過濾器轉換坐標為常規數字到地理位置,例如33°38'24"N, 85°49'2"W。
2. 一個地理編碼器,轉換成地址的地理位置(也是基于谷歌的API)。
3. 使用HTML5的地理定位服務來獲取用戶當前位置的方法。
Google地圖 APIs 是極其豐富的。以下是一些資源入口:
Google地圖APIs 文檔: https://developers.google.com/maps/documentation/
Google許可條款:https://developers.google.com/maps/licensing
Wijmo Grid 指令
最后一個例子是可編輯的表格指令:
這里展示的圖表插件是 Wijmo 前端插件套包中的一款插件 wijgrid 插件:
<wij-grid data="data" allow-editing="true" after-cell-edit="cellEdited(e, args)" > <wij-grid-column binding="country" width="100" group="true"> </wij-grid-column> <wij-grid-column binding="product" width="140" > </wij-grid-column> <wij-grid-column binding="amount" width="100" format="c2" aggregate="sum" > </wij-grid-column> </wij-grid>
"wij-grid" 指令定制表格的屬性,"wij-grid-column" 指令定制特性表格列的屬性。以上標記定義了一個擁有三列的可編輯表格,分別為:“country”, "product" 和 "amount"。并且,以country列分組并且計算每個分組的合計。
這個指令中最特別的一點是 “wij-grid”和“wij-grid-column”的連接。為了使這個連接起作用,父指令中定義了如下controller:
app.directive("wijGrid", [ "$rootScope", "wijUtil", function ($rootScope, wijUtil) { return { restrict: "E", replace: true, transclude: true, template: "<table ng-transclude/>", scope: { data: "=", // List of items to bind to. allowEditing: "@", // Whether user can edit the grid. afterCellEdit: "&", // Event that fires after cell edits. allowWrapping: "@", // Whether text should wrap within cells. frozenColumns: "@" // Number of non-scrollable columns }, controller: ["$scope", function ($scope) { $scope.columns = []; this.addColumn = function (column) { $scope.columns.push(column); } }], link: function (scope, element, attrs) { // omitted for brevity, see full source here: // http://jsfiddle.net/Wijmo/jmp47/ } } }]);
關于controller 方法使用前文中提到的數組語法聲明,在這個例子中,controller定義了addColumn 方法,它將會被"wij-grid-column" 指令調用。父指令會通過特定標記來訪問列。
以下是"wij-grid-column" 指令的使用方法:
app.directive("wijGridColumn", function () { return { require: "^wijGrid", restrict: "E", replace: true, template: "<div></div>", scope: { binding: "@", // Property shown in this column. header: "@", // Column header content. format: "@", // Format used to display numeric values in this column. width: "@", // Column width in pixels. aggregate: "@", // Aggregate to display in group header rows. group: "@", // Whether items should be grouped by the values in this column. groupHeader: "@" // Text to display in the group header rows. }, link: function (scope, element, attrs, wijGrid) { wijGrid.addColumn(scope); } } });
require 成員用于指定"wij-grid-column" 指令的父級指令"wij-grid"。link 方法接收父指令的引用 (controller) ,同時通過addColumn 方法傳遞自身的scope 給父指令。scope 包含了表格用于創建列的所有信息。
更多指令
鏈接為一些AngularJS 指令的在線實例: http://wijmo.gcpowertools.com.cn/demo/AngularExplorer/ ,你可以在例子的基礎上進行練習。例子都是嚴格的安照本文中的描述制作的,所以你可以無障礙學習他們。
資源推薦:
1. AngularJS by Google AngularJS主頁。
2. AngularJS Directives documentation AngularJS 指令官方幫助文檔。
3. AngularJS directives and the computer science of JavaScript 比較實用的AngularJS指令說明文章。
4. Video Tutorial: AngularJS Fundamentals in 60-ish Minutes AngularJS 介紹視頻。
5. About those directives AngularJS 研發人員發布的視頻教程。
6. Egghead.io AngularJS 使用系列視頻教程。
7. Wijmo AngularJS Samples AngularJS 在線實例。
相關閱讀:
Wijmo已率先支持Angular4 & TypeScript 2.2
文章列表