如何正确控制Angular指令的$ dirty和$ pristine状态

时间:2015-03-18 14:50:08

标签: javascript angularjs angularjs-directive kendo-ui

我正在尝试创建一个角度指令,该指令将组成一个KendoUI TreeView控件和一个文本输入,以便它搜索输入的文本并选择该项目。我有这个功能,但我希望该指令在表单中表现良好,以便在更改复选框值时,设置表单的脏属性。

不幸的是,刚才的行为是当有人在输入中输入文本时,from被设置为脏(不是所需的行为)并且检查复选框没有效果。

我希望在表单上调用$ setDirty(),但它对我所拥有的函数不可见,我希望有一种干净的方式让它可见而不是将它添加到$ scope,这也赢得了'解决了将$ dirty设置为true但输入搜索文本的问题。

我已经创建了一个Plunk来证明这个问题。

以下是该指令的代码:

var serviceRoot = "http://demos.telerik.com/kendo-ui/service";
 angular.module("KendoDemos", ["kendo.directives"])
   .controller("MyCtrl", function($scope, $http) {
     $scope.treeData = new kendo.data.HierarchicalDataSource({
       data: [{
         text: "Cat"
       }, {
         text: "Dog",
         items: [{
           text: "Fido"
         }, {
           text: "Rover"
         }]
       }, {
         text: "Rabbit",
         checked: true
       }]
     });
   });

 (function() {
   'use strict';


   var app = angular.module('KendoDemos');

   var template = '<div>	<div class="input-group">		<input type="text" ng-click="textNotFound=false"			   class="form-control" placeholder="Find node"			   ng-model="searchText" ng-enter="search()" ng-esc="searchText=\'\';textNotFound = false;">		<div class="input-group-btn">			<span class="btn btn-default" ng-click="search()"><span class="fa fa-search clickable"				style="font-size: 14px; height: 18px"></span></span>		</div>	</div>	<div id="treeview" kendo-tree-view="searchTree" k-data-source="dataSource" k-load-on-demand="false" k-on-check="onCheck(kendoEvent)"	      k-options="{checkboxes:true }">		<span k-template>{{dataItem.text}}</span>	</div></div>'

   app.directive('searchableTree', function() {
     //Usage:
     //<div data-searchable-tree ng-model="vm.treeData"></div>
     var directive = {
       template: template,
       require: '?^form',
       replace: true,
       transclude: true,
       scope: {
         'dataSource': '=ngModel',
         'controlId': '@id'
       },
       restrict: 'AE',
       link: function($scope, element, attrs, formCtrl) {
         $scope.search = function(id) {
           var tree = $scope.searchTree;
           var node = tree.findByText($scope.searchText);
           tree.expandTo($scope.searchTree.dataItem(node));
           tree.select(node);
           tree.dataItem(node).set("checked", true);
           //var checkbox = $(node).find(":checkbox");
           //checkbox.prop("checked", true);
         }
         $scope.setSelected = function(id) {
           alert(id);
         }
         $scope.onSelect = function(id) {
           alert(id);
         }
         $scope.onCheck = function(e) {
           var checkbox = $(e.node).find(":checkbox");
           var checked = checkbox.prop("checked");
           //updateValidity(e.node, checked);
         }
       }
     };
     return directive;
   });

   app.directive('ngEnter', function() {
     return function(scope, element, attrs) {
       element.bind("keydown keypress", function(event) {
         if (event.which === 13) {
           scope.$apply(function() {
             scope.$eval(attrs.ngEnter);
           });

           event.preventDefault();
         }
       });
     };
   });

   app.directive('ngEsc', function() {
     return function(scope, element, attrs) {
       element.bind("keydown keypress", function(event) {
         if (event.which === 27) {
           scope.$apply(function() {
             scope.$eval(attrs.ngEsc);
           });

           event.preventDefault();
         }
       });
     };
   });

 })();
/* Styles go here */

html {
  font-size: 12px;
  font-family: Arial, Helvetica, sans-serif;
}
#example {
  text-align: center;
}
.demo-section {
  display: inline-block;
  vertical-align: top;
  width: 320px;
  height: 300px;
  text-align: left;
  margin: 0 2em;
}
.clickable {
  cursor: pointer;
}
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.common.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.default.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.dataviz.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.dataviz.default.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2014.3.1411/styles/kendo.default.mobile.min.css" />
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">

<script src="http://cdn.kendostatic.com/2014.3.1411/js/jquery.min.js"></script>
<script src="http://cdn.kendostatic.com/2014.3.1411/js/angular.min.js"></script>
<script src="http://cdn.kendostatic.com/2014.3.1411/js/kendo.all.min.js"></script>

<body>
  <div id="example" ng-app="KendoDemos" ng-controller="MyCtrl">
    <form name='myForm'>
      <div name='myTree' data-searchable-tree ng-model="treeData"></div>
    </form>
    <hr/>

    <div>Directive is {{myForm.myTree.$dirty ? 'dirty' : 'pristine'}}</div>
    <hr/>
    <div>Form is {{myForm.$dirty ? 'dirty' : 'pristine'}}</div>
  </div>


</body>

2 个答案:

答案 0 :(得分:2)

2016年2月8日附录:自写这篇文章以来,我意识到范围方法仅在调试模式下可用。我不建议将其用于生产代码。

我遇到了类似的问题,实际上提出了一个非常简单的解决方案来解决它。如果您有对元素的引用,则可以使用scope()方法来查找它所存在的范围。鉴于此,此解决方案将起作用:

  function dirtyParent(uid)
    {
      var formEl = angular.element(angular.element('#' + uid).closest('form'));

      var formScope = formEl.scope();

      var formName = formEl.attr('name');

      formScope[formName].$setDirty();
    }

此函数从DOM获取并获取元素,查找其最新的祖先即表单,获取该元素的名称和范围,然后在作用域上找到表单控制器(与{共享其名称) {1}}表单的属性)并在其上调用name方法。这样,您的控制器将被设置为$ dirty状态。

看起来你可能可以在New Dev的答案中使用$ formController信息跳过DOM遍历,但是当我编写这段代码时我没有意识到。

答案 1 :(得分:1)

这里有一些事情,所以如果不详细说明你的特定指令,你应该理解以下内容:

<强> NG-模型:

您应该require: "ngModel"而不是将其用作范围变量。 ng-model是一个指令,通过它您可以正确地与其他表单和验证器集成。 ng-model故意与DOM无关,应该用作模型和视图值之间的概念管道。

如果你这样做,你就不需要require: "form"

您有自定义输入控件:

认识到您正在有效地构建自定义输入指令。它以某种方式设置模型,并以某种方式呈现UI。

从Angular文档中查看有关创建自定义输入指令的this example

在自定义指令的模板中使用ng-model:

现在,您看到$dirty的问题是因为您的指令(ng-model)中的ng-model="searchText"与外部表单集成,完全不知道它是另一个的一部分自定义输入控件。

ngModel指令搜索(参见src)获取DOM层次结构中的表单指令,最终找到指令所在的表单。

解决此问题的一种方法是不在模板中使用ng-model,并执行element.on('input')(或其他内容)来检测更改,并设置&#34;必需&#的视图值34; ngModel恰当。

另一个也是&#34;技巧&#34; ngModel认为表单控制器为空,因此不会更新它。

这很容易做到 - 但我无法评论这是否适合未来发展。在您的指令的pre-link中执行以下操作:

link: {
  pre: function(scope, element){
     // this will trip the search, and apply a `nullFormCtrl` internally, 
     // which doesn't do anything.
     element.data("$formController", null); 
  }
}