回顧
上一篇講解了引入bootstrap構建一個簡單的登錄頁面,如何讓angularjs自動啟動並綁定視圖,操作過程當中如何使用ui-bootstrap,繼而完成簡單功能后如何引入seajs后如何使ng手動啟動。我會盡量把自己在學習當中遇到的問題以及如何解決分享給各位,如果大家有什么疑問或者想要達到的功能可以告訴我,我可以嘗試去把效果做出來然后再下一篇文章分享給各位。
需要解決的問題
1、實現ajax form
2、構建菜單
實現ajax form
首先給$scope定義一個formData的對象,然后將formData內的屬性綁定到對應的input上,html代碼如下:
//html代碼 <input type="text" class="form-control input-lg" placeholder="用戶名" required autofocus ng-model="formData.name" /> <input type="password" class="form-control input-lg" placeholder="密碼" required ng-model="formData.password" /> //js代碼 $scope.formData = {};//這里可以不定義對應的name和password,也可以為{ name: '', password: '' }
完成綁定以后,當我么對input進行操作的時候,變化的值就會同步到formData中,反過來也是一樣的。
接下來要使用一個類似jQuery.ajax的函數,在ng中叫做$http(nodejs中也是http),對於這個對象詳細的介紹可以查看API,它一樣提供了名為post的函數來實現ajax的交互,我們可以使用原來的ngClick方法來實現ajax,但是這樣便沒有辦法讓表單進行驗證了,因此這里使用ngSubmit綁定在form表單上,然后將登錄按鈕類型改為submit,那么當表單有效的情況下,點擊按鈕就會觸發ngSubmit的函數了,然后將原來的alerts刪除,並加上根據后台返回的代碼顯示不同信息的提示信息,代碼如下:
<form name="frmLogin" class="col-sm-offset-4 col-sm-4" role="form" ng-controller="HomeLoginController" ng-submit="submit()"> <input type="text" class="form-control input-lg" placeholder="用戶名" required autofocus ng-model="formData.name" /> <input type="password" class="form-control input-lg" placeholder="密碼" required ng-model="formData.password" /> <button class="btn-lg btn-primary btn-block" type="submit">登錄</button> <span class="text-danger" ng-show="responseCode" ng-bind="responseCode == 4 ? '賬號被禁用!' : '帳號密碼有誤!'"></span> </form>
ngModel和ngBind的區別:ngModel用來綁定input、select、textarea等form表單控件,ngBind是綁定文本內容的(不包括html,如果要綁定html需要使用ngBindHtml),這里大家會發現表述的時候使用的是ngSubmit、ngClick、ngModel等(這些都是ng定義的指令)但是在html中卻寫成了ng-submit、ng-click等,這是ng的一個規則(當我們自己定義指令的時候也是一樣的規則,如menuContent在html內使用的時候要寫成menu-content),跟jQuery的css是一樣的,其次這里出現的ngShow指令是指當指定的值或表達式為真的情況下,該元素是顯示的,否則隱藏,跟它對應的屬性是ngHide,其次ngBind綁定的值也同樣可以是值也可以是表達式(大部分指令的名字都是很容易理解的)。
然后改造submit函數來實現ajax form,但是這里需要注意的是ng提供的ajax跟jQuery的ajax有一些區別,需要注意如下兩點:
- data參數是不能像jQuery那樣直接使用json對象的(jQuery內部會將其轉化成QueryString,即a=b&c=d的格式),如果使用json對象后台將無法取到表單內的任何值
- headers必須指定'Content-Type': 'application/x-www-form-urlencoded',否則無法識別為表單數據,跟上一點一樣的結果
由於ng沒有提供將對象轉化為QueryString格式的函數,但是jQuery提供了,因此可以使用jQuery.Param,但是ng提供的ajax的優點是,如果返回的是json字符串,它會默認轉成json對象,代碼如下:
$scope.submit = function () {
$http.post('/mvc/Home/Login', $.param($scope.formData), {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).success(function (data) {
$scope.responseCode = data.code;
//1s后讓錯誤消息隱藏
$timeout(function () {
$scope.responseCode = 0;
}, 1000);
});
};
這樣實現了ng版的ajax form了,相對於jQuery.form的方式,ng版本的代碼多一些但是只要抽取一個庫就簡單多了。
構建菜單
當成功登錄后,用戶菜單是必不可少的,但是UI組件並沒有提供tree,因此這里需要我們自己去實現了,這里就以一個比較簡單的樹作為此次實現的目標吧,如圖:

要實現以上的tree首先要認識ng的幾個指令(directive),首先是第一篇用到的ngRepeat,這個指令簡單的說就是對包含的HTML以模板的方式進行循環,循環體則為指定的數據源,代碼如下:
<ul ng-repeat="m in list">
<li ng-bind="m.name"></li>
</ul>
list指向Controller內的$scope.list,而m in list類似於foreach(var m in list)。但是使用以上的代碼得到的卻只是根節點而已,如果才能遍歷節點的時候也遍歷它的子節點呢?
由於ng提供給的指令無法實現以上的要求,那么只有自己自定義指令去完成這個功能了,自定義指令要在注冊模塊的時候調用directive來注冊自定義的指令(官方API好不容易找到該方法的說明卻在$compile內),注意命名依然是camelCase命名法,按照最初的想法在第一層ngRepeat內再嵌入自定義的指令並將數據指向children,代碼如下:
//html
<ul>
<es-tree data="list"><es-tree>
</ul>
//js
angular.module('es.tree', []).directive('esTree', function () {
return {
replace: true,
restrict: 'E',
scope: {
data: '='
},
template: [
'<li ng-repeat="m in data">',
' <a href="#" ng-bind="m.name"></a>',
' <ul>',
' <es-tree data="m.children"></es-tree>',
' </ul>',
'</li>',
].join('')
};
});
以上directive方法返回對象的屬性說明如下:
replace:表示template或templateUrl內的html是否替換指令元素(<es-tree>)
restrict:表示如何識別該指令(E:元素名 A:屬性 C:class M:注釋)
scope:作用域內的對象包含的屬性或方法,key是屬性名或方法名,value:必須的幾個值(@:將屬性內的文本值賦予key =:獲取屬性內對應的對象賦予key &:將屬性內表達式的值賦予key),如果value的值加入了其他字符串如=dataSource則在使用的時候要變成<es-tree data-source="數據源字段">
template:該指令所編譯成的html
其余的說明清查看API如果有不清楚的,可以給我留言,^_^。template使用數組只是為了排版好看而已,沒有其他意思。
但是我們用以上的結構后,會出現RangeError: Maximum call stack size exceeded的異常,這是由於編譯過程當中並不會判斷children是否有效才進行生成的,因此就成了一個無限循環了。那么只能再將節點的生成拆分出另一個指令,然后再內部進行判斷,當存在子節點的時候生成子節點,不存在的情況下只生成<a>的內容,代碼如下:
//esTree
template: [
'<li ng-repeat="m in data">',
' <es-tree-node data="m"></es-tree-node>',
'</li>',
].join('')
//節點指令
directive('esTreeNode', function ($compile) {
return {
link: function ($scope, $element, $attrs) {
var data = $scope.data;
var template = ['<a href="#" ng-bind="data.name"></a>'];
if (data.children && data.children.length) {
template.push(
'<ul>',
' <es-tree data="data.children"></es-tree>',
'</ul>');
}
var html = $compile(template.join(''))($scope);
$element.replaceWith(html);
},
restrict: 'E',
scope: {
data: '='
}
};
});
這次終於達到所要的效果了,那么怎么樣實現點擊一個節點的時候這個節點被選中而且是只能單個節點被選中呢,其實這個還是很簡單的,只需要給<a>部分添加一個ngClick的事件,並且ngClass綁定一個bool的字段,然后在Controller內進行控制就可以了,代碼如下:
//其他省略
//esTree
<a href="#" ng-class="{ selected: data.selected }" ng-bind="data.name" ng-click="select(data)"></a>
//controller
controller('esTreeController', function ($scope) {
$scope.select = function (m) {
if ($scope.$root.selectedNode)
$scope.$root.selectedNode.selected = 0;
m.selected = 1;
$scope.$root.selectedNode = m;
};
})
如果要綁定某些事件呢,類似於easyui提供的onSelect這樣的函數呢,其實原理跟data是一樣的,那么這里就不再闡述了,文章就到這里了,如果有什么錯誤請支持,謝謝!
