По мотивам топика Chosen: сделай выпадающие списки более дружественными.
Довольно симпатичный виджет, иногда даже может быть полезен (допустим когда есть определенные требования к дизайну). Но в данный момент виджет не позволят добавлять позиции динамически, что возмутило товарища alexsrdk, да и меня тоже :) Сейчас попробуем это дело исправить.
Исходники представлены в сыром виде, писались быстро, думаю там есть что рефакторить, я не стал тратить на это время, так как статья написана в образовательных целях, показать подход к решению задачи (что впрочем не исключает возможности практического использования).
В jQuery версии я не нашел способа добраться до основного класса виджета «Chosen», поэтому пришлось немного патчить основной исходник виджета (понимаю что это плохой подход).
Вариантов добраться до класса Chosen можно придумать много, мне в голову приходит как минимуму 3:
Довольно симпатичный виджет, иногда даже может быть полезен (допустим когда есть определенные требования к дизайну). Но в данный момент виджет не позволят добавлять позиции динамически, что возмутило товарища alexsrdk, да и меня тоже :) Сейчас попробуем это дело исправить.
Исходники представлены в сыром виде, писались быстро, думаю там есть что рефакторить, я не стал тратить на это время, так как статья написана в образовательных целях, показать подход к решению задачи (что впрочем не исключает возможности практического использования).
Вариант с jQuery версией
Патчим код виджета
В jQuery версии я не нашел способа добраться до основного класса виджета «Chosen», поэтому пришлось немного патчить основной исходник виджета (понимаю что это плохой подход).
Вариантов добраться до класса Chosen можно придумать много, мне в голову приходит как минимуму 3:
- Изменить тело кастом функции jQuery.fn.chosen подобным образом
$.fn.extend({
chosen: function(data, options) {
var createdInstances = [];
$(this).each(function(input_field) {
if (!($(this)).hasClass("chzn-done")) {
createdInstances.push(new Chosen(this, data, options));
}
});
return createdInstances;
}
});
* This source code was highlighted with Source Code Highlighter.
- Можно сохранять созданный экземпляр в хранилище элемента
$.fn.extend({
chosen: function(data, options) {
return $(this).each(function(input_field) {
if (!($(this)).hasClass("chzn-done")) {
return $(this).data('chosenInstance', new Chosen(this, data, options));
}
});
}
});
* This source code was highlighted with Source Code Highlighter.
Получить объект Chosen можно подобным образом
var createdChosenInstance = $('#bears_multiple').chosen().data('chosenInstance');
* This source code was highlighted with Source Code Highlighter.
- Можно сделать отдельную функцию на получения класса
$.fn.extend({
chosenClass: function() {
return Chosen;
}
});
* This source code was highlighted with Source Code Highlighter.
Библиотекой jQuery пользуюсь очень редко (в основном работаю с YUI), с API под рукой, поэтому вероятно эти варианты не оптимальны.
В последующем коде используется третий вариант доступа к классу виджета. Модификация класса Chosen будет происходить на уровне prototype (методы будут общими, то есть изменения коснуться всех вновь создаваемых экземпляров класса). В принципе можно расширять уже созданные объекты (получая созданные объекты по вариантам 1 или 2), но если изменения должны коснуться всех виджетов, лучше работать с прототипом.
Основной код расширения функционала jQuery версии виджета
(function($, ChosenClass) {
var dynamicItemInstance;
function DynamicItem(chosenInstance) {
$((this.chosen = chosenInstance).search_results).parent().prepend(
this.elContainer = $(document.createElement('div')));
this.elContainer.addClass('chzn-results-additemcontainer');
this.elContainer.append(this.elButton = $(document.createElement('button')));
this.elButton.click($.proxy(this.addNewItem, this));
}
DynamicItem.prototype = {
constructor: DynamicItem,
show: function() {
var data = this.chosen.results_data,
text = this.text,
isNotSelected = true;
if (this.chosen.choices) {
(this.chosen.search_choices.find("li.search-choice").each(function(el) {
var itemIdx = this.id.substr(this.id.lastIndexOf("_") + 1),
item = data[itemIdx];
if (item.value === text) {
isNotSelected = !isNotSelected;
return false;
}
}));
}
this.elContainer[isNotSelected ? 'show' : 'hide']();
},
update: function(terms) {
if ((this.text = terms).length) {
this.elButton.text('Add new item "' + this.text + '"');
this.show();
} else {
this.elContainer.hide();
}
},
addNewItem: function(terms) {
this.chosen.form_field.options.add(new Option(this.text, this.text));
this.chosen.form_field_jq.trigger('liszt:updated');
this.chosen.result_highlight = this.chosen.search_results.children().last();
return this.chosen.result_select();
}
};
$.extend(ChosenClass.prototype, {
no_results: (function(fnSuper) {
return function(terms) {
(dynamicItemInstance || (dynamicItemInstance = new DynamicItem(this))).update(terms);
return fnSuper.call(this, terms);
};
})(ChosenClass.prototype.no_results),
results_hide: (function(fnSuper) {
return function() {
dynamicItemInstance && dynamicItemInstance.elContainer.hide();
return fnSuper.call(this);
};
})(ChosenClass.prototype.results_hide),
winnow_results_set_highlight: (function(fnSuper) {
return function() {
dynamicItemInstance && dynamicItemInstance.elContainer.hide();
return fnSuper.apply(this, arguments);
};
})(ChosenClass.prototype.winnow_results_set_highlight)
});
})(jQuery, jQuery.fn.chosenClass());
* This source code was highlighted with Source Code Highlighter.
Немного проанализировав исходники виджета, прикинул как можно внедрить динамическое добавление позиций. Сhosen методы пееропределены с использованием замыканий, функционал добавления позиции в отдельном классе. Конечно при необходимости кнопку можно заменить на ссылку, добавить CSS стилей (маркерующий класс на блок кнопки установлен) и так далее.
Демо
Вариант с Prototype версией
В Prototype версии виджета класс Chosen доступен глобально (window.Chosen) поэтому патчить ничего не пришлось.
(function(Chosen) {
var dynamicItemInstance;
function DynamicItem(chosenInstance) {
(this.chosen = chosenInstance).search_results.up().insert({
top: this.elContainer = $(document.createElement('div'))
});
this.elContainer.addClassName('chzn-results-additemcontainer');
this.elContainer.insert(this.elButton = $(document.createElement('button')));
Event.observe(this.elButton, 'click', this.addNewItem.bind(this));
}
DynamicItem.prototype = {
constructor: DynamicItem,
show: function() {
var data = this.chosen.results_data,
text = this.text,
isNotSelected = true;
if (this.chosen.choices) {
(this.chosen.search_choices.select("li.search-choice").each(function(el) {
var itemIdx = el.id.substr(el.id.lastIndexOf("_") + 1),
item = data[itemIdx];
if (item.value === text) {
isNotSelected = !isNotSelected;
return false;
}
}));
}
this.elContainer[isNotSelected ? 'show' : 'hide']();
},
update: function(terms) {
if ((this.text = terms).length) {
this.elButton.update('Add new item "' + this.text + '"');
this.show();
} else {
this.elContainer.hide();
}
},
addNewItem: function(terms) {
this.chosen.form_field.options.add(new Option(this.text, this.text));
Event.fire(this.chosen.form_field, "liszt:updated");
this.chosen.result_highlight = this.chosen.search_results.childElements().pop();
return this.chosen.result_select();
}
};
Chosen.prototype.no_results = (function(fnSuper) {
return function(terms) {
(dynamicItemInstance || (dynamicItemInstance = new DynamicItem(this))).update(terms);
return fnSuper.call(this, terms);
};
})(Chosen.prototype.no_results);
Chosen.prototype.results_hide = (function(fnSuper) {
return function() {
dynamicItemInstance && dynamicItemInstance.elContainer.hide();
return fnSuper.call(this);
};
})(Chosen.prototype.results_hide);
Chosen.prototype.winnow_results_set_highlight = (function(fnSuper) {
return function() {
dynamicItemInstance && dynamicItemInstance.elContainer.hide();
return fnSuper.apply(this, arguments);
};
})(Chosen.prototype.winnow_results_set_highlight);
})(window.Chosen);
new Chosen($('bears_multiple'));
* This source code was highlighted with Source Code Highlighter.
Демо
Замечания
На месте автора виджета, я бы реализовывал общее ядро (вместо отдельных версий для Prototype/jQuery/Mootools). Из библиотек использовал бы только самые необходимые методы, через обертку (интерфейс). Цель этого — иметь одну (общую) версию основного кода виджета. Сейчас, при теперешнем подходе форков под разные фреймворки, не вижу большого смысла комитить что-то на github.
Благодарю всех накидавших кармы, это позволило опубликовать статью. - Можно сделать отдельную функцию на получения класса