Ответ 1
Подход:
Я бы решил эту проблему, используя Ember Component. Я использовал компонент, потому что это будет:
-
Легко повторное использование
-
Код является самодостаточным и не имеет внешних требований к любому другому вашему коду.
-
Мы можем использовать простой javascript для создания представления. Обычный javascript должен облегчить понимание потока кода (потому что вам не нужно знать, что делает Ember с расширенными объектами за кулисами), и у него будет меньше накладных расходов.
Демо:
Я создал здесь JSBin здесь, код ниже.
Использование
Добавьте к шаблону дескрипторов:
{{select-filter-box data=model selected=selected}}
Создайте тэг select-filter-box
, а затем привяжите свою модель к атрибуту data
и вашему массиву значений selected
к атрибуту selected
.
Приложение:
App = Ember.Application.create();
App.ApplicationController = Ember.ObjectController.extend({
model: DATA.model_data,
selected: ['Author','']
});
App.SelectFilterBoxComponent = Ember.Component.extend({
template: Ember.Handlebars.compile(''), // Blank template
data: null,
lastCount: 0,
selected: [],
selectedChanged: function(){
// Properties required to build view
var p = this.getProperties("elementId", "data", "lastCount", "selected");
// Used to gain context of controller in on selected changed event
var controller = this;
// Check there is at least one property. I.e. the base model.
var length = p.selected.length;
if(length > 1){
var currentModelName = p.selected[0];
var type = {};
// This function will return an existing select box or create new
var getOrCreate = function(idx){
// Determine the id of the select box
var id = p.elementId + "_" + idx;
// Try get the select box if it exists
var select = $("#" + id);
if(select.length === 0){
// Create select box
select = $("<select id='" + id +"'></select>");
// Action to take if select is changed. State is made available through evt.data
select.on("change", { controller: controller, index: idx }, function(evt){
// Restore the state
var controller = evt.data.controller;
var index = evt.data.index;
var selected = controller.get("selected");
// The selected field
var fieldName = $(this).val();
// Update the selected
selected = selected.slice(0, index);
selected.push(fieldName);
controller.set("selected", selected);
});
// Add it to the component container
$("#" + p.elementId).append(select);
}
return select;
};
// Add the options to the select box
var populate = function(select){
// Only populate the select box if it doesn't have the correct model
if(select.data("type")==currentModelName)
return;
// Clear any existing options
select.html("");
// Get the field from the model
var fields = p.data[currentModelName].fields;
// Add default empty option
select.append($("<option value=''>------</option>"));
// Add the fields to the select box
for(var f = 0; f < fields.length; f++)
select.append($("<option>" + fields[f] + "</option>"));
// Set the model type on the select
select.data("type", currentModelName);
};
var setModelNameFromFieldName = function(fieldName){
// Get the field type from current model meta
type = p.data[currentModelName].meta[fieldName];
// Set the current model
currentModelName = (type !== undefined && type.model !== undefined) ? type.model : null;
};
// Remove any unneeded select boxes. I.e. where the number of selects exceed the selected length
if(p.lastCount > length)
for(var i=length; i < p.lastCount; i++)
$("#" + p.elementId + "_" + i).remove();
this.set("lastCount", length);
// Loop through all of the selected, to build view
for(var s = 1; s < length; s++)
{
// Get or Create select box at index s
var select = getOrCreate(s);
// Populate the model fields to the selectbox, if required
populate(select);
// Current selected
var field = p.selected[s];
// Ensure correct value is selected
select.val(field);
// Set the model for next iteration
setModelNameFromFieldName(field);
if(s === length - 1 && type !== undefined && type.model !== undefined)
{
p.selected.push('');
this.notifyPropertyChange("selected");
}
}
}
}.observes("selected"),
didInsertElement: function(){
this.selectedChanged();
}
});
Как это работает
Компонент принимает два параметра model
и selected
, затем привязывает наблюдателя к свойству selected
. Каждый раз, когда выбор изменяется либо через взаимодействие пользователя с полями выбора, либо с помощью свойства, связанного с selected
, представление будет переопределено.
В коде используется следующий подход:
-
Определите, является ли массив выбора (
selected
) более 1. (поскольку первым значением должна быть базовая модель). -
Прокрутите все выбранные поля
i
, начиная с индекса 1.- Определите, существует ли флажок
i
. Если не создать поле выбора. - Определите, имеет ли поле выбора
i
правильные поля моделей, основанные на текущей заполненной модели. Если да, ничего не делайте, если не заполняете поля. - Задайте текущее значение поля выбора.
- Если мы последний поле выбора и поле выбрали ссылки на модель, затем нажмите пустое значение на выбор, чтобы вызвать следующий снимок.
- Определите, существует ли флажок
-
При создании окна выбора обработчик
onchange
подключается, чтобы обновить значениеselected
, разрезав массивselected
справа от текущего индекса и добавив его собственное значение. Это приведет к необходимости изменения представления. -
Свойство
count
отслеживает предыдущую длинуselected
, поэтому, если сделано изменение к выбору, которое уменьшает текущую длину значенийselected
, то ненужные поля выбора могут быть удалены.
Исходный код прокомментирован, и я надеюсь, что это понятно, если у вас есть какие-либо вопросы с запросами, как это работает, не стесняйтесь спрашивать, и я попытаюсь объяснить это лучше.
Ваша модель:
Посмотрев на свою модель, рассмотрели ли вы ее упрощение ниже? Я ценю, что вы, возможно, не сможете, по другим причинам, выходящим за рамки вопроса. Просто мысль.
DATA.model_data = {
"Book": {
"id": {},
"title": {},
"publication_year": {},
"authors": { "model": "Author" }
},
"Author": {
"id": {},
"first_name": {},
"last_name": {},
"books": { "model": "Book" }
}
};
Таким образом, имена полей будут считаны с ключами объекта, а значение будет метаданных.
Надеюсь, вы сочтете это полезным. Сообщите мне, есть ли у вас какие-либо вопросы или проблемы.
Контроллер:
С этим компонентом вы можете использовать любой контроллер. В моей демонстрации компонента я использовал Ember, встроенный в ApplicationController
для простоты.
Объяснение notifyPropertyChange()
:
Это вызвано тем, что когда мы вставляем новую строку в массив selected
, используя функциональность push
массивов.
Я использовал метод push
, потому что это самый эффективный способ добавить новую запись в существующий массив.
В то время как у Ember есть метод pushObject
, который также должен заботиться об уведомлении, я не смог его получить почитай это. Поэтому this.notifyPropertyChange("selected");
сообщает Ember, что мы обновили массив. Однако я надеюсь, что это не разбойник.
Альтернатива Ember Component - реализована как вид
Если вы не хотите использовать его в формате Component, вы можете реализовать его как представление. Это в конечном счете достигает одной и той же цели, но это может быть более знакомым шаблоном проектирования для вас.
См. этот JSBin для реализации в виде представления. Я не буду включать полный код здесь, потому что некоторые из них такие же, как и выше, вы можете увидеть его в JSBin
Использование:
Создайте экземпляр App.SelectFilterBoxView
с контроллером, у которого есть свойство data
и selected
:
var myView = App.SelectFilterBoxView.create({
controller: Ember.Object.create({
data: DATA.model_data,
selected: ['Author','']
})
});
Затем добавьте представление по мере необходимости, например #main
.
myView.appendTo("#main");