现在这很酷。前几天,我创建了一个小工具来回答另一个问题,后来意识到我离题了。而你,最终让我注意到了我的回答的问题。谢谢!
那么,在实现来自其他组件的自定义字段时需要执行以下步骤:
- 创建子组件
- 渲染子组件
- 确保子组件的大小调整正确
- 获取和设置值
- 中继事件
创建子组件
首先,创建组件很容易。与为任何其他用途创建组件相比,没有什么特别之处。
但是,您必须在父字段的 initComponent
方法中创建子代(而不是在呈现时创建)。这是因为外部代码可以合法地期望在 initComponent
之后实例化组件的所有依赖对象(例如,向它们添加侦听器)。
此外,您可以在调用超级方法之前创建子项。如果在超级方法之后创建子项,则可能会在尚未实例化子项时调用字段的 setValue
方法(请参见下文)。
initComponent: function() {
this.childComponent = Ext.create(...);
this.callParent(arguments);
}
如你所见,我正在创建一个单一组件,这在大多数情况下是您想要的。但是您也可以想要变得花哨并组合多个子组件。在这种情况下,我认为尽快回到熟悉的领域是明智的:即创建一个容器作为子组件,并在其中进行组合。
渲染
接下来就是渲染问题。起初,我考虑使用fieldSubTpl
来呈现一个容器div,并让子组件在其中呈现自己。然而,在这种情况下,我们不需要模板功能,因此我们可以完全绕过它,使用getSubTplMarkup
方法。
我研究了Ext中的其他组件,以了解它们如何管理子组件的呈现。在BoundList及其分页工具栏(请参见
code)中,我找到了一个很好的例子。因此,为了获取子组件的标记,我们可以使用
Ext.DomHelper.generateMarkup
与子组件的
getRenderTree
方法相结合。
因此,这是我们字段的
getSubTplMarkup
实现:
getSubTplMarkup: function() {
var buffer = Ext.DomHelper.generateMarkup(this.childComponent.getRenderTree(), []);
return buffer.join('');
}
现在,这还不够。
BoundList的代码告诉我们组件渲染中还有另一个重要的部分:调用子组件的
finishRender()
方法。幸运的是,我们的自定义字段将在需要时调用其自己的
finishRenderChildren
方法。
finishRenderChildren: function() {
this.callParent(arguments);
this.childComponent.finishRender();
}
大小调整
现在我们的子项已经渲染到正确的位置,但它不会考虑父元素的大小。这对于表单字段来说尤其令人烦恼,因为这意味着它不会遵守锚定布局。
这很容易解决,我们只需要在父元素调整大小时调整子元素的大小即可。根据我的经验,自从Ext3以来,这方面已经得到了极大的改善。在这里,我们只需要不要忘记标签的额外空间:
onResize: function(w, h) {
this.callParent(arguments);
this.childComponent.setSize(w - this.getLabelWidth(), h);
}
处理数值
这一部分当然取决于您的子组件和您正在创建的字段。此外,从现在开始,只需要以常规方式使用您的子组件,所以我不会详细说明这一部分。
最少,您还需要实现字段的getValue
和setValue
方法。这将使表单的getFieldValues
方法工作,并足以从表单中加载/更新记录。
为了处理验证,您必须实现getErrors
。为了完善这个方面,您可能想添加一些CSS规则来视觉表示字段的无效状态。
如果您希望在实际表单提交(而不是使用AJAX请求)时,使您的字段可用于表单中,则需要getSubmitValue
返回一个可以转换为字符串而不会损坏的值。
除此之外,据我所知,您不必担心Ext.form.field.Base
引入的概念或raw value,因为它仅用于处理实际输入元素中的值的表示。对于我们的Ext组件作为输入,我们已经远离了这条路!
事件
您的最后一项工作将是为您的字段实现事件。您可能希望触发Ext.form.field.Field
的三个事件,即change
, dirtychange
和validitychange
。
再次强调,实现方式非常具体化,取决于您使用的子组件,说实话,我还没有深入探索这个方面。所以我会让您自己进行连接。
然而,我的初步结论是,Ext.form.field.Field
提供了为您完成所有繁重工作的功能,只要(1)在需要时调用checkChange
,并且(2)isEqual
的实现与您的字段值格式相匹配。
示例:TODO列表字段
最后,这里有一个完整的代码示例,使用网格来表示TODO列表字段。
您可以在jsFiddle上实时查看,我试图展示该字段的有序行为。
Ext.define('My.form.field.TodoList', {
extend: 'Ext.form.field.Base'
,alias: 'widget.todolist'
,initComponent: function() {
this.grid = this.childComponent = Ext.create('Ext.grid.Panel', {
hideHeaders: true
,columns: [{dataIndex: 'value', flex: 1}]
,store: {
fields: ['value']
,data: []
}
,height: this.height || 150
,width: this.width || 150
,tbar: [{
text: 'Add'
,scope: this
,handler: function() {
var value = prompt("Value?");
if (value !== null) {
this.grid.getStore().add({value: value});
}
}
},{
text: "Remove"
,itemId: 'removeButton'
,disabled: true
,scope: this
,handler: function() {
var grid = this.grid,
selModel = grid.getSelectionModel(),
store = grid.getStore();
store.remove(selModel.getSelection());
}
}]
,listeners: {
scope: this
,selectionchange: function(selModel, selection) {
var removeButton = this.grid.down('#removeButton');
removeButton.setDisabled(Ext.isEmpty(selection));
}
}
});
this.grid.store.on({
scope: this
,datachanged: this.checkChange
});
this.callParent(arguments);
}
,getSubTplMarkup: function() {
var buffer = Ext.DomHelper.generateMarkup(this.childComponent.getRenderTree(), []);
return buffer.join('');
}
,finishRenderChildren: function() {
this.callParent(arguments);
this.childComponent.finishRender();
}
,onResize: function(w, h) {
this.callParent(arguments);
this.childComponent.setSize(w - this.getLabelWidth(), h);
}
,setValue: function(values) {
var data = [];
if (values) {
Ext.each(values, function(value) {
data.push({value: value});
});
}
this.grid.getStore().loadData(data);
}
,getValue: function() {
var data = [];
this.grid.getStore().each(function(record) {
data.push(record.get('value'));
});
return data;
}
,getSubmitValue: function() {
return this.getValue().join(',');
}
});