现在这很酷。前几天,我创建了一个小提琴来回答另一个问题,然后才意识到我跑题了。你在这里,终于让我注意到我的回答问题。谢谢!
因此,以下是从另一个组件实现自定义字段所需的步骤:
- 创建子组件
- 渲染子组件
- 确保正确调整子组件的大小和大小
- 获取和设置值
- 中继事件
创建子组件
第一部分,创建组件,很简单。与为任何其他用途创建组件相比,没有什么特别之处。
但是,您必须在父字段的initComponent 方法中创建子字段(而不是在呈现时)。这是因为外部代码可以合理地期望组件的所有依赖对象都在 initComponent 之后实例化(例如,向它们添加侦听器)。
此外,你可以善待自己,在调用 super 方法之前创建孩子。如果您在 super 方法之后创建子项,您可能会在子项尚未实例化时调用您的字段的 setValue 方法(见下文)。
initComponent: function() {
this.childComponent = Ext.create(...);
this.callParent(arguments);
}
如您所见,我正在创建一个组件,这在大多数情况下都是您想要的。但是您也可以想花哨并组合多个子组件。在这种情况下,我认为最好尽快回到众所周知的领域:即创建一个容器作为子组件,并在其中进行组合。
渲染
然后是渲染的问题。起初我考虑使用fieldSubTpl 来渲染一个容器div,并让子组件在其中渲染自己。但是,在这种情况下,我们不需要模板功能,因此我们也可以使用 getSubTplMarkup 方法完全绕过它。
我探索了 Ext 中的其他组件,以了解它们如何管理子组件的呈现。我在 BoundList 及其分页工具栏中找到了一个很好的示例(请参阅code)。所以,为了获取子组件的标记,我们可以结合子组件的getRenderTree方法使用Ext.DomHelper.generateMarkup。
所以,这是我们领域的getSubTplMarkup 的实现:
getSubTplMarkup: function() {
// generateMarkup will append to the passed empty array and return it
var buffer = Ext.DomHelper.generateMarkup(this.childComponent.getRenderTree(), []);
// but we want to return a single string
return buffer.join('');
}
现在,这还不够。 code of 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 列表字段。
你可以看到它live on jsFiddle,在这里我试图表明该字段的行为是有序的。
Ext.define('My.form.field.TodoList', {
// Extend from Ext.form.field.Base for all the label related business
extend: 'Ext.form.field.Base'
,alias: 'widget.todolist'
// --- Child component creation ---
,initComponent: function() {
// Create the component
// This is better to do it here in initComponent, because it is a legitimate
// expectationfor external code that all dependant objects are created after
// initComponent (to add listeners, etc.)
// I will use this.grid for semantical access (value), and this.childComponent
// for generic issues (rendering)
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 // initial state
,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));
}
}
});
// field events
this.grid.store.on({
scope: this
,datachanged: this.checkChange
});
this.callParent(arguments);
}
// --- Rendering ---
// Generates the child component markup and let Ext.form.field.Base handle the rest
,getSubTplMarkup: function() {
// generateMarkup will append to the passed empty array and return it
var buffer = Ext.DomHelper.generateMarkup(this.childComponent.getRenderTree(), []);
// but we want to return a single string
return buffer.join('');
}
// Regular containers implements this method to call finishRender for each of their
// child, and we need to do the same for the component to display smoothly
,finishRenderChildren: function() {
this.callParent(arguments);
this.childComponent.finishRender();
}
// --- Resizing ---
// This is important for layout notably
,onResize: function(w, h) {
this.callParent(arguments);
this.childComponent.setSize(w - this.getLabelWidth(), h);
}
// --- Value handling ---
// This part will be specific to your component of course
,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(',');
}
});