2015-02-09 3 views
0

Я создал пользовательскую директиву для рендеринга слайдера для вопроса (по существу, обертывания jquery ui slider). Директива принимает ngModel и обновляет ее, когда пользователь использует слайдер, и к родительской модели прикрепляется $ watch (ngModel, переданный директиве, является только частью родительской модели). Директива имеет несколько экземпляров на странице.Несколько экземпляров пользовательской директивы, путаница с ngModel

Я столкнулся с проблемой с часами, так как кажется, что часы всегда возникают по последнему вопросу на странице. Так, например, страница с 10 вопросами, используя слайдер на вопросе 1, вызывает триггеры последнего вопроса (вопрос 10). Я считаю, что проблема имеет какое-то отношение к директивам/изолированной сфере действия и/или функции часов, но я не могу ее решить.

this.app.directive('questionslider',() => { 
    var onChangeEvent = (event, ui) => { 
     updateModel(ui.value); 
    }; 

    var onSlideEvent = (event, ui) => { 
     updateUi(event, ui); 
    }; 

    var updateUi = (event, ui) => { 
     $(ui.handle).find(".ff-handle-glyph > div").css("top", (ui.value) * - 10); 
    } 

    var updateModel = (newValue) => { 
     // find value in values list... 
     angular.forEach(isolatedScope.model.PossibleValues, function(value) { 
      if (parseInt(value.Name) === newValue) { 
       isolatedScope.$apply(function() { 
        isolatedScope.model.Value = value; 
       }); 
      } 
     }); 
    }; 

    var isolatedScope: any; 

    return { 
     restrict: 'AE', 
     replace: true, 
     template: '<div></div>', 
     scope: { 
      model: '=ngModel', 
     }, 
     link: function(scope, element, attrs, ctrl) { 
      isolatedScope = scope; 
      scope.$watch(ngModelCtrl, function() { 
       // use provided defaultValue if model is empty 
       var value = isolatedScope.model.Value === null ? isolatedScope.model.DefaultValue : isolatedScope.model.Value; 

       element.slider({ 
        min: 0, 
        max: isolatedScope.model.PossibleValues.length, 
        value: value.Name, 
        change: onChangeEvent, 
        slide: onSlideEvent 
       }); 
      } 
     } 
    }; 
}; 

код, чтобы добавить часы в контроллере

this.$scope.questions.forEach(function(question) { 
    this.$scope.$watch(
     function() { return question; }, 
     function(newVal, oldVal) { this.updateQuestion(newVal, oldVal) }, 
     true 
    ); 
}); 

UpdateQuestion функции (теперь просто выводит текущий вопрос)

function updateQuestion(newVal, oldVal) { 
    // prevent event on initial load 
    if (newVal === oldVal) { 
     return; 
    } 

    console.log(newVal); 
} 

В нг-повтор разметки инстанцировании questionsliders

<div data-ng-repeat="question in Questions"> 
    <h4>{{question.QuestionText}}</h4> 
    <p>{{question.RangeMinText}}</p> 
    <questionslider ng-model="question"></questionslider>   
    <p>{{question.RangeMaxText}}</p> 
</div> 

Вопрос JSON будет выглядеть следующим образом

{ 
    "DefaultValue": { 
     "Id": "5", 
     "Name": "5" 
    }, 
    "Id": "1", 
    "IsAnswered": false, 
    "PossibleValues": [ 
     { 
      "Id": "1", 
      "Name": "1" 
     }, 
     { 
      "Id": "2", 
      "Name": "2" 
     }, 
     { 
      "Id": "3", 
      "Name": "3" 
     }, 
     { 
      "Id": "4", 
      "Name": "4" 
     }, 
     { 
      "Id": "5", 
      "Name": "5" 
     }, 
     { 
      "Id": "6", 
      "Name": "6" 
     }, 
     { 
      "Id": "7", 
      "Name": "7" 
     }, 
     { 
      "Id": "8", 
      "Name": "8" 
     }, 
     { 
      "Id": "9", 
      "Name": "9" 
     }, 
     { 
      "Id": "10", 
      "Name": "10" 
     } 
    ], 
    "QuestionText": "hows it haning?", 
    "RangeMaxText": "not good", 
    "RangeMinText": "Very good", 
    "Type": 0, 
    "Value": null 
} 
], 
"Title": "Question title", 
"Type": 0 
} 

Так вопрос не является, независимо от того, какой вопрос я обновляю с директивой слайдера, это всегда последний на странице перешла в updateQuestion.

ОБНОВЛЕНИЕ Я пробовал использовать $ watchCollection, но ничего не срабатывает.

this.$scope.$watchCollection(
    'questions', 
    function (newVal, oldVal) { 
     // prevent event on initial load 
     if (newVal === oldVal) { 
      return; 
     } 

     for (var i = 0; i < newVal.length; i++) { 
      if (newVal[i] != oldVal[i]) { 
       this.$log.info("update : " + newVal.Id); 
      } 
     } 
    } 
); 

Я также попытался с

function() { return questions; } 

как первые выражения. Еще не повезло.

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

UPDATE Так я попытался использовать отдельные контроллеры для каждого вопроса, добавив часы на каждый вопрос в контроллере, и странное дело в том, что даже это воспроизводя тот же сценарий. Это еще последний вопрос на странице, переданной в функцию часов.

разметки

<div data-ng-repeat="question in Questions" data-ng-controller="QuestionInstanceController"> 
    <h4>{{question.QuestionText}}</h4> 
    <p>{{question.RangeMinText}}</p> 
    <questionslider ng-model="question"></questionslider>   
    <p>{{question.RangeMaxText}}</p> 
</div> 

Код контроллера

app.controller('QuestionInstanceController', function($scope) { 
     console.log($scope.question); // outputs correct question here! 
     $scope.$watch(
      function() { return $scope.question; }, 
      function(newValue, oldValue) { 
       console.log(newValue); // always outputs last question on page 
      }, 
      true); 
    } 
} 

Он должен иметь что-то делать с моей пользовательской директивы, я как-то я перезаписывать предыдущие случаи, когда наличие нескольких экземпляров на странице?

+0

Вы можете поставить несколько образцов с точки зрения HTML также? –

+0

Я бы также предположил, что присвоение значения в изолированной области действия директивы «ngModel» находится в плохом состоянии и запутанно. –

+0

Привет, Майкл, я добавил разметку. Я думал, что ngModel была лучшей практикой для такого рода, потому что его что-то обновляемое, но это не потому, что это изолированная область, это правильно? – morteng

ответ

0

Так что мне удалось найти решение самостоятельно. Проблема в том, что у меня был «var isolScope»; в директиве, которая была назначена при каждом запуске ссылки(). Я думал, что vars, объявленные в директиве, изолированы на каждом экземпляре, но на самом деле они перезаписываются при каждой реализации директивы. Поэтому

Решения было перенести реализацию OnChange непосредственно к инициализации компоненты слайдера, и, таким образом, избежать необходимости доступа к переменной области позже

this.app.directive('questionslider',() => { 
    var onChangeEvent = (event, ui) => { 

    }; 

    var onSlideEvent = (event, ui) => { 
     updateUi(ui.value, ui.handle); 
    }; 

    var updateUi = (value, element) => { 

    } 

    var onStartSlide = (event, ui) => { 

    } 

    var onEndSlide = (event, ui) => { 

    } 

    return { 
     restrict: 'AE', 
     require: 'ngModel', 
     replace: true, 
     templateUrl: 'templates/questionSlider.html', 
     scope: { 
      model: '=ngModel' 
     }, 
     link: (scope: any, element, attrs, ngModelCtrl: ng.INgModelController) => { 
      scope.$watch(ngModelCtrl,() => { 
       // use provided defaultValue if model is empty 
       var value = scope.model.Value === null ? scope.model.DefaultValue : scope.model.Value; 
       var hasNoValue = scope.model.Value === null; 

       if (hasNoValue) { 
        element.find('.ui-slider-handle').addClass('dg-notset'); 
       } 

       element.slider({ 
        min: parseInt(scope.model.PossibleValues[0].Name), 
        max: scope.model.PossibleValues.length, 
        value: parseInt(value.Name), 
        slide: onSlideEvent, 
        start: onStartSlide, 
        stop: onEndSlide, 
        animate: true, 
        change: (event, ui) => { 
         // find value in values list... 
         angular.forEach(scope.model.PossibleValues, (val) => { 
          if (parseInt(val.Name) === ui.value) { 
           scope.$apply(() => { 
            scope.model.Value = val; 
           }); 
          } 
         }); 

         onChangeEvent(event, ui); 
        } 
       }); 
      }); 
     } 
    }; 
}); 
0

Я ожидаю, что ваша логика $ scope. $ Watch отбросит вас.Самый простой способ может быть поставить часы на весь массив вопросов, используя $ watchCollection:

$scope.$watchCollection(questions, function(newValue, oldValue) { 
    for(index=0; index<newValue.length; index++) { 
     if (newValue[index] !== oldValue[index]) { 
      console.log("Question " + index + " updated to: " + newValue[index]); 
     } 
    } 
}); 

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

app.controller('QuestionCtrl', function($scope) { 
    $scope.$watch('question', function(newValue, oldValue) { 
      if (newVal === oldVal) { 
       return; 
      } 

      console.log(newVal); 
    } 
}); 

С вашей точки зрения слегка модифицированном включить ссылку на новую QuestionCtrl:

<div data-ng-repeat="question in Questions" ng-controller='QuestionCtrl'> 
    <h4>{{question.QuestionText}}</h4> 
    <p>{{question.RangeMinText}}</p> 
    <questionslider ng-model="question"></questionslider>   
    <p>{{question.RangeMaxText}}</p> 
</div> 

This article дает дополнительную информацию об использовании контроллеров с нг-повторе ,

Я надеюсь, что одно из них поможет вам.

+0

Я уточнил вопрос с моими выводами с этими предложениями выше. – morteng

Смежные вопросы