2016年8月13日 星期六

自製檢查file size的directive (input type="file") - AngularJs

AngularJS的表單、欄位驗證(Validation)非常好用,但有時會碰到想要自訂驗證方式、
或是某個欄位AngularJS並沒有實作Validation時(例如input type="file"),
就需要自訂有客製化驗證能力的directive

在這邊的需求如下:

  1. 製作一個directive,名稱為file-validator
  2. 配合input type="file"使用
  3. 可以設定file size(大小,單位Byte)和file type(副檔名), 用法範例:
  4. 如果fileSize沒指定或小於等於0,則不對fileSize做限制驗證。
  5. 如果fileType沒指定或為空字串,則不對fileType做限制驗證。
完成的程式碼成品如下:


說明:

  1. 當我們在ng-form裡面的input設置name及ng-model (及ngModelController)後,如果AngularJS有實作此種input type類型的話,Angular會在View中的值或對應model的
    值改變時,進行View及model的雙向挷定、同時變更。
    ngModelController裡面會存放model的值,即ngModel.$modelValue。
    也會存放view(通常為input type中顯示的值),即ngModel.$viewValue。
    並且也會管理此input的valid狀態。

    但因為AngularJS並沒有對input type="file"進行實作,也就是,不管User在input中選了什麼檔案,皆不會存值到ngModel.$modelValue及ngModel.$viewValue中,當然雙向挷定的model中也不會有值。

    所以為們要帶自制的directive中,指定
    required : ngModel
    來得到管理這個input的ngModel,並且手動的設定model的值。
    (!!不要手動用ngModel.$setViewValue()設定viewValue的值,會導致model value 會被蓋掉)
  2. 因為要設定model的值,所以我們要引入$parse,$parse(code)可以代入一串程式碼,有點
    像eval,$parse(code)會返回一個函式,並且此函式有一個函式,assign(scope, value),
    可以在scope中執行程式碼,並將value賦值給程式碼執行得到的變數。
    例如:
    $parse("myCtrl.fileUpload").assign(scope, file);
    代表在scope中執行 :  myCtrl.fileUpload = file;
  3. 在ngModel中,可以使用
    ngModel.$setValidity( validType, isValid)
    來設定此input的validType是valid還是invalid。
  4. 在解析如以下字串時,
    file-validator="fileType:'png'; fileSize:333;"
    我們使用了兩個正規表達式:
    fileSize\s*:\s*(\d+)\s;

    fileType\s*:\s*(["'])(\w+)\1\s*;
    其中第二個正規達式中的 \1 的意思是跟第一個Group相匹配,而第一個Group就是(["']),
    也就是fileType:"png"; 或 fileType: 'png';可以匹配,但
                fileType:'png";  或 fileType:"png'; 不可以匹配,
    雙引號及單引號要成對使用。

原碼紀錄(怕jsFiddle出問題):
使用angular.js
html:

請選擇size小於3MB的PNG檔案


file valid: {{myForm.fileUpload.$valid}}
file size (Byte): {{myCtrl.fileUpload.size}}
副檔名錯誤
檔案過大


javascript:
 var myApp = angular.module("myApp", []);

 myApp.controller("myController", [function() {
   //
 }]);
 //自製的file validator
 myApp.directive('fileValidator', ["$parse", function($parse) {
   return {
     restrict: 'A',
     scope: true,
     require: 'ngModel', //代表管理input type="file"這個input的ngModelController,例如此例
     //ngModel.name = "fileUpload",
     //可以連結ng-model裡指定的變數值(ngModel.modelValue) 和
     //input view欄位中顯示的值(ngModel.viewValue)                          
     link: function(scope, elm, attrs, ngModel) {
       var expression = attrs.fileValidator;

       var fileSizeReg = /fileSize:\s*(\d+)\s*;/;
       var fileTypeReg = /fileType\s*:\s*(["'])(\w+)\1\s*;/;

       //規定file的size,單位Byte
       var fileSizeLimit = fileSizeReg.exec(expression)[1] || 0;
       //規定副檔名
       var fileTypeLimit = fileTypeReg.exec(expression)[2] || "";

       elm.bind('change', function() { //發現input type file值改變時
         scope.$apply(function() {
           var file = elm[0].files[0]; //取得file資料
           var fileType = /.+\.(.+)/.exec(file.name)[1];

           //$parse("程式碼")可以返回一個function,之後可以用這個function的
           //assign(scope, value)來在scope中進行賦值(value)動作,
           //例如: 在scope中墸行: 程式碼 = value (有點像eval())
           $parse(attrs.ngModel).assign(scope, file);

           //檢查file副檔名及size
           //用ngModel設置fileSize的valid
           if (fileTypeLimit === "" || fileType === fileTypeLimit) {
             ngModel.$setValidity("fileType", true);
           } else {
             ngModel.$setValidity("fileType", false);
           }
           //用ngModel設置fileSize的invalid
           if (fileSizeLimit <= 0 || file.size < fileSizeLimit) {
             ngModel.$setValidity("fileSize", true);
           } else {
             ngModel.$setValidity("fileSize", false);
           }
         });
       });
     }
   };
 }]);

沒有留言 :

張貼留言