Angularjs Custom select2 директива
Я создал простую пользовательскую директиву AngularJs для этого удивительного плагина jquery jQuery-Select2 следующим образом:
Директива
app.directive("select2",function($timeout,$parse){
return {
restrict: 'AC',
link: function(scope, element, attrs) {
$timeout(function() {
$(element).select2();
},200);
}
};
});
Использование в HTML-шаблонах:
<select class="form-control" select2 name="country"
data-ng-model="client.primary_address.country"
ng-options="c.name as c.name for c in client.countries">
<option value="">Select Country</option>
</select>
Он работает как ожидалось, и мой обычный select
элемент заменяется плагинами select2
.
Однако есть одна проблема, хотя иногда она показывает значение по умолчанию i.e Select Country
здесь, хотя в выпадающем меню автоматически выбирается значение модели.
Теперь, если я увеличиваю $timeout
интервал от 200
до некоторого высокого значения, скажем 1500
, он работает, но задерживает рендеринг директивы. Также я думаю, что это не является правильным решением для него, так как мои данные загружаются через ajax.
Я также попытался обновить директиву следующим образом, но в этом не повезло:
app.directive("select2",function($timeout,$parse){
return {
restrict: 'AC',
require: 'ngModel',
link: function(scope, element, attrs) {
var modelAccessor = $parse(attrs.ngModel);
$timeout(function() {
$(element).select2();
});
scope.$watch(modelAccessor, function (val) {
if(val) {
$(element).select2("val",val);
}
});
}
};
});
PS: Я знаю, что есть аналогичный модуль, присутствующий ui-select, но он требует некоторой разной разметки в форме <ui-select></ui-select>
, и мое приложение уже полностью разработано, и я просто хочу заменить стандартный флажок select2.
Итак, можете ли вы, пожалуйста, направить меня, как я могу решить эту проблему и убедиться, что директива поддерживает синхронизацию с последним поведением?
Ответы
Ответ 1
Это может быть проще, чем вы ожидали!
Пожалуйста, посмотрите на Plunker
В принципе, все плагины, часы Angularjs $должны основываться на чем-то. Я не уверен на 100% для jQuery-select2; но я думаю, что просто контроль нормальных событий DOM. (И в случае Angular $watch, это "грязный контрольный цикл" )
Моя идея заключается в том, чтобы позволить доверять jquery-Select2 и AngularJS для обработки этих событий изменений.
Нам просто нужно следить за изменениями в Angular и обновлять выбор в Select2 способами
var refreshSelect = function() {
if (!element.select2Initialized) return;
$timeout(function() {
element.trigger('change');
});
};
//...
scope.$watch(attrs.ngModel, refreshSelect);
Примечание: Я добавил в 2 новых часах, которые, я думаю, вы хотели бы иметь!
Ответ 2
Я не знаком с select2 (поэтому фактический API для получения и установки отображаемого значения в элементе управления может быть некорректным), но я предлагаю это в качестве альтернативы:
app.directive("select2",function($timeout){
return {
restrict: 'AC',
require: 'ngModel',
link: function(scope, element, attrs, model) {
$timeout(function() {
element.select2();
});
model.$render = function() {
element.select2("val",model.$viewValue);
}
element.on('change', function() {
scope.$apply(function() {
model.$setViewValue(element.select2("val"));
});
})
}
};
});
Первый $timeout необходим, потому что вы используете ng-options, поэтому параметры не будут находиться в DOM до следующего цикла дайджест. Проблема в том, что новые параметры не будут добавлены в элемент управления, если модель стран позже будет изменена вашим приложением.
Ответ 3
Angular не понравится, если данные модели будут изменены сторонним плагином. Мое предположение основывается на том, что при использовании $timeout существует условие гонки между Angular обновлением опций или модели и плагина select2. Решение, с которым я столкнулся, состоит в том, чтобы сделать обновление главным образом из рук Angular и сделать это вручную из директивы, таким образом вы можете гарантировать, что все будет соответствовать независимо от того, кто изменяет. В этой директиве я придумал:
app.directive("select2",function($timeout,$parse){
return {
restrict: 'AC',
link: function(scope, element, attrs) {
var options = [],
el = $(element),
angularTriggeredChange = false,
selectOptions = attrs["selectOptions"].split(" in "),
property = selectOptions[0],
optionsObject = selectOptions[1];
// watch for changes to the defining data model
scope.$watch(optionsObject, function(n, o){
var data = [];
// format the options for select2 data interface
for(var i in n) {
var obj = {id: i, text: n[i][property]};
data.push(obj);
}
el.select2({data: data});
// keep local copy of given options
options = n;
}, true);
// watch for changes to the selection data model
scope.$watch(attrs["selectSelection"], function(n, o) {
// select2 is indexed by the array position,
// so we iterate to find the right index
for(var i in options) {
if(options[i][property] === n) {
angularTriggeredChange = true;
el.val(i).trigger("change");
}
}
}, true);
// Watch for changes to the select UI
el.select2().on("change", function(e){
// if the user triggered the change, let angular know
if(!angularTriggeredChange) {
scope.$eval(attrs["selectSelection"]+"='"+options[e.target.value][property]+"'");
scope.$digest();
}
// if angular triggered the change, then nothing to update
angularTriggeredChange = false;
});
}
};
});
Я добавил атрибуты select-options
и select-model
. Они будут использоваться для заполнения и обновления данных с помощью интерфейса select2. Вот пример html:
<select id="sel" class="form-control" select2 name="country"
select-selection="client.primary_address.country"
select-options="name in client.countries" >
<option value="">Select Country</option>
</select>
<div>Selected: {{client.primary_address.country}}</div>
Обратите внимание, что в директиве все еще есть какая-то очистка, и при принятии каких-либо предположений о входных данных, таких как "in" в атрибуте select-options, есть что-то. Он также не применяет атрибуты, а просто терпит неудачу, если они не существуют.
Также обратите внимание, что я использовал Select2 version 4, о чем свидетельствует el.val(i).trigger("change")
. Возможно, вам придется отменить некоторые вещи, если используете более старую версию.
Вот jsfiddle demo директивы в действии.
Ответ 4
Он напрямую не отвечает на ваш вопрос, но, пожалуйста, возьмите его, поскольку есть некоторые люди, которые хотят сделать другой подход, а не придерживаться jQuery select2.
Я создал свой собственный для этой цели, потому что меня не удовлетворяли существующие, которые не соответствуют принципам Angular, сначала HTML.
Это еще рано, но я думаю, что все функции работают во всех современных браузерах.
https://github.com/allenhwkim/angular-autocomplete
Вот примеры
Ответ 5
Я попытался воспроизвести вашу проблему, и, похоже, она работает хорошо. вот скрипка, с которой я пришел:
http://jsfiddle.net/s24gLdgq/
У вас может быть другое поведение в зависимости от версии Angular и/или Select2, которую вы используете, не могли бы вы указать это?
Также, если вы хотите предотвратить мерцание, обязательно скройте тег <select>
по умолчанию, чтобы ничего не отображалось до того, как выйдет элемент select2.
Это также делается в моем jsfiddle с помощью CSS
.form-control { width: 200px; opacity: 0 }