如何根据条件创建在目标元素上添加ng-class和ng-disabled的指令?

时间:2022-08-22 17:00:59

I have the following code:

我有以下代码:

app.directive "ngDisableOnVar", ($compile) ->
  restrict: "A"
  terminal: true
  priority: 1000
  replace:false
  scope: {}
  compile: compile = (element, attrs) ->
    cattr = attrs["ngDisableOnVar"]
    element.attr("ng-class", "{'disabled': !#{cattr}}")
    element.attr("ng-disabled", "!#{cattr}")
    element.removeAttr("ng-disable-on-var")
    pre: preLink = (scope, iElement, iAttrs, controller) ->

    post: postLink = (scope, iElement, iAttrs, controller) ->
      $compile(iElement)(scope)

I tried to base the code on the answer given here. Basically, I'd like to have the following:

我试着将代码基于这里给出的答案。基本上,我想要有以下内容:

<input ngDisableOnVar="someScopeVariable>

And have it replaced with the following:

并将其替换为以下内容:

<input ng-class="{'disabled': !someScopeVariable}" ng-disabled="!someScopeVariable">

Something is wrong, cause even though I have them applied to my element, they're always disabled, even though the scope variable evaluates to true. What am I doing wrong?

有些东西是错误的,因为即使我将它们应用于我的元素,它们也总是被禁用,即使范围变量的计算结果为true。我究竟做错了什么?

EDIT: I created a plunker, where the first 2 buttons are created with ng-class and ng-disabled, and the other 2 buttons, should have the same things applied on them through the use of the directive.

编辑:我创建了一个plunker,其中前两个按钮是使用ng-class和ng-disabled创建的,另外两个按钮应该通过使用指令应用相同的东西。

Here is the plunker version with shared scope: http://plnkr.co/edit/TebCQL20ubh5AgJ6nMIl?p=preview

以下是具有共享范围的plunker版本:http://plnkr.co/edit/TebCQL20ubh5AgJ6nMIl?p = preview

And here's the one without the shared scope:http://plnkr.co/edit/CPm55MrHA8z6Bx4GbxoN?p=preview

这是没有共享范围的那个:http://plnkr.co/edit/CPm55MrHA8z6Bx4GbxoN?p = preview

The problem is, the one without the shared scope does not update. How can I make them update, and have the conditions depend on the variables passed as arguments?

问题是,没有共享范围的那个不会更新。如何让它们更新,并且条件取决于作为参数传递的变量?

EDIT #2: I'm starting to believe that the scope sharing is the correct way these 2 buttons should act, short of creating a new directive that encapsulates both buttons within it. Not 100% sure though.

编辑#2:我开始相信范围共享是这两个按钮应该采取行动的正确方式,而不是创建一个新的指令来封装其中的两个按钮。不过100%肯定。

4 个解决方案

#1


5  

I would go with your EDIT #2 because they are related. If we create them as separate elements, we need to somehow pass the related element to each one => When we click on 1 button, we update itself and also the related element.

我会选择你的编辑#2,因为它们是相关的。如果我们将它们创建为单独的元素,我们需要以某种方式将相关元素传递给每个元素=>当我们单击1按钮时,我们更新自身以及相关元素。

Here I modified your first approach to make it work: http://plnkr.co/edit/KgYIlATiw9xzTEZt9Jv1?p=preview

在这里,我修改了您的第一种方法,使其工作:http://plnkr.co/edit/KgYIlATiw9xzTEZt9Jv1?p = preview

In this example, I have to pass the related element to each directive so that when we click we can update itself and the related element:

在这个例子中,我必须将相关元素传递给每个指令,这样当我们点击时我们可以更新自己和相关元素:

related-element="btnForward"

I did some modifications in the directive:

我在指令中做了一些修改:

scope: {
      reactOn: "=", //use property binding instead of function binding
      relatedElement:"@" 
    },
link: function(scope, element, attrs) { 
      scope.toggle = function(){
        scope.reactOn = !scope.reactOn;//toggle current element
        var relatedScope = $("#"+scope.relatedElement).scope();//get related element's scope and toggle it
        relatedScope.reactOn = !relatedScope.reactOn;
      }
      //var cattr = attrs.ngDisableReactOn;
      element.attr("ng-class", "{'disabled': !reactOn}"); //Use reactOn instead as this is the property of current scope
      element.attr("ng-disabled", "!reactOn");
      element.attr("ng-click", "toggle()"); 
      element.removeAttr("ng-disable-react-on");
      $compile(element)(scope);
    }

We don't need to make things complex. Just create a normal directive to wrap 2 buttons.

我们不需要让事情复杂化。只需创建一个普通指令来包装2个按钮。

myApp.directive("ngDisableReactOn", function($compile) {
  return {
    restrict: "A",
    templateUrl:"ngDisableReactOn.html",
    scope: {
      can_go_back: "@"
    },
    link: function(scope, element, attrs) { 
      scope.goBack = function(){
          scope.can_go_back = false;
      }

      scope.goFwd = function(){
          scope.can_go_back = true;
      }
    }
  }
});

Template:

<input type="button" value="go back" ng-click="goBack()"  ng-class="{'disabled': !can_go_back}" ng-disabled="!can_go_back">
<input type="button" value="go fwd"  ng-click="goFwd()"   ng-class="{'disabled': can_go_back}" ng-disabled="can_go_back">

DEMO

Another solution is to create a parent directive as a container. This is the solution I like the most. With this approach, you can freely change the inner content of the directive like adding more buttons, more text,....(DON'T NEED TO HARDCODE THE TEMPLATE) The parent directive works as a manager to ensure there is only 1 active child at a time:

另一种解决方案是将父指令创建为容器。这是我最喜欢的解决方案。使用这种方法,您可以*更改指令的内部内容,例如添加更多按钮,更多文本,....(不需要硬编码模板)父指令作为管理员工作,以确保只有1个活动一次一个孩子:

myApp.directive("ngDisableReactOnContainer", function() { //Container directive to manage all child directives
  return {
    restrict: 'EA',
    replace: true,
    transclude: true,//Use transclusion to move inner content to the template
    template: '<div ng-transclude></div>',
    controller: function() {
      var children = []; 
      this.selectChild = function(activeChild) { //ensure that only 1 child is active at a time
        activeChild.active = true;
        angular.forEach(children, function(child) {
          if (child != activeChild) {
            child.active = false;
          }
        });
      }

      this.addChild = function(child) {
        children.push(child);
      }
    }
  };
});

myApp.directive("ngDisableReactOn", function($compile) {
  return {
    restrict: "A",

    scope:{
      active:"@"
    },

    require: '^?ngDisableReactOnContainer',
    link: function(scope, element, attrs, controller) {

      scope.active = scope.active === 'true';

      controller.addChild(scope);//register itself with the container

      scope.select = function(){//When this element is clicked, inform the container to toggle all children accordingly.
         controller.selectChild(scope);
      }

      //Add ng-class and ng-disabled based on your requirement.
      element.attr("ng-class", "{'disabled': active}"); //Use active instead as this is the property of current scope
      element.attr("ng-disabled", "active");
      element.attr("ng-click", "select()"); 
      element.removeAttr("ng-disable-react-on");
      $compile(element)(scope);
    }
  }
});

Using these directives would be simple:

使用这些指令很简单:

<div ng-disable-react-on-container>
    <input ng-disable-react-on type="button" value="button 1" active="true" >
    <input ng-disable-react-on type="button" value="button 2" >
    <input ng-disable-react-on type="button" value="button 3" >
</div>

DEMO

#2


1  

Here is a very ugly way to demonstrate how to compile the template during the link function. It is ugly because I didn't address any binding on the scope variable. You might want to isolate the scope or setup two-way binding but this should give you the gist of how to access the scope for compiling purposes.

这是一个非常难看的方式来演示如何在链接函数期间编译模板。这很难看,因为我没有解决范围变量的任何绑定问题。您可能希望隔离范围或设置双向绑定,但这应该为您提供如何访问范围以进行编译的要点。

app.directive('foo', function($compile) {
  return function(scope, elem, attrs) {
    var html;
    if (scope.testVar)
      html = '<input ng-class="{\'disabled\': !someScopeVariable}" ng-disabled="!someScopeVariable" />';
    else
      html = '<input />';
    var htmlEl = angular.element(html),
      compiledEl = $compile(htmlEl)(scope);
    elem.replaceWith(compiledEl);
  }
});

http://plnkr.co/edit/xBS4ZMXVwqv8CwWvwTu5?p=preview

#3


1  

You can get same effect with another similar approach. Plunk here

您可以使用其他类似方法获得相同的效果。插在这里

Instead of $compile in link function you can use template in your directive and ng-disabled with a variable in scope that is bind to parent scope variable via isolated scope.

您可以在指令中使用模板而不是在链接函数中使用$ comp,并且可以使用通过隔离范围绑定到父作用域变量的作用域中的变量来禁用ng。

#4


-2  

Have you tried to remove the ! before the var name?

你有没有试过删除!在var名称之前?

<input ng-class="{'disabled': someScopeVariable}" ng-disabled="someScopeVariable">

#1


5  

I would go with your EDIT #2 because they are related. If we create them as separate elements, we need to somehow pass the related element to each one => When we click on 1 button, we update itself and also the related element.

我会选择你的编辑#2,因为它们是相关的。如果我们将它们创建为单独的元素,我们需要以某种方式将相关元素传递给每个元素=>当我们单击1按钮时,我们更新自身以及相关元素。

Here I modified your first approach to make it work: http://plnkr.co/edit/KgYIlATiw9xzTEZt9Jv1?p=preview

在这里,我修改了您的第一种方法,使其工作:http://plnkr.co/edit/KgYIlATiw9xzTEZt9Jv1?p = preview

In this example, I have to pass the related element to each directive so that when we click we can update itself and the related element:

在这个例子中,我必须将相关元素传递给每个指令,这样当我们点击时我们可以更新自己和相关元素:

related-element="btnForward"

I did some modifications in the directive:

我在指令中做了一些修改:

scope: {
      reactOn: "=", //use property binding instead of function binding
      relatedElement:"@" 
    },
link: function(scope, element, attrs) { 
      scope.toggle = function(){
        scope.reactOn = !scope.reactOn;//toggle current element
        var relatedScope = $("#"+scope.relatedElement).scope();//get related element's scope and toggle it
        relatedScope.reactOn = !relatedScope.reactOn;
      }
      //var cattr = attrs.ngDisableReactOn;
      element.attr("ng-class", "{'disabled': !reactOn}"); //Use reactOn instead as this is the property of current scope
      element.attr("ng-disabled", "!reactOn");
      element.attr("ng-click", "toggle()"); 
      element.removeAttr("ng-disable-react-on");
      $compile(element)(scope);
    }

We don't need to make things complex. Just create a normal directive to wrap 2 buttons.

我们不需要让事情复杂化。只需创建一个普通指令来包装2个按钮。

myApp.directive("ngDisableReactOn", function($compile) {
  return {
    restrict: "A",
    templateUrl:"ngDisableReactOn.html",
    scope: {
      can_go_back: "@"
    },
    link: function(scope, element, attrs) { 
      scope.goBack = function(){
          scope.can_go_back = false;
      }

      scope.goFwd = function(){
          scope.can_go_back = true;
      }
    }
  }
});

Template:

<input type="button" value="go back" ng-click="goBack()"  ng-class="{'disabled': !can_go_back}" ng-disabled="!can_go_back">
<input type="button" value="go fwd"  ng-click="goFwd()"   ng-class="{'disabled': can_go_back}" ng-disabled="can_go_back">

DEMO

Another solution is to create a parent directive as a container. This is the solution I like the most. With this approach, you can freely change the inner content of the directive like adding more buttons, more text,....(DON'T NEED TO HARDCODE THE TEMPLATE) The parent directive works as a manager to ensure there is only 1 active child at a time:

另一种解决方案是将父指令创建为容器。这是我最喜欢的解决方案。使用这种方法,您可以*更改指令的内部内容,例如添加更多按钮,更多文本,....(不需要硬编码模板)父指令作为管理员工作,以确保只有1个活动一次一个孩子:

myApp.directive("ngDisableReactOnContainer", function() { //Container directive to manage all child directives
  return {
    restrict: 'EA',
    replace: true,
    transclude: true,//Use transclusion to move inner content to the template
    template: '<div ng-transclude></div>',
    controller: function() {
      var children = []; 
      this.selectChild = function(activeChild) { //ensure that only 1 child is active at a time
        activeChild.active = true;
        angular.forEach(children, function(child) {
          if (child != activeChild) {
            child.active = false;
          }
        });
      }

      this.addChild = function(child) {
        children.push(child);
      }
    }
  };
});

myApp.directive("ngDisableReactOn", function($compile) {
  return {
    restrict: "A",

    scope:{
      active:"@"
    },

    require: '^?ngDisableReactOnContainer',
    link: function(scope, element, attrs, controller) {

      scope.active = scope.active === 'true';

      controller.addChild(scope);//register itself with the container

      scope.select = function(){//When this element is clicked, inform the container to toggle all children accordingly.
         controller.selectChild(scope);
      }

      //Add ng-class and ng-disabled based on your requirement.
      element.attr("ng-class", "{'disabled': active}"); //Use active instead as this is the property of current scope
      element.attr("ng-disabled", "active");
      element.attr("ng-click", "select()"); 
      element.removeAttr("ng-disable-react-on");
      $compile(element)(scope);
    }
  }
});

Using these directives would be simple:

使用这些指令很简单:

<div ng-disable-react-on-container>
    <input ng-disable-react-on type="button" value="button 1" active="true" >
    <input ng-disable-react-on type="button" value="button 2" >
    <input ng-disable-react-on type="button" value="button 3" >
</div>

DEMO

#2


1  

Here is a very ugly way to demonstrate how to compile the template during the link function. It is ugly because I didn't address any binding on the scope variable. You might want to isolate the scope or setup two-way binding but this should give you the gist of how to access the scope for compiling purposes.

这是一个非常难看的方式来演示如何在链接函数期间编译模板。这很难看,因为我没有解决范围变量的任何绑定问题。您可能希望隔离范围或设置双向绑定,但这应该为您提供如何访问范围以进行编译的要点。

app.directive('foo', function($compile) {
  return function(scope, elem, attrs) {
    var html;
    if (scope.testVar)
      html = '<input ng-class="{\'disabled\': !someScopeVariable}" ng-disabled="!someScopeVariable" />';
    else
      html = '<input />';
    var htmlEl = angular.element(html),
      compiledEl = $compile(htmlEl)(scope);
    elem.replaceWith(compiledEl);
  }
});

http://plnkr.co/edit/xBS4ZMXVwqv8CwWvwTu5?p=preview

#3


1  

You can get same effect with another similar approach. Plunk here

您可以使用其他类似方法获得相同的效果。插在这里

Instead of $compile in link function you can use template in your directive and ng-disabled with a variable in scope that is bind to parent scope variable via isolated scope.

您可以在指令中使用模板而不是在链接函数中使用$ comp,并且可以使用通过隔离范围绑定到父作用域变量的作用域中的变量来禁用ng。

#4


-2  

Have you tried to remove the ! before the var name?

你有没有试过删除!在var名称之前?

<input ng-class="{'disabled': someScopeVariable}" ng-disabled="someScopeVariable">