我正在尝试创建一个用于遍历关系数据库中的表的接口。每个选择代表一列。如果列是外键,则向右添加新选择。这会持续发生在用户访问的每个外键上。选择的数量是动态的。
我制作了一个错误的实现,其中包含手动添加和删除选择视图的代码。我认为它可能会被更好的Ember代码替换(某些数组对象可能?),我只是不确定如何最好地使用框架来解决这个问题。
这是我的JSBin http://jsbin.com/olefUMAr/3/edit
HTML:
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Ember template" />
<meta charset=utf-8 />
<title>JS Bin</title>
<script src="http://code.jquery.com/jquery-1.9.0.js"></script>
<script src="http://builds.emberjs.com/handlebars-1.0.0.js"></script>
<script src="http://builds.emberjs.com/tags/v1.1.2/ember.js"></script>
</head>
<body>
<script type="text/x-handlebars" data-template-name="my_template">
{{view fieldSelects}}
</script>
<div id="main"></div>
</body>
</html>
JavaScript的:
App = Ember.Application.create();
var TemplatedViewController = Ember.Object.extend({
templateFunction: null,
viewArgs: null,
viewBaseClass: Ember.View,
view: function () {
var controller = this;
var viewArgs = this.get('viewArgs') || {};
var args = {
template: controller.get('templateFunction'),
controller: controller
};
args = $.extend(viewArgs, args);
return this.get('viewBaseClass').extend(args);
}.property('templateFunction', 'viewArgs'),
appendView: function (selector) {
this.get('view').create().appendTo(selector);
},
appendViewToBody: function () {
this.get('view').create().append();
}
});
var DATA = {};
DATA.model_data = {
"Book": {
"fields": [
"id",
"title",
"publication_year",
"authors"
],
"meta": {
"id": {},
"title": {},
"publication_year": {},
"authors": {
"model": "Author"
}
}
},
"Author": {
"fields": [
"id",
"first_name",
"last_name",
"books"
],
"meta": {
"id": {},
"first_name": {},
"last_name": {},
"books": {
"model": "Book"
}
}
}
};
var Controller = TemplatedViewController.extend({
view: function () {
var controller = this;
return this.get('viewBaseClass').extend({
controller: controller,
templateName: 'my_template'
});
}.property(),
selectedFields: null,
fieldSelects: function () {
var filter = this;
return Ember.ContainerView.extend({
controller: this,
childViews: function () {
var that = this;
var selectedFields = filter.get('selectedFields');
var ret = [];
var model = 'Book';
selectedFields.forEach(function (item, index, enumerable) {
var selection = item;
if (model) {
var select = that.makeSelect(model, that.getPositionIndex(), selection, true).create();
ret.pushObject(select);
model = DATA.model_data[model].meta[selection].model;
}
});
return ret;
}.property(),
nextPositionIndex: 0,
incrementPositionIndex: function () {
this.set('nextPositionIndex', this.get('nextPositionIndex') + 1);
},
getPositionIndex: function () {
var index = this.get('nextPositionIndex');
this.incrementPositionIndex();
return index;
},
setNextPositionIndex: function (newValue) {
this.set('nextPositionIndex', newValue+1);
},
makeSelect: function (modelName, positionIndex, selection, isInitializing) {
var view = this;
return Ember.Select.extend({
positionIndex: positionIndex,
controller: filter,
content: DATA.model_data[modelName].fields,
prompt: '---------',
selection: selection || null,
selectionChanged: function () {
var field = this.get('selection');
// Remove child views after this one
var lastIndex = view.get('length') - 1;
if (lastIndex > this.get('positionIndex')) {
view.removeAt(this.get('positionIndex')+1, lastIndex-this.get('positionIndex'));
view.setNextPositionIndex(this.get('positionIndex'));
}
if (! isInitializing && DATA.model_data[modelName].meta[field].model) {
var relatedModel = DATA.model_data[modelName].meta[field].model;
view.pushObject(view.makeSelect(relatedModel, view.getPositionIndex()).create());
}
// Reset ``isInitializing`` after the first run
if (isInitializing) {
isInitializing = false;
}
var selectedFields = [];
view.get('childViews').forEach(function (item, index, enumerable) {
var childView = item;
var selection = childView.get('selection');
selectedFields.pushObject(selection);
});
filter.set('selectedFields', selectedFields);
}.observes('selection')
});
}
});
}.property()
});
var controller = Controller.create({
selectedFields: ['authors', 'first_name']
});
$(function () {
controller.appendView('#main');
});
答案 0 :(得分:5)
我会使用Ember Component来解决这个问题。 我使用了一个组件,因为它将是:
轻松重复使用
代码是自包含的,对您的任何其他代码没有外部要求。
我们可以使用普通的javascript来创建视图。简单的javascript应该使代码流更容易理解(因为您不必知道Ember在幕后使用扩展对象做了什么),并且它将减少开销。
我在下面的代码中创建了此JSBin here。
添加到您的车把模板:
{{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.(因为第一个值必须是基本模型)。
从索引1开始循环所有选定的字段i
。
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我们更新了数组。 但是我希望这不是一个破坏者。
如果您不希望以组件格式使用它,则可以将其实现为视图。它最终实现了相同的目标,但这对您来说可能是一种更熟悉的设计模式。
See this JSBin for implementation as a View。 我不会在这里包含完整的代码,因为其中一些与上面相同,你可以在JSBin中看到它
使用具有App.SelectFilterBoxView
和data
属性的控制器创建selected
的实例:
var myView = App.SelectFilterBoxView.create({
controller: Ember.Object.create({
data: DATA.model_data,
selected: ['Author','']
})
});
然后根据需要附加视图,例如#main
。
myView.appendTo("#main");
答案 1 :(得分:1)
不幸的是,即使在您的JSFiddle中添加Ember作为库之后,您的代码也无法运行,但ContainerView
可能正是您所寻找的:http://emberjs.com/api/classes/Ember.ContainerView.html因为这些视图可以动态添加/除去。
答案 2 :(得分:0)
this.$().remove()
或this.$().append()
可能是您正在寻找的内容: