指令中的AngularJS $ watch不会触发

时间:2016-02-26 22:01:11

标签: javascript angularjs angularjs-directive

我有一块手表正在查看我的滑块模型,当我想将其中一个属性设置为可见或启用时,它似乎不想触发更改了手表的事件。如果我单击按钮它应该隐藏或禁用手柄,它不会。如果我拖动另一个句柄,则调用updateDOM,然后隐藏或禁用句柄。不确定我在这里做错了什么。

    scope.$watch('sliders', function(oldValue, newValue) {
      console.log('sliders Update: ', oldValue, ' : ', newValue);
      updateDOM();
    });

这是一个有效的插件:http://plnkr.co/edit/I3A9H8qTs0z4CVaYnyJZ?p=preview

'use strict';

angular.module('angularMultiSlider', [])
  .directive('multiSliderKey', function($compile) {
    return {
      restrict: 'EA',
      transclude: true,
      scope: {
        displayFilter: '@',
        sliders : '=ngModel'
      },
      link: function(scope, element) {
        var sliderStr = '';
        if (scope.displayFilter === undefined) scope.displayFilter = '';
        var filterExpression = scope.displayFilter ===  '' ? '' : ' | ' + scope.displayFilter;

        angular.forEach(scope.sliders, function(slider, key){
          var colorKey = slider.color ? '<span style="background-color:' + slider.color + ';"></span> ' : '';
          sliderStr += '<div class="key">' + colorKey + '{{ sliders[' + key.toString() + '].title }} <strong>{{ sliders[' + key.toString() + '].value ' + filterExpression + '}}</strong></div>';
        });

        var sliderControls = angular.element('<div class="angular-multi-slider-key">' + sliderStr + '</div>');
        element.append(sliderControls);
        $compile(sliderControls)(scope);
      }
    }
  })
  .directive('multiSlider', function($compile, $filter) {
    var events = {
      mouse: {
        start: 'mousedown',
        move: 'mousemove',
        end: 'mouseup'
      },
      touch: {
        start: 'touchstart',
        move: 'touchmove',
        end: 'touchend'
      }
    };

    function roundStep(value, precision, step, floor) {
      var remainder = (value - floor) % step;
      var steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder;
      var decimals = Math.pow(10, precision);
      var roundedValue = steppedValue * decimals / decimals;
      return parseFloat(roundedValue.toFixed(precision));
    }

    function offset(element, position) {
      return element.css({
        left: position
      });
    }

    function pixelize(position) {
      return parseInt(position) + 'px';
    }

    function contain(value) {
      if (isNaN(value)) return value;
      return Math.min(Math.max(0, value), 100);
    }

    function overlaps(b1, b2, offsetTop) {
      function comparePositions(p1, p2) {
        var r1 = p1[0] < p2[0] ? p1 : p2;
        var r2 = p1[0] < p2[0] ? p2 : p1;
        return r1[1] > r2[0] || r1[0] === r2[0];
      }

      var posB1 = [[ b1.offsetLeft, b1.offsetLeft + b1.offsetWidth ], [ offsetTop, offsetTop -  b1.scrollTop + b1.offsetHeight ]],
        posB2 = [[ b2.offsetLeft, b2.offsetLeft + b2.offsetWidth ], [ b2.offsetTop, b2.offsetTop -  b2.scrollTop + b2.offsetHeight ]];

      return comparePositions( posB1[0], posB2[0] ) && comparePositions( posB1[1], posB2[1] );
    }

    return {
      restrict: 'EA',
      require: '?ngModel',
      scope: {
        floor: '@',
        ceiling: '@',
        step: '@',
        precision: '@',
        bubbles: '@',
        displayFilter: '@',
        sliders: '=ngModel'
      },
      template :
      '<div class="bar"></div>',

      link: function(scope, element, attrs, ngModel) {
        if (!ngModel) return; // do nothing if no ng-model

        //base copy to see if sliders returned to original
        var original;

        ngModel.$render = function() {
          original = angular.copy(scope.sliders);
        };

        element.addClass('angular-multi-slider');

        // DOM Components
        if (scope.displayFilter === undefined) scope.displayFilter = '';
        var filterExpression = scope.displayFilter ===  '' ? '' : ' | ' + scope.displayFilter;

        var sliderStr = '<div class="limit floor">{{ floor ' + filterExpression + ' }}</div>' +
                        '<div class="limit ceiling">{{ ceiling ' + filterExpression + '}}</div>';
        angular.forEach(scope.sliders, function(slider, key){
          sliderStr += '<div class="handle"></div><div class="bubble">{{ sliders[' + key.toString() + '].title }}{{ sliders[' + key.toString() + '].value ' + filterExpression + ' }}</div>';
        });
        var sliderControls = angular.element(sliderStr);
        element.append(sliderControls);
        $compile(sliderControls)(scope);

        var children  = element.children();
        var bar       = angular.element(children[0]),
          ngDocument  = angular.element(document),
          floorBubble = angular.element(children[1]),
          ceilBubble  = angular.element(children[2]),
          bubbles = [],
          handles = [];

        angular.forEach(scope.sliders, function(slider, key) {
          handles.push(angular.element(children[(key * 2) + 3]));
          bubbles.push(angular.element(children[(key * 2) + 4]));
        });

        // Control Dimensions Used for Calculations
        var handleHalfWidth = 0,
          barWidth = 0,
          minOffset = 0,
          maxOffset = 0,
          minValue = 0,
          maxValue = 0,
          valueRange = 0,
          offsetRange = 0,
          bubbleTop = undefined,
          bubbleHeight = undefined,
          handleTop = undefined,
          handleHeight = undefined;

        if (scope.step === undefined) scope.step = 10;
        if (scope.floor === undefined) scope.floor = 0;
        if (scope.ceiling === undefined) scope.ceiling = 500;
        if (scope.precision === undefined) scope.precision = 2;
        if (scope.bubbles === undefined) scope.bubbles = false;

        var bindingsSet = false;

        var updateCalculations = function() {
          scope.floor = roundStep(parseFloat(scope.floor), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor));
          scope.ceiling = roundStep(parseFloat(scope.ceiling), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor));

          angular.forEach(scope.sliders, function(slider) {
            slider.value = roundStep(parseFloat(slider.value), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor));
          });

          handleHalfWidth = handles[0][0].offsetWidth / 2;
          barWidth = bar[0].offsetWidth;
          minOffset = 0;
          maxOffset = barWidth - handles[0][0].offsetWidth;
          minValue = parseFloat(scope.floor);
          maxValue = parseFloat(scope.ceiling);
          valueRange = maxValue - minValue;
          offsetRange = maxOffset - minOffset;
        };

        var updateDOM = function () {

          updateCalculations();

          var percentOffset = function (offset) {
            return contain(((offset - minOffset) / offsetRange) * 100);
          };

          var percentValue = function (value) {
            return contain(((value - minValue) / valueRange) * 100);
          };

          var pixelsToOffset = function (percent) {
            return pixelize(percent * offsetRange / 100);
          };

          var setHandles = function () {
            offset(ceilBubble, pixelize(barWidth - ceilBubble[0].offsetWidth));
            angular.forEach(scope.sliders, function(slider,key){
              if (slider.color) {
                handles[key].css({'background-color': slider.color});
              }

              if (slider.value >= minValue && slider.value <= maxValue) {
                offset(handles[key], pixelsToOffset(percentValue(slider.value)));
                offset(bubbles[key], pixelize(handles[key][0].offsetLeft - (bubbles[key][0].offsetWidth / 2) + handleHalfWidth));
                handles[key].css({'display': 'block'});
                if ('' + scope.bubbles === 'true') {
                  bubbles[key].css({'display': 'block'});
                }
              } else {
                handles[key].css({'display': 'none'});
                bubbles[key].css({'display': 'none'});
              }

              if (slider.hasOwnProperty("visible") && slider.visible === false) {
                handles[key].css({'display': 'none'});
                bubbles[key].css({'display': 'none'});
              }

              if (slider.hasOwnProperty("enabled") && slider.enabled === false) {
                handles[key].addClass('disabled');
                bubbles[key].addClass('disabled');
              } else {
                handles[key].removeClass('disabled');
                bubbles[key].removeClass('disabled');
              }
            });
          };

          var resetBubbles = function() {
            if (scope.sliders.length > 1) {
              //timeout must be longer than css animation for proper bubble collision detection
              for (var i = 0; i < scope.sliders.length; i++) {
                (function (index) {
                  setTimeout(function () {
                    overlapCheck(index);
                  }, i * 150);
                })(i);
              }
            }
          };

          var overlapCheck = function(currentRef) {
            var safeAtLevel = function(cRef, level) {
              for (var x = 0; x < scope.sliders.length; x++) {
                  if (x != cRef && overlaps(bubbles[cRef][0], bubbles[x][0], (bubbleTop * level))) {
                    return safeAtLevel(cRef, level + 1);
                  }
                }
              return level;
            };

            if (scope.sliders.length > 1) {
              var safeLevel = safeAtLevel(currentRef, 1) - 1;
              handles[currentRef].css({top: pixelize((-1 * (safeLevel * bubbleHeight)) + handleTop), height: pixelize(handleHeight + (bubbleHeight * safeLevel)), 'z-index':  99-safeLevel});
              bubbles[currentRef].css({top: pixelize(bubbleTop - (bubbleHeight * safeLevel))});
            }
          };

          var bind = function (handle, bubble, currentRef, events) {
            var onEnd = function () {
              handle.removeClass('grab');
              bubble.removeClass('grab');
              if (!(''+scope.bubbles === 'true')) {
                bubble.removeClass('active');
              }

              ngDocument.unbind(events.move);
              ngDocument.unbind(events.end);

              if (angular.equals(scope.sliders, original)) {
                ngModel.$setPristine();
              }

              //Move possible elevated bubbles back down if one below it moved.
              resetBubbles();
              scope.$apply();
            };

            var onMove = function (event) {
              // Suss out which event type we are capturing and get the x value
              var eventX = 0;
              if (event.clientX !== undefined) {
                eventX = event.clientX;
              }
              else if ( event.touches !== undefined && event.touches.length) {
                eventX = event.touches[0].clientX;
              }
              else if ( event.originalEvent !== undefined &&
                event.originalEvent.changedTouches !== undefined &&
                event.originalEvent.changedTouches.length) {
                eventX = event.originalEvent.changedTouches[0].clientX;
              }

              var newOffset = Math.max(Math.min((eventX - element[0].getBoundingClientRect().left - handleHalfWidth), maxOffset), minOffset),
                newPercent = percentOffset(newOffset),
                newValue = minValue + (valueRange * newPercent / 100.0);

              newValue = roundStep(newValue, parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor));
              scope.sliders[currentRef].value = newValue;

              setHandles();
              overlapCheck(currentRef);

              ngModel.$setDirty();
              scope.$apply();
            };

            var onStart = function (event) {
              if (scope.sliders[currentRef].hasOwnProperty("enabled") && scope.sliders[currentRef].enabled === false) {
                bubble.addClass('disabled');
                handle.addClass('disabled');
                return;
              }
              updateCalculations();
              bubble.addClass('active grab');
              handle.addClass('active grab');
              setHandles();
              event.stopPropagation();
              event.preventDefault();
              ngDocument.bind(events.move, onMove);
              return ngDocument.bind(events.end, onEnd);
            };

            handle.bind(events.start, onStart);
          };

          var setBindings = function () {
            var method, i;
            var inputTypes = ['touch', 'mouse'];
            for (i = 0; i < inputTypes.length; i++) {
              method = inputTypes[i];
              angular.forEach(scope.sliders, function(slider, key){
                bind(handles[key], bubbles[key], key, events[method]);
                if (scope.sliders[key].hasOwnProperty("enabled") && scope.sliders[key].enabled === false) {
                  handles[key].addClass('disabled');
                  bubbles[key].addClass('disabled');
                }
              });
            }

            bindingsSet = true;
          };

          if (!bindingsSet) {
            setBindings();

            // Timeout needed because bubbles offsetWidth is incorrect during initial rendering of html elements
            setTimeout( function() {
              if ('' + scope.bubbles === 'true') {
                angular.forEach(bubbles, function (bubble) {
                   bubble.addClass('active');
                });
              }
              updateCalculations();
              setHandles();

              //Get Default sizes of bubbles and handles, assuming each are equal, calculated from css
              handleTop = handleTop === undefined ? handles[0][0].offsetTop : handleTop;
              handleHeight = handleHeight === undefined ? handles[0][0].offsetHeight : handleHeight;
              bubbleTop = bubbleTop === undefined ? bubbles[0][0].offsetTop : bubbleTop;
              bubbleHeight = bubbleHeight === undefined ? bubbles[0][0].offsetHeight + 7 : bubbleHeight ; //add 7px bottom margin to the bubble offset for handle

              resetBubbles();
            }, 10);
          }
        };

        // Watch Models based on mode
        scope.$watch('sliders', function(oldValue, newValue) {
          console.log('sliders Update: ', oldValue, ' : ', newValue);
          updateDOM();
        });
        scope.$watch('ceiling', function() {
          bindingsSet = false;
          updateDOM();
        });
        scope.$watch('floor', function() {
          bindingsSet = false;
          updateDOM();
        });
        // Update on Window resize
        window.addEventListener('resize', updateDOM);
      }
    }
  });

1 个答案:

答案 0 :(得分:0)

我在这里找到了解决方案:How to deep watch an array in angularjs?深表/财产观察。

$scope.$watch('columns', function() {
  // some value in the array has changed 
}, true); // watching properties