6

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?

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.

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

And here's the one without the shared scope: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.

Community
  • 1
  • 1
Geo
  • 93,257
  • 117
  • 344
  • 520

4 Answers4

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.

Here I modified your first approach to make it work: 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.

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:

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

Khanh TO
  • 48,509
  • 13
  • 99
  • 115
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

Brian Lewis
  • 5,739
  • 1
  • 21
  • 28
  • 1
    I don't want to critizise your answer - I don't know a better one. But when I see such things (in my own AngularJS-code as well), I have a really bad feeling. My experience with such constructions is that it will become a maintenance and bugfix nightmare. Better be little more verbously and avoid such things. **Just my opinion...** – hgoebl Feb 02 '14 at 17:45
  • Completely understand your point. That is why I said it was "very ugly". I was just trying to demonstrate how to use `$compile` without having to abstract everything out. – Brian Lewis Feb 02 '14 at 19:32
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.

MRB
  • 3,752
  • 4
  • 30
  • 44
-2

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

<input ng-class="{'disabled': someScopeVariable}" ng-disabled="someScopeVariable">
guli
  • 1,183
  • 1
  • 9
  • 19
  • But I don't understand how that would make a difference. Can you elaborate? – Geo Jan 31 '14 at 16:17
  • In the first time you use someScoepvariable and after !someScopeVariable. But perhaps i didn't understand the problem. – guli Jan 31 '14 at 16:27