是否有一种简单的方法可以使用angularjs只读取整个DOM子树?

时间:2014-07-19 23:45:46

标签: angularjs readonly

我有一些input s,当某些属性发生变化时(例如,购物车被检出),所有这些都应该变为readonly

我可以为每个这样的输入添加ng-readonly=cart.checkedOut,这很无聊,容易遗忘,并且因为有部分人无权访问cart这一事实而变得复杂

所以我正在考虑修改input(以及textareaselect,它们只是错误的输入),以便它在其DOM的祖先中查找类{{ 1}}并且可能使自己只读。我的问题是与readonly的互动,这可能会导致ng-readonly只读用于其他原因。看起来我必须修改input,但AFAIK无法修改现有指令,只能为同一个名称添加其他行为。

不知何故,我感到迷茫;我确信这是一个简单的解决方案,但随着我的潜入,一切都变得复杂起来。向我展示正确的方向就足够了。或者可能是针对原始问题的不同解决方案。

3 个答案:

答案 0 :(得分:0)

请注意,对于多个表单输入元素,将忽略readonly属性。

因此,ng-readonly可能无法正常使用某些组件(即,它不会阻止用户交互编辑值)。

也就是说,解决问题的一种方法是制作一个监视签出状态的指令,并动态添加或删除适当的输入元素属性,使它们看起来被锁定以进行编辑。

就像我上面提到的,您需要关注的属性取决于您使用的元素类型。对于readonly不适用的用户,例如select,您可以选择使用disable。但是,无法提交已禁用的元素,这就是hidden输入类型通常用于补充select元素的原因。在您的情况下,它似乎也是不受欢迎的,因为您希望不要触摸您的DOM。

要仍然使用disable并解决后一个问题,在提交表单时,您可以制作另一个指令,从具有它们的输入元素中去除所有disabled属性,确保所有输入数据得到提交。

这是JSBin关于如何动态管理输入元素属性的基本演示。

答案 1 :(得分:0)

这是我制作的解决方案,它扩展了inputselecttextarea指令。

在我们的应用程序的配置块中使用$provide.decorator,我们可以在不触及其内部实现的情况下扩充这些指令行为。

我建议你做的是为你从包含$scope广播的事件设置一个监听器。附加回调,检查当前输入指令的nodeName - 并关闭用户输入(使用readOnlydisabled)。

这些方面的东西:

app.config(function ($provide) {

  var directives = [
    'input',
    'select',
    'textarea'
  ];

  var eventName = '__disableInputs';

  angular.forEach(directives, function (directive) {

    $provide.decorator(directive + 'Directive', function ($delegate, $controller) {

      // Decorating directives returns an array. Select the first index.
      var directive = $delegate[0];

      // If a controller is tied to the directive, store a reference to it.
      if (directive.controller) {
        var origCtrl = directive.controller;
      }

      // Define a new controller on the directive.
      directive.controller = function ($scope, $element, $attrs) {

        if (origCtrl) {
          // Extend this new controller with the old behaviour of the 
          // original controller.
          angular.extend(this, $controller(origCtrl, { 
            $scope: $scope,
            $element: $element,
            $attrs: $attrs
          }));
        }

        // Add an event listener to the directive controller.
        var unlisten = $scope.$on(eventName, function () {
          if ($element[0].nodeName === 'SELECT') {
            $element[0].disabled = true;
          } else {
            $element[0].readOnly = true;
          }
        });

        $scope.$on('$destroy', unlisten);
      };

      // Return the original (augmented) directive.
      return $delegate;      
    });
  });
});

然后,我们可以简单地从控制器(或其他一些地方)触发行为:

app.controller('ctrl', function ($scope, $timeout) {

  $timeout(function () {
    $scope.$broadcast('__disableInput', {});
  }, 5000);

});

现在,每个inputselecttextarea目前都在广播中 范围应该应用其重复回调,而不是通过常规编辑 用户意味着了。

还有可能从$ rootScope中触发这个 - 但是;使用内置的ng指令时, 我想说添加我们自己的自定义标志是一个很好的做法,该标志决定指令是否监听 在我们的配置块(__disableInput)中定义的eventName与否。

如果我们要添加此自定义标志, 我们应该将新的directive.controller函数更改为:

if (origCtrl) {
  angular.extend(this, $controller(origCtrl, { 
    $scope: $scope,
    $element: $element,
    $attrs: $attrs
  }));  
}

// Only add our eventlistener if canBeDisabled is set.
if ($attrs.canBeDisabled !== undefined) {

  var unlisten = $scope.$on(eventName, function (e, data) {
    if ($element[0].nodeName === 'SELECT') {
      $element[0].disabled = true;
    } else {
      $element[0].readOnly = true;
    }
  });

  $scope.$on('$destroy', unlisten);
}

然后在我们看来,我们会这样做:

<input ng-model="someModel.property" can-be-disabled type="text">

现在,这是一个粗略的例子,可能还有一些我尚未探索过的问题。 但我确实认为如果你想要一个简单的方式,这是朝着正确方向迈出的一步 扩展ng指令而不必触及其内部和/或创建 新指令。

我还没做的一件事是将eventName定义为app.value(或app.constant), 这样我们就不必在配置块中定义eventName或担心拼写错误 在设立此活动的广播员时。我们只是将价值注入我们的背景中 并将其用作活动的名称。

这是给你的JSBin:http://jsbin.com/xipokuyi/5/edit

更深入地探讨探索此类方法的文章的链接: http://angular-tips.com/blog/2013/09/experiment-decorating-directives/

答案 2 :(得分:0)

TL;博士;

辅助服务用作指令和控制器之间的通信机制。服务登记册&amp;取消注册元素(在指令链接功能中)并启用&amp;通过函数调用(在控制器中)禁用所有已注册的元素。

解决方案1 ​​ - 任何位置的只读元素

HERE是我的解决方案的种子 1 ,其中元素可以位于DOM树中的任何位置。

辅助服务

app.factory("demoReadonlyState", function(){

  var elements = [];
  var state = false;

  return {
    enable : function(){
      state = false;
      for (var i = 0, len = elements.length; i < len; i++){
        elements[i].removeAttribute('readonly');
      }
    },
    disable : function(){
      state = true;
      for (var i = 0, len = elements.length; i < len; i++){
        elements[i].setAttribute('readonly', "True");
      }
    },
    addElement : function(domEl) { 
      elements.push(domEl);
      if(state){
        domEl.setAttribute('readonly', state);
      }
      else {
        domEl.removeAttribute('readonly');
      }
      console.log("add -- # elements: ", elements.length);
    },
    removeElement : function(el) {
      var index = elements.indexOf(el);
      if (index > -1) {
        elements.splice(index, 1);
      }
      console.log("remove -- # elements: ", elements.length);
    }
  }
})

指令

app.directive("demoReadonly", function(demoReadonlyState){

  return {
    link : function(scope, element){
      console.log(demoReadonlyState);
      demoReadonlyState.addElement(element[0]);
      element.on('$destroy', function(){
        demoReadonlyState.removeElement(element);
      })
    }
  }
})

使用

JS

app.controller("MainCtrl", function($scope, demoReadonlyState){
  $scope.roState = demoReadonlyState;
  $scope.roState.enable();
  //$scope.roState.disable();
});

HTML

<body ng-controller="MainCtrl">
    <input demo-readonly />
    <input />
    <input demo-readonly />
    <input />
    <input demo-readonly />
    <input />

    <div>
      <button ng-click="roState.enable()">enable</button>
      <button ng-click="roState.disable()">disable</button>
    </div>

    <div>
      <button ng-click="arr.push([])">add element</button>
      <button ng-click="arr.length && arr.splice(0,1)">remove element</button>
    </div>

    <div ng-repeat="x in arr">
      <input demo-readonly/>
    </div>
</body>

1 通过种子我的意思是,它是不可配置的。在正常的解决方案中,指令可以传递一个字符串(demo-readonly="type_X"),它指定一组应该enabled/disabled的元素,保持所有其他组完整。

解决方案2 - DOM子树中的只读元素

HERE是一种处理带有单个指令的子树的方法。它可以独立处理多个子树。在这种形式下,它不适用于动态ng-repeat 2 ,但可以轻松实现更强大的机制(尽管需要使用附加指令)。

辅助服务

app.factory("demoReadonlyState", function(){

  var elements = {};

  return {
    enable : function(type){
      element = elements[type];
      var inputs = element.querySelectorAll('input');
      for (var i = 0, len = inputs.length; i < len; i++){
        inputs[i].removeAttribute('readonly');
      }
    },
    disable : function(type){
      element = elements[type];
      var inputs = element.querySelectorAll('input');
      for (var i = 0, len = inputs.length; i < len; i++){
        inputs[i].setAttribute('readonly', "True");
      }
    },
    addElement : function(type, domEl) { 
      elements[type] = domEl;
    },
    removeElement : function(type) {
      delete elements[type];
    }
  }
})

指令

app.directive("demoReadonly", function(demoReadonlyState){

  return {
    scope : {
      type : "@demoReadonly"
    },
    link : function(scope, element){
      demoReadonlyState.addElement(scope.type, element[0]);
      element.on('$destroy', function(){
        demoReadonlyState.removeElement(scope.type);
      })
    }
  }
})

使用

JS

app.controller("MainCtrl", function($scope, demoReadonlyState){
  $scope.roState = demoReadonlyState;
  $scope.roState.enable('XXX');
  //$scope.roState.disable('XXX');
});

HTML

  <body ng-controller="MainCtrl">
    <div>
      <button ng-click="roState.enable('XXX')">enable</button>
      <button ng-click="roState.disable('XXX')">disable</button>
    </div>


    <div demo-readonly="XXX">
      <input/>
      <input/>
      <div>
        <input/>
        <input/>
      </div>
    </div>

  </body>

1 基本上使用动态ng-repeat需要在每次更新集合后手动调用enable/disable