我想使用其中的其他ExtJS组件(例如TreePanel)创建自定义ExtJS 表单字段组件。我怎样才能最轻松地完成这项工作?
我已阅读Ext.form.field.Base的文档,但我不想按fieldSubTpl
定义字段正文。我只想编写创建ExtJS组件的代码,也可以编写一些获取和设置值的代码。
更新:汇总目的如下:
这个新组件应该适合 将GUI表示为字段。应该有 标签和相同的对齐方式(标签, 其他领域没有必要 进一步黑客攻击。
可能,我有 写一些getValue,setValue 逻辑。我宁愿将它嵌入到这个组件中,而不是将分离的代码复制到我必须管理的更隐藏的表单字段中。
答案 0 :(得分:27)
为了扩展@RobAgar的答案,遵循我为ExtJS 3编写的非常简单的Date Time字段,它是我为ExtJS 4创建的quickport。重要的是使用Ext.form.field.Field
mixin。这个mixin为逻辑行为和表单字段状态提供了一个通用接口,包括:
字段值的getter和setter方法 跟踪价值和有效性变化的事件和方法 触发验证的方法
这可用于组合多个字段,并将它们作为一个字段进行操作。对于总自定义字段类型,我建议扩展Ext.form.field.Base
这是我上面提到的例子。即使是像日期对象那样我们需要在getter和setter中格式化数据,这应该是多么容易。
Ext.define('QWA.form.field.DateTime', {
extend: 'Ext.form.FieldContainer',
mixins: {
field: 'Ext.form.field.Field'
},
alias: 'widget.datetimefield',
layout: 'hbox',
width: 200,
height: 22,
combineErrors: true,
msgTarget: 'side',
submitFormat: 'c',
dateCfg: null,
timeCfg: null,
initComponent: function () {
var me = this;
if (!me.dateCfg) me.dateCfg = {};
if (!me.timeCfg) me.timeCfg = {};
me.buildField();
me.callParent();
me.dateField = me.down('datefield')
me.timeField = me.down('timefield')
me.initField();
},
//@private
buildField: function () {
var me = this;
me.items = [
Ext.apply({
xtype: 'datefield',
submitValue: false,
format: 'd.m.Y',
width: 100,
flex: 2
}, me.dateCfg),
Ext.apply({
xtype: 'timefield',
submitValue: false,
format: 'H:i',
width: 80,
flex: 1
}, me.timeCfg)]
},
getValue: function () {
var me = this,
value,
date = me.dateField.getSubmitValue(),
dateFormat = me.dateField.format,
time = me.timeField.getSubmitValue(),
timeFormat = me.timeField.format;
if (date) {
if (time) {
value = Ext.Date.parse(date + ' ' + time, me.getFormat());
} else {
value = me.dateField.getValue();
}
}
return value;
},
setValue: function (value) {
var me = this;
me.dateField.setValue(value);
me.timeField.setValue(value);
},
getSubmitData: function () {
var me = this,
data = null;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
data = {},
value = me.getValue(),
data[me.getName()] = '' + value ? Ext.Date.format(value, me.submitFormat) : null;
}
return data;
},
getFormat: function () {
var me = this;
return (me.dateField.submitFormat || me.dateField.format) + " " + (me.timeField.submitFormat || me.timeField.format)
}
});
答案 1 :(得分:24)
现在很酷。前几天,我创造了一个小提琴来回答另一个问题,然后才意识到我是偏离主题的。在这里,你终于把我的问题提到了我的答案。谢谢!
因此,以下是从另一个组件实现自定义字段所需的步骤:
创建组件的第一部分很简单。与为任何其他用途创建组件相比,没有什么特别的。
但是,您必须在父字段的initComponent
方法中创建子项(而不是在呈现时)。这是因为外部代码可以合法地期望在initComponent
之后实例化组件的所有依赖对象(例如,向它们添加侦听器)。
此外,您可以善待自己并在调用super方法之前创建子项。如果您在super方法之后创建子项,则可能会在子项尚未实例化时调用您的字段的setValue
方法(请参阅下面的内容)。
initComponent: function() {
this.childComponent = Ext.create(...);
this.callParent(arguments);
}
如您所见,我正在创建一个单独的组件,这在大多数情况下都是您想要的。但是你也可以想要花哨并组合多个子组件。在这种情况下,我认为尽快回到众所周知的领域是很聪明的:也就是说,创建一个容器作为子组件,然后组成它。
然后是渲染问题。起初我考虑使用fieldSubTpl
来渲染容器div,并让子组件呈现在其中。但是,在这种情况下我们不需要模板功能,因此我们也可以使用getSubTplMarkup
方法完全绕过它。
我探索了Ext中的其他组件,以了解它们如何管理子组件的呈现。我在BoundList及其分页工具栏中找到了一个很好的例子(参见code)。因此,为了获得子组件的标记,我们可以将Ext.DomHelper.generateMarkup
与子项的getRenderTree
方法结合使用。
所以,这是我们的领域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列表字段。
你可以看到它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(',');
}
});
答案 2 :(得分:4)
嘿。在发布赏金后,我发现Ext.form.FieldContainer
不仅仅是字段容器,而是一个完全成熟的组件容器,因此有一个简单的解决方案。
您需要做的就是扩展FieldContainer
,覆盖initComponent
以添加子组件,并实施setValue
,getValue
以及适合您的值的验证方法数据类型。
以下是一个网格示例,其值为名称/值对对象列表:
Ext.define('MyApp.widget.MyGridField', {
extend: 'Ext.form.FieldContainer',
alias: 'widget.mygridfield',
layout: 'fit',
initComponent: function()
{
this.callParent(arguments);
this.valueGrid = Ext.widget({
xtype: 'grid',
store: Ext.create('Ext.data.JsonStore', {
fields: ['name', 'value'],
data: this.value
}),
columns: [
{
text: 'Name',
dataIndex: 'name',
flex: 3
},
{
text: 'Value',
dataIndex: 'value',
flex: 1
}
]
});
this.add(this.valueGrid);
},
setValue: function(value)
{
this.valueGrid.getStore().loadData(value);
},
getValue: function()
{
// left as an exercise for the reader :P
}
});
答案 3 :(得分:3)
我已经这样做了几次。这是我使用的一般过程/伪代码:
afterrender
中,隐藏文本字段,并使用this.wrap = this.resizeEl = this.positionEl = this.el.wrap()
this.wrap
(例如,在配置中使用renderTo: this.wrap
)getValue
和setValue
以与您手动呈现的组件对话resize
侦听器中手动调整大小beforeDestroy
方法中创建的任何组件!我迫不及待地将我们的代码库切换到ExtJS 4,在这种情况下这些事情很容易。
祝你好运!答案 4 :(得分:3)
由于问题相当模糊 - 我只能提供ExtJS v4的基本模式。
即使它不是太具体,它还有一个相当普遍的进步:
Ext.define('app.view.form.field.CustomField', {
extend: 'Ext.form.field.Base',
requires: [
/* require further components */
],
/* custom configs & callbacks */
getValue: function(v){
/* override function getValue() */
},
setValue: function(v){
/* override function setValue() */
},
getSubTplData: [
/* most likely needs to be overridden */
],
initComponent: function(){
/* further code on event initComponent */
this.callParent(arguments);
}
});
文件/ext/src/form/field/Base.js提供了可以覆盖的所有配置和功能的名称。
答案 5 :(得分:2)
遵循http://docs.sencha.com/ext-js/4-0/#/api/Ext.form.field.Base
的文件此代码将创建一个可重复使用的TypeAhead / Autocomplete样式字段,用于选择语言。
var langs = Ext.create( 'Ext.data.store', {
fields: [ 'label', 'code' ],
data: [
{ code: 'eng', label: 'English' },
{ code: 'ger', label: 'German' },
{ code: 'chi', label: 'Chinese' },
{ code: 'ukr', label: 'Ukranian' },
{ code: 'rus', label: 'Russian' }
]
} );
Ext.define( 'Ext.form.LangSelector', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.LangSelector',
allowBlank: false,
hideTrigger: true,
width: 225,
displayField: 'label',
valueField: 'code',
forceSelection: true,
minChars: 1,
store: langs
} );
您只需将xtype设置为窗口小部件名称即可在表单中使用该字段:
{
xtype: 'LangSelector'
fieldLabel: 'Language',
name: 'lang'
}
答案 6 :(得分:1)
许多答案要么使用Mixin Ext.form.field.Field,要么扩展到一些已经满足其需求的已经制作的类 - 这很好。
但是我不建议完全覆盖setValue方法,那就是IMO非常糟糕的形式!
除了设置和获取值之外还发生了很多其他事情,并且如果你完全覆盖它 - 那么你就会搞砸脏状态,处理rawValue等等。
我猜这里有两个选项,一个是在你声明的方法中使用callParent(arguments)以保持简化,或者在你完成时最后应用继承的方法(mixin或extend)
但不要只是覆盖它而不考虑已经制作的方法在幕后做什么。
还要记住,如果在新类中使用其他字段类型 - 那么请将isFormField属性设置为false - 否则表单上的getValues方法将获取这些值并与em一起运行!
答案 7 :(得分:-1)
以下是扩展Ext Panel的自定义面板示例。您可以扩展任何组件,查看可以使用的字段,方法和事件的文档。
Ext.ns('yournamespace');
yournamespace.MyPanel = function(config) {
yournamespace.MyPanel.superclass.constructor.call(this, config);
}
Ext.extend(yournamespace.MyPanel, Ext.Panel, {
myGlobalVariable : undefined,
constructor : function(config) {
yournamespace.MyPanel.superclass.constructor.apply(this, config);
},
initComponent : function() {
this.comboBox = new Ext.form.ComboBox({
fieldLabel: "MyCombo",
store: someStore,
displayField:'My Label',
typeAhead: true,
mode: 'local',
forceSelection: true,
triggerAction: 'all',
emptyText:'',
selectOnFocus:true,
tabIndex: 1,
width: 200
});
// configure the grid
Ext.apply(this, {
listeners: {
'activate': function(p) {
p.doLayout();
},
single:true
},
xtype:"form",
border: false,
layout:"absolute",
labelAlign:"top",
bodyStyle:"padding: 15px",
width: 350,
height: 75,
items:[{
xtype:"panel",
layout:"form",
x:"10",
y:"10",
labelAlign:"top",
border:false,
items:[this.comboBox]
},
{
xtype:"panel",
layout:"form",
x:"230",
y:"26",
labelAlign:"top",
border:false,
items:[{
xtype:'button',
handler: this.someAction.createDelegate(this),
text: 'Some Action'
}]
}]
}); // eo apply
yournamespace.MyPanel.superclass.initComponent.apply(this, arguments);
this.comboBox.on('select', function(combo, record, index) {
this.myGlobalVariable = record.get("something");
}, this);
}, // eo function initComponent
someAction : function() {
//do something
},
getMyGlobalVariable : function() {
return this.myGlobalVariable;
}
}); // eo extend
Ext.reg('mypanel', yournamespace.MyPanel);
答案 8 :(得分:-2)
您能描述一下您需要更多的UI要求吗?您确定甚至需要构建整个字段来支持TreePanel吗?为什么不从普通树面板上的单击处理程序设置隐藏字段的值(请参阅API中的“隐藏”xtype)?
为了更全面地回答您的问题,您可以找到许多有关如何扩展ExtJS组件的教程。您可以通过利用Ext.override()或Ext.Extend()方法来实现此目的。
但我的感觉是你的设计可能过于复杂。您可以通过为此隐藏字段设置值来实现您需要执行的操作。如果您有复杂的数据,则可以将该值设置为某些XML或JSON字符串。
编辑以下是一些教程。在您的UI设计方面,我强烈建议您使用KISS规则。保持简单愚蠢! Extending components using panels