Как написать директиву angular, которая переключает классы с помощью селектора

#javascript #angularjs #angularjs-directive

#javascript #angularjs #angularjs-директива

Вопрос:

Я пытаюсь написать директиву, которая переключает классы на основе условия селектора:

 <label class-when="{'is-checked': ':has(input:checked)', 'is-disabled': ':has(input:disabled)'}">
    <input type="checkbox">
    Example checkbox
</label>
  

Мне нужно как-то следить за изменениями DOM в элементе и его потомках, но я получаю сообщение об ng:areq ошибке. Как я могу это сделать?

 define(function (require) {
    var _ = require('lodash');

    return {
        restrict: 'A',
        scope: {
            object: '@classWhen'
        },
        link: function (scope, element) {
            scope.$watchCollection(function() {
                return element.find('*').add(element);
            }, function () {
                _.forOwn(scope.object, function (test, classes) {
                    test = typeof test === 'boolean' ? test : element.is(test);
                    element.toggleClass(classes, test);
                });
            });
        }
    };
});
  

Комментарии:

1. @ryanyuyu Я бы предположил, что директива требуется из-за структурной директивы для родительского элемента или самого элемента (это просто не показано в предоставленном коде). Я могу ошибаться, хотя

2. Я решил для вас одну важную проблему, которая заключается в том, что your scope.object является строкой, поэтому выполнение _.forOwn() дает вам каждый символ в строке с индексом в качестве ключа. Чтобы исправить это, вам нужно выполнить JSON.parse() on scope.object , что приводит к следующей проблеме. Ваша строка является недопустимым JSON, потому что она имеет одинарные кавычки, окружающие свойства / значения, и ей нужны двойные кавычки, чтобы быть допустимым JSON. Тогда остается проблема в том, что ваша директива не срабатывает, когда пользователь устанавливает / снимает флажок. Я все еще работаю над этой частью, но я думаю, что я дал вам большой шаг в правильном направлении

3. Или просто используйте двустороннюю "=" привязку для scope.object .

4. Все это просто неправильно с точки зрения angular. Вы никогда не должны делать подобные вещи. Если вы хотите, чтобы стиль контейнера зависел от входных данных — директивы должны быть для каждого входного элемента, а не для одной волшебной директивы сверху.

5. @ryanve Проверьте мое обновленное решение

Ответ №1:

Хорошо, прочитав ваш комментарий о вознаграждении, я понимаю, что вы хотите, чтобы это было независимым от контроллера. Я потратил некоторое время на полную переработку своего решения, и я думаю, что наконец-то нашел способ выполнить то, что вы хотите.

Это действительно сводится к 2 вещам:

1) Обнаружение изменения :checked статуса флажка и

2) Обнаружение изменения :disabled статуса флажка

Обнаружение 1) было простым, так как вы можете использовать простой обработчик изменений jQuery, но для обнаружения 2) потребовалось немного больше исследований. Это требует использования scope.$watch дочернего ng-disabled атрибута on .

Вот демонстрация того, как это будет работать:

 var app = angular.module("myApp", [])
  .directive("classWhen", function() {
    function setClasses(classWhen, $element, $input) {
      Object.keys(classWhen).forEach(function(key) {
        var test = classWhen[key];
        // .toggleClass("className", true) === .addClass("className")
        // .toggleClass("className", false) === .removeClass("className")
        $element.toggleClass(key, $element.is(test));
      });
    }
    return {
      restrict: 'A',
      link: function link (scope, element, attrs) {
        var classWhen = JSON.parse(attrs.classWhen);
        var $element = $(element);
        $element.find("*").each(function (index, elem) {
          var $elem = $(this);
          // namespace the change event so we can easily .off() it
          $elem.off("change.classWhen");
          $elem.on("change.classWhen", function () {
            setClasses(classWhen, $element, $elem);
          });
          // watch child ng-disabled attribute
          scope.$watch($elem.attr("ng-disabled"), function (val) {
            setClasses(classWhen, $element, $elem);
          });
        });
      }
    };
  });  
 .is-checked {
  background-color: yellow;
}
.is-disabled {
  background-color: lightblue;
}  
 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-app="myApp">
  <label>
    <input type="checkbox" ng-model="disableAll" />Disable All</label>
  <br>
  <br>
  <label class-when='{"is-checked": ":has(input:checked)", "is-disabled": ":has(input:disabled)"}'>
    <input type="checkbox" ng-disabled="disableAll">Example checkbox 1
  </label>
  <br>
  <label class-when='{"is-checked": ":has(input:checked)", "is-disabled": ":has(input:disabled)"}'>
    <input type="checkbox" ng-disabled="disableAll">Example checkbox 2
  </label>
  <br>
  <label class-when='{"is-checked": ":has(input:checked, .test)", "is-disabled": ":has(input:disabled)"}'>
    <input type="text" ng-disabled="disableAll" ng-class="testingClass" ng-model="testingClass"/>
    <br>
    <input type="checkbox" ng-disabled="disableAll">
    Example checkbox 3
  </label>
  <br>
  <label class-when='{"is-checked": ":has(input:checked)", "is-disabled": ":has(input:disabled)"}'>
    <input type="checkbox" ng-disabled="disableAll">Example checkbox 4
  </label>
</div>  

Комментарии:

1. Спасибо за обновленный ответ! Кажется, это работает в бегуне фрагмента, но я все еще получаю ng:areq ошибку, когда в моей среде angular

2. @ryanve ng: areq — это общая ошибка для неверного аргумента. Очевидно, что это не проблема с моим кодом, поэтому у меня нет способа отладить вашу проблему, если вы не опубликуете более подробную информацию об этом. Можете ли вы дать мне номер строки, расширенное описание ошибки и соответствующий код в этой области? Или, что еще лучше, вы можете опубликовать скрипку, которая воспроизводит ошибку?

Ответ №2:

Вот моя попытка решить вашу проблему в общем виде: https://plnkr.co/edit/kVyJQClmQhF3FeBTmKaX?p=preview

Код директивы:

 app.directive('classWhen', () => (scope, element, attrs) => {
  scope.$watchCollection(getClasses, setClasses)

  function getClasses() {
    // We have to evaluate expression in attrs.classWhen so that we get object
    const rawClasses = scope.$eval(attrs.classWhen)
    const classes = {}

    // then we normalize it so that strings are treated as jquery selectors
    Object.keys(rawClasses).forEach((className) => {
      const expr = rawClasses[className]
      classes[className] = typeof expr === 'string'
        ? element.is(expr)
        : !!expr // we normalize falsy values to booleans
    })

    // then we return it to $watchCollection
    return classes
  }

  /**
   * will be called whenever any of the classes changes
   */
  function setClasses(classes) {
    Object.keys(classes).forEach((className) => {
      element.toggleClass(className, classes[className])
    })
  }
})
  

Комментарии:

1. @ryanve решает ли это вашу проблему? Как я могу улучшить свой ответ?

2. Мне нравится простота этого подхода, но в зависимости от того, как изменяется DOM, работа занимает несколько секунд. Как будто он ожидает следующего дайджеста

3. Проблема с этим решением такая же, как и с одним из моих оригинальных решений для этого (если вы посмотрите историю редактирования в моем ответе). # 1, он не обрабатывает несколько флажков по отдельности, и # 2, он полагается на переменные контроллера, которые OP указал в комментарии к вознаграждению: «Я бы хотел сделать это в общей директиве или фильтре, который ничего не требует от контроллера »

4. @ryanve что вы подразумеваете под тем, как меняется DOM ? Можете ли вы привести пример plunk, где он ведет себя подобным образом? Я предполагаю, что вы меняете его извне digest . В моем plunk я добавил ng-model к вводу именно потому, что он решает запуск дайджеста при изменении входного значения.

Ответ №3:

Я создал общую скрипку для этого, а также с RequireJS, как вы использовали.

Пожалуйста, посмотрите здесь

http://fiddle.jshell.net/balasuar/dugmu/15/

Примечание: независимо от того, какое свойство вы добавили в селектор, например :checked , and :disabled , добавьте соответствующее свойство angular к дочернему элементу, например ng-model and ng-disabled . Это будет работать как шарм!!

Код

 var app = window.app = angular.module("myApp", []);

define('classWhenDirective', function () {
    //var _ = require('lodash');
    var app = window.app;debugger;
    app.directive("classWhen", function() {
    return {
    restrict: 'A',
    link: function(scope, element, attrs) {
          scope.$watch(getChanges, setClasses)

  function getChanges() {
    var attrs = [];
    
    function getAttrs(elm) {
       $.each(elm.attributes, function(i, attrib){
     var name = attrib.name;
     var value = attrib.value;
       if(value amp;amp; name.indexOf('ng-') > -1) {
         value = scope.$eval(value);
       }
     if(value) {      
       attrs.push(name   ':'   value);
     }
  });
     
    }
    
    getAttrs(element.get(0));
    
    element.children().each(function(){
       getAttrs(this);
    });
    
    return attrs.join(',');
  }

  function setClasses() {
    var rawClasses = scope.$eval(attrs.classWhen);

    Object.keys(rawClasses).forEach(function(className){
      var expr = rawClasses[className]
      var setClass = typeof expr === 'string'
        ? element.is(expr)
        : !!expr;
       element.toggleClass(className, setClass);
    });
  }
    }
  };   
    });
});

define('todo', ['classWhenDirective'], function() {
  var app = window.app;
  app.controller('myController', function($scope) {

  });
});

// angular bootstrap
require(['todo'], function() {
  angular.bootstrap(document, ['myApp']);
});  
 .is-checked {
  color: green;
}
.is-disabled {
  color: gray;
}  
 <script src="http://requirejs.org/docs/release/2.0.6/minified/require.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h2>ClassWhen Directive</h2>

<div ng-controller="myController">
  <label>
    <input type="checkbox" ng-model="disableAll" />Disable All</label>
  <br>
  <br>
  <label class-when='{"is-checked": ":has(input:checked)", "is-disabled": ":has(input:disabled)"}'>
    <input type="checkbox" ng-model="checkBox1" ng-disabled="disableAll">ClassWhen checkbox 1
  </label>
  <br>
  <label class-when='{"is-checked": ":has(input:checked)", "is-disabled": ":has(input:disabled)"}'>
    <input type="checkbox" ng-model="checkBox2" ng-disabled="disableAll">ClassWhen checkbox 2
  </label>
</div>