NgModel - Как бороться с его различным поведением в разных браузерах?
Я пытаюсь разобраться с различным поведением ngModel
в разных браузерах.
Моя директива обертывает jqueryUI autocomplete и в событии select
она вызывает ngModel.$setViewValue(selectedItem.id)
. Autocomplete позволяет пользователю выбирать элемент с помощью мыши или нажатием enter
на клавиатуре.
Если предложенный пункт:
{
"name": "Apple",
"id": "1000"
}
Я ожидаю, что после его выбора значением ngModel будет выбран элемент id
- 1000
.
-
В Chrome он работает нормально - он правильно устанавливает $viewValue
и $modelValue
($modelValue=1000
).
-
В Firefox он устанавливает модель как в Chrome ($modelValue=1000
), но когда я нажимаю где-то еще - размываю (тогда браузер, вероятно, запускает событие change
), модель изменяется и становится такой же, как видимое входное значение ($modelValue='Apple'
).
-
В IE 11 он устанавливает правильную модель только тогда, когда я выбираю элемент с щелчком мыши. Если я выберу его, нажав enter
, модель станет видимым входным значением ($modelValue='Apple'
)
Вот plunkr: http://plnkr.co/edit/o2Jkgprf8EakGqnpu22Y?p=preview
Я хотел бы достичь такого же поведения в каждом браузере. Как бороться с этими проблемами?
Ответы
Ответ 1
Хорошо, я думаю, что сделал это. Решение основано на комментарии Yoshi и использует локальную модель для хранения выбранных данных.
Когда пользователь выбирает что-то, для локальной модели установлено значение Object
, а $viewValue
- текстовое значение выбранного элемента. Затем парсер устанавливает свойство id
локальной модели как $modelValue
.
select: function(event, ui) {
if(ui.item && ui.item.data){
model = ui.item.data
ngModel.$setViewValue(model.name);
scope.$apply();
}
}
ngModel.$parsers.push(function(value) {
if(_.isObject(model) && value!==model.name){
model = value;
return model;
}
return model.id;
});
Функция Parser также делает одну важную вещь. Поскольку он запускается, когда пользователь вводит что-то или в событии change
(это была проблема в firefox!), Он проверяет, совпадает ли значение с текущим значением текстовой локальной модели, и если оно не изменяет локальную модель на это значение. Это означает, что если функция парсера запускается change
, значение события будет таким же, как текстовое значение, поэтому $modelValue
не изменяется, но если пользователь вводит что-то модель, обновляется до типизированного значения (оно становится String
).
Функция Validator проверяет, является ли локальная модель Object
. Если это не означает, что поле недействительно, поэтому по умолчанию его $modelValue
исчезает.
Вот plunkr:
http://plnkr.co/edit/2ZkXFvgLIwDljfJoyeJ1?p=preview
(В функции форматирования я возвращаю то, что приходит, поэтому $viewValue
временно Object
, а затем в методе $render
я вызываю $setViewValue
правильно установить $viewValue
и $modelValue
, поэтому он становится String
Я слышал, что $setViewValue
не должен запускаться в методе $render
, но я не вижу другого способа установить правильный $modelValue
, когда что-то приходит извне).
Ответ 2
Это похоже на http://bugs.jqueryui.com/ticket/8878
Как указано в приведенной выше ссылке, событие изменения запускается только в Firefox, а не в Chrome. Таким образом, в вашем случае $setViewValue снова запускается при нажатии на него, а для значения модели установлено значение "Яблоко".
Существует обратный вызов изменения для автозаполнения jquery ui widget. Возможно, для обработки обоих случаев/браузеров вам придется явно задать значение представления снова при этом обратном вызове (и он работает).
http://plnkr.co/edit/GFxhzwieBJTSL8zjSPSZ?p=preview
link: function(scope, elem, attrs, ngModel) {
elem.on('change', function(){
// This will not be printed in Chrome and only on firefox
console.log('change');
});
select: function(event, ui) {
ngModel.$setViewValue(ui.item.data.id);
scope.$apply();
},
// To handle firefox browser were change event is triggered
// when clicked outside/blur
change: function(event, ui) {
ngModel.$setViewValue(ui.item.data.id);
scope.$apply();
}
Ответ 3
У меня были сходные бои с ngModelController
и $setViewValue
.
В конце концов я искал альтернативные решения.
Один из подходов, который я нашел, который работал довольно хорошо, заключался в создании нового элемента в качестве директивы компонента, который включает в себя входной тег как передаваемый элемент.
app.directive('fruitAutocomplete', function($http) {
return {
restrict: 'E',
require: 'ngModel',
transclude: true,
template: '<ng-transclude></ng-transclude>',
link: function(scope, elem, attrs, ngModelController) {
var $input = elem.find('input');
$input.autocomplete({
...
});
}
}
})
В HTML:
<fruit-autocomplete name="fruit" ng-model="model.fruit">
<input ng-disabled="inputDisabled" placeholder="input fruit"/>
</fruit-autocomplete>
Вот рабочий Plunker
С помощью этого предлагаемого решения вы можете выделить ngModelController
и модальный интерфейс jQueryUI в свой собственный пользовательский элемент, и он не мешает "нормальному" тегу <input>
, и вы не обеспокоены ошибкой jQueryUI.
При использовании тега <input>
в качестве элемента transcluded вы по-прежнему можете использовать большинство входных свойств Angular, таких как ng-disabled
, placeholder
и т.д.