年末了愈來愈懶散,AngularJs的學習落了一段時間,博客最近也沒更新。慚愧~前段時間有試了一下用yeoman構建Angular項目,感受學的差很少了想作個項目練練手,誰知遇到了一系列問題。yeoman是基於node.js的一套工具包,因爲我一直在windows下編程,並且node.js對於windows環境的支持也在慢慢增強,因此想嘗試在windows下用yeoman跟搭建一個項目。過程遠比想象的坎坷多了,各類報錯,各類搜資料解決問題,最終仍是沒法解決一些編譯出錯,以失敗了結,轉戰Linux。在此也提醒你們若是想在windows下使用yeoman,仍是謹慎爲好!css
今天來學習一下一直被我忽視掉的表單驗證。ng的強項是開發CRUD應用,也就是與用戶操做多、交互比較頻繁的應用。表單是與用戶交互的一個重要角色,因此萬萬不能忽視。在學習以後發現這部分知識不只僅是想象中的那麼簡單,比起其餘特性,咱們一直不怎麼重視的表單驗證,其實也能夠作的很簡單,並且易維護。下面就開始吧~html
看這個小標題也行你會差別,表單驗證,怎麼跟controller扯上關係了。ng中的form已經不一樣於咱們平時用的form標籤,作了加強。form是FormController的一個實例。如何理解這句話呢?想一想咱們使用ng-controller指令的情景:node
<div ng-controller="testC"> <input type="test" ng-model="a" /> </div> <scritp> function testC($scope){ //............. } </script>
應用了ng-controller的div就是testC的一個實例,咱們能夠在模板中使用定義在$scopt上的任何屬性和方法,而testC的定義也是由咱們本身實現的。當咱們使用<form>的時候也是這樣的道理,FormController由ng爲咱們定義好了,有一系列屬性和方法提供給咱們完成驗證工做,form實例經過name屬性來進行標識,咱們能夠經過此標識來訪問form實例的屬性和方法,如:angularjs
<form name="myform"> {{myform.$valid}} </form>
form提供的屬性都是用來表示表單的驗證狀態的,包括:$pristine(表單沒有填寫記錄)、$dirty(表單有填寫記錄)、$valid(經過驗證)、$invalid(未經過驗證)、$error(驗證錯誤信息)。除$error外,前四個的值爲true或false表示相應的狀態。$error的值爲一個js對象,包含了如下驗證內容的狀態:編程
email
max
maxlength
min
minlength
number
pattern
required
url
這些內容咱們會在稍後的例子中看到。FormController還提供了一些方法,咱們通常不手工調用它們,都是系統本身調用。可參考官方文檔:http://docs.angularjs.org/api/ng.directive:form.FormControllerwindows
表單元素,如input、checkbox、radio等也不是普通的表單元素了,它們統統是NgModelController的實例。與form同樣,也是經過name屬性來標識。FormController擁有的那五個屬性,NgModelController也一樣擁有,除此以外,還有許多額外的屬性和方法,咱們稍後也在示例中展現,可參考官方文檔:http://docs.angularjs.org/api/ng.directive:ngModel.NgModelControllerapi
還有一個特性須要瞭解,一個表單中的表單元素,會做爲這個form的屬性自動加在上面,經過name標識就能夠訪問到,如:數組
<form name="myform"> <input type="text" name="myname" /> {{myform.myname.$valid}} </form>
ng框架提供了很是方便的驗證機制,你只須要在標籤上加點指令,像使用HTML5提供的驗證那樣,而後在css中根據規則定義好正確/錯誤的樣式就OK了,例如咱們要讓一個文本框爲必填項,使用required:瀏覽器
<form name="myform novalidate> <input type="text" ng-model="a" required /> </form>
有幾點須要注意:app
這部分仍是至關簡單的,下面咱們寫例子來測一下這幾種驗證機制,HTML代碼以下:
<div ng-app="MyApp"> <div ng-controller="testC"> <form name="myform" novalidate> required: <input type="text" name="test1" ng-model="test1" required><br /> ng-minlength(3): <input type="text" name="test2" ng-model="test2" ng-minlength="3"><br /> ng-maxlength(10): <input type="text" name="test3" ng-model="test3" ng-maxlength="10"><br /> ng-pattern(/[a-f]/): <input type="text" name="test4" ng-model="test4" ng-pattern="/[a-f]/"><br /> type="number"(2-8): <input type="number" name="test5" max="8" min="2" ng-model="test5"><br /> type="url": <input type="url" name="test6" ng-model="test6"><br /> type="email": <input type="email" name="test7" ng-model="test7"><br /> </form> <div> <h2>表單驗證結果:</h2> myform.$invalid : {{myform.$invalid}}<br /> myform.$valid : {{myform.$valid}}<br /> myform.$pristine : {{myform.$pristine}}<br /> myform.$dirty : {{myform.$dirty}}<br /> myform.$error : {{myform.$error}}<br /> <h2>表單項驗證結果</h2> required:<br /> myform.test1.$invalid : {{myform.test1.$invalid}}<br /> myform.test1.$valid : {{myform.test1.$valid}}<br /> myform.test1.$pristine : {{myform.test1.$pristine}}<br /> myform.test1.$dirty : {{myform.test1.$dirty}}<br /> myform.test1.$error : {{myform.test1.$error}}<br /> myform.test2.$error : {{myform.test2.$error}}<br /> </div> </div> </div>
CSS代碼,爲不一樣的狀態設置不一樣的背景色:
input.ng-pristine { background-color: white; } input.ng-dirty { background-color: lightyellow; } input.ng-valid { background-color: lightgreen; } input.ng-invalid { background-color: pink; }
js代碼,進行controller的初始化:
var app = angular.module('MyApp',[]); app.controller('testC',function($scope){ $scope.test1=''; $scope.test2=''; $scope.test3=''; $scope.test4=''; $scope.test5=''; $scope.test6=''; $scope.test7=''; });
結果以下:
該示例編寫在runjs上,點擊查看http://runjs.cn/code/gspvlfrw
在上面的代碼中,你也看到了我從FormConroller實例myform訪問到的屬性,還有從NgModelController訪問到的屬性。這些屬性是很是有用的,好比你能夠給表單的按鈕加上:ng-disabled="myform.$invalid",這樣在表單未經過驗證的時候,提交按鈕始終是不可點的。另外也能夠根據表單元素的這些屬性,來控制具體的錯誤提示信息,好比郵箱輸錯了,讓"請輸入正確的郵箱「這行字顯示出來,若是你順着個人思路,應該立馬能想象到。
除了內置的這些驗證規則,你還能夠本身定義。方法就是寫一個指令,加在表單元素上。聽起來好簡單的樣子,可是這指令與通常的指令可不一樣,咱們須要按必定的規則來寫,這樣才能夠融入ng的驗證機制,讓你自定義的跟內置的同樣能夠便捷的工做和管理。這個時候,NgModelController提供的方法就派上用場了。咱們從一個例子來開始吧。
我想讓個人輸入框只容許輸入偶數,咱們來定義一個名爲even-num的指令,在頁面上使用的時候像這樣:
<input type="number" ng-model="test1" even-num />
完整的js代碼以下:
var app = angular.module('MyApp',[]); app.controller('testC',function($scope){ $scope.test1 = ''; }); app.directive('evenNum',function(){ return { require: 'ngModel', link: function(scope, elm, attrs, ctrl) { ctrl.$parsers.push(function(viewValue) { if (viewValue % 2 == 0) { ctrl.$setValidity('evenNum', true); return viewValue; } else { ctrl.$setValidity('evenNum', false); return viewValue; } }); } }; });
運行結果:
上面的例子編寫在runjs上,點擊查看http://runjs.cn/code/gdq8m0gb
如今來解釋一下上面的代碼。自定義指令的方式若是你不熟悉,能夠先看一下我以前寫的自定義指令部分。由於咱們的指令要依賴NgModelController,因此寫上了require:'ngModel',注意書寫方式。 另外在link函數中,經過ctrl引用到了咱們注入的NgModelController,而後向它的$parsers屬性中push了一個函數進去。這個$parsers是什麼東西呢?很明顯它是一個數組,由於咱們能夠push東西進去。在解釋以前,咱們先清楚兩個概念:咱們把模板中的數據,像{{aa}}這樣的,叫作viewValue,故名思義,視圖中的數據。咱們把模型/controller中的數據,叫作modelValue。ng所說的雙向綁定,就是把這二者進行綁定。這個$parsers保存了從viewValue向modelValue綁定過程當中的處理函數,它們未來會依次執行。由於咱們的驗證是從用戶輸入開始,即view發生了變化,因此咱們的驗證邏輯就加在這裏。在驗證結果中,咱們調用ctrl.$setValidity方法,將結果保存,這樣框架就能完成接下來的一系列工做。
與$parsers相對的,還有一個屬性叫$formatters,它保存的是從modelValue向viewValue綁定過程當中的處理函數。那咱們定義的這個驗證函數,要不要也push進$formatters裏去呢?這取決於你的須要。若是你對二者的區別還不太清楚,看了下面這個例子就明白了:
上面的例子編寫在runjs上,點擊查看http://runjs.cn/code/9vde2r0w
兩個input的ng-model指向的是同一個,因此數據會同步變化,可是$formatters裏沒有push進去驗證函數,因此在從modelValue向ViewValue綁定的過程當中,副本並無進行驗證。若是把驗證函數push進$formatters,那麼副本也會跟着驗證了。
咱們都知道,在表單元素上使用ng-model能夠進行雙向綁定。可是雙向綁定只能用於input、checkbox這些標準表單控件上,你給一個div加ng-model是不能雙向綁定的,由於系統不知道該如何綁定。因此話說過來,要想給非標準表單控件雙向綁定,代碼還得本身來寫,說白了就是自定義一個指令。其實這部份內容放在自定義指令中也是合適的,可是官網在這裏提到了,我也來介紹一下。
咱們直接從例子開始,你們必定見過自適應的文本區域,就是隨着輸入內容的增長,會自動變高的textarea。用<textarea>標籤作的話,須要加js代碼才能夠實現。更好的是純css的方案,使用HTML5的新屬性contenteditable,用一個div來模擬文本區域,div的高度默認就是自適應的,正好能夠知足需求。基本的HTML代碼和css代碼以下:
<style> .smarttextarea{ width: 400px; min-height: 100px; max-height: 400px; border: 1px solid; overflow: auto; padding:5px 10px 20px; } </style> <div contenteditable=」true" class="smarttextarea"></div>
咱們如今要作的就是,讓這個模擬出來的文本區域跟真正的textarea那樣,能夠進行數據的雙向綁定,這樣就能夠進行驗證了。我定義了一個名爲smarttextarea的指令,使用起來像這樣:
<smarttextarea contenteditable="true" class="smarttextarea" ng-model="test3" required></smarttextarea>
指令的定義以下:
app.directive('smarttextarea',function(){ var link = function(scope, elm, attrs, ctrl) { //view=>model數據綁定 elm.bind('keyup', function() { scope.$apply(function() { ctrl.$setViewValue(elm.html()); }); }); //model=>view數據綁定 ctrl.$render = function() { elm.html(ctrl.$viewValue); }; ctrl.$setViewValue(elm.html()); }; return { template : '<div></div>', replace : true, require: 'ngModel', restrict: 'E', link : link }; });
看一下效果:
上面的例子編寫在runjs上,點擊查看http://runjs.cn/code/lhysp5vh
我給模擬出來的textarea加了required驗證,能夠發現生效了。其實關鍵代碼就是進行了數據的雙向綁定處理,包括兩步:
通過這兩步,咱們就自定義了一個跟標準表單控件同樣的元素,能夠進行數據的雙向綁定,表單驗證統統沒有問題。