如何创建自定义ExtJS表单字段组件?

pcj*_*zer 31 forms extjs extjs4

我想使用其中的其他ExtJS组件(例如TreePanel)创建自定义ExtJS 表单字段组件.我怎样才能最轻松地完成这项工作?

我已经阅读了Ext.form.field.Base的文档,但我不想定义字段体fieldSubTpl.我只想编写创建ExtJS组件的代码,也可以编写一些获取和设置值的代码.

更新:总结目的如下:

  • 这个新组件应该以GUI形式作为字段.它应该具有标签和其他字段的相同对齐(标签,锚点),而无需进一步的黑客攻击.

  • 可能,我必须编写一些getValue,setValue逻辑.我宁愿将它嵌入到这个组件中,而不是将分离的代码复制到我必须管理的更隐藏的表单字段中.

sra*_*sra 27

为了扩展@RobAgar的答案,遵循我为ExtJS 3写的一个非常简单的Date Time字段,它是我为ExtJS 4制作的quickport.重要的是使用Ext.form.field.Fieldmixin.这个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)
    }
});
Run Code Online (Sandbox Code Playgroud)


rix*_*ixo 24

现在这很酷.前几天,我创造了一个小提琴来回答另一个问题,然后才意识到我是偏离主题的.在这里,你终于把我的问题提到了我的答案.谢谢!

因此,以下是从另一个组件实现自定义字段所需的步骤:

  1. 创建子组件
  2. 渲染子组件
  3. 确保子组件的大小和大小正确
  4. 获得和设定价值
  5. 转发事件

创建子组件

第一部分,创建组件,很容易.与为任何其他用途创建组件相比,没有什么特别之处.

但是,您必须在父字段的initComponent方法中创建子项(而不是在呈现时).这是因为外部代码可以合法地期望在之后实例化组件的所有依赖对象initComponent(例如,向其添加侦听器).

此外,在调用super方法之前,你可以善待自己并创建孩子.如果您在super方法之后创建子项,则可能会setValue在子项尚未实例化时调用您的字段的方法(请参阅下面的内容).

initComponent: function() {
    this.childComponent = Ext.create(...);
    this.callParent(arguments);
}
Run Code Online (Sandbox Code Playgroud)

如你所见,我正在创建一个单独的组件,这是大多数情况下你想要的.但是你也可以想要花哨并组合多个子组件.在这种情况下,我认为尽快回到众所周知的领域是很聪明的:也就是说,创建一个容器作为子组件,然后组成它.

渲染

然后是渲染问题.起初我考虑使用fieldSubTpl渲染容器div,并让子组件呈现在其中.但是,在这种情况下我们不需要模板功能,因此我们也可以使用该getSubTplMarkup方法完全绕过它.

我探索了Ext中的其他组件,以了解它们如何管理子组件的呈现.我在BoundList及其分页工具栏中找到了一个很好的例子(参见代码).因此,为了获得子组件的标记,我们可以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('');
}
Run Code Online (Sandbox Code Playgroud)

现在,这还不够.BoundList代码告诉我们组件呈现中还有另一个重要的部分:调用finishRender()子组件的方法.幸运的是,我们的自定义字段将finishRenderChildren在需要完成时调用自己的方法.

finishRenderChildren: function() {
    this.callParent(arguments);
    this.childComponent.finishRender();
}
Run Code Online (Sandbox Code Playgroud)

调整

现在我们的孩子将被渲染到正确的位置,但它不会尊重其父字段大小.在表单字段的情况下,这尤其令人讨厌,因为这意味着它不会遵循锚点布局.

这是非常简单的修复,我们只需要在调整父字段大小时调整子项的大小.根据我的经验,这是自Ext3以来大大改进的东西.在这里,我们只需要忘记标签的额外空间:

onResize: function(w, h) {
    this.callParent(arguments);
    this.childComponent.setSize(w - this.getLabelWidth(), h);
}
Run Code Online (Sandbox Code Playgroud)

处理价值

当然,这部分将取决于您的子组件以及您正在创建的字段.此外,从现在开始,只需要定期使用您的子组件,所以我不会过多详细介绍这部分内容.

最小的,你还需要实现你的领域getValuesetValue方法.这将使getFieldValues表单的方法起作用,这足以从表单加载/更新记录.

要处理验证,您必须实施getErrors.为了完善这一方面,您可能需要添加一些CSS规则来直观地表示字段的无效状态.

然后,如果您希望您的字段可以在将作为实际表单提交的表单中使用(而不是使用AJAX请求),则您需要getSubmitValue返回一个可以在不损坏的情况下转换为字符串的值.

除此之外,据我所知,您不必担心引入的概念或原始值,Ext.form.field.Base因为它仅用于处理实际输入元素中值的表示.以我们的Ext组件作为输入,我们离开了这条路!

活动

您的上一份工作是为您的领域实施活动.你可能会想火的三件大事Ext.form.field.Field,那就是change,dirtychangevaliditychange.

同样,实现将非常特定于您使用的子组件,说实话,我没有太多地探讨这个方面.所以我会让你自己连线.

我的初步结论是,Ext.form.field.Field提供为你做所有繁重的工作,前提是(1)你需要checkChange时打电话,(2)isEqual实施与你的领域的价值格式一起工作.

示例:TODO列表字段

最后,这是一个完整的代码示例,使用网格表示TODO列表字段.

你可以在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(',');
    }
});
Run Code Online (Sandbox Code Playgroud)


It *_*unt -2

您能描述一下您的 UI 要求吗?您确定您甚至需要构建整个字段来支持 TreePanel 吗?为什么不从普通树面板上的单击处理程序设置隐藏字段的值(请参阅 API 中的“隐藏”xtype)?

为了更全面地回答您的问题,您可以找到许多有关如何扩展 ExtJS 组件的教程。您可以通过利用 Ext.override() 或 Ext.Extend() 方法来完成此操作。

但我的感觉是你的设计可能过于复杂。您可以通过为此隐藏字段设置值来实现您需要执行的操作。如果您有复杂的数据,您可以将值设置为某些 XML 或 JSON 字符串。

编辑这里有一些教程。我强烈建议您在进行 UI 设计时采用 KISS 规则。保持简单愚蠢! 使用面板扩展组件