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

| 我想使用其中的其他ExtJS组件(例如TreePanel)创建自定义ExtJS表单字段组件。我如何最轻松地做到这一点? 我已经阅读过Ext.form.field.Base的文档,但是我不想通过“ 0”来定义字段主体。我只想编写创建ExtJS组件的代码,也许还要编写一些获取和设置值的代码。 更新:总结目的如下: 此新组件应适合 将GUI作为字段。应该有 标签和相同的对齐方式(标签, 锚)其他字段,无需 进一步的黑客攻击。 可能,我有 写一些getValue,setValue 逻辑。我宁愿将其嵌入到此组件中,而不是制作单独的代码,该代码将内容复制到我也必须管理的其他隐藏形式字段中。     
已邀请:
        为了扩展@RobAgar的答案,遵循我为ExtJS 3编写的一个非常简单的“日期时间”字段以及为ExtJS 4编写的快速端口。重要的是使用
Ext.form.field.Field
mixin。此混合为表单字段的逻辑行为和状态提供了一个通用接口,包括: 字段值的Getter和Setter方法 跟踪价值和有效性变化的事件和方法 触发验证的方法 这可用于组合多个字段并使它们充当一个字段。对于总的自定义字段类型,我建议扩展
Ext.form.field.Base
这是我上面提到的示例。即使对于像date对象这样的东西,我们都需要格式化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)
    }
});
    
现在很酷。前几天,我发现小提琴要回答另一个问题,然后才意识到自己是题外话。在这里,您终于要引起我的注意了。谢谢! 因此,这是从另一个组件实现自定义字段所需的步骤: 创建子组件 渲染子组件 确保正确调整子组件的大小并调整其大小 获取和设置值 接力赛事 创建子组件 第一部分,创建组件很容易。与为任何其他用途创建组件相比,没有什么特别的。 但是,必须在父字段的
initComponent
方法中创建子对象(而不是在渲染时)。这是因为外部代码可以合理地期望组件的所有相关对象在``4''之后实例化(例如向其添加侦听器)。 此外,您可以对自己好一点,并在调用super方法之前创建孩子。如果您是在super方法之后创建子级的,则可能会在尚未实例化该子级时调用该字段的
setValue
方法(请参见下面的内容)。
initComponent: function() {
    this.childComponent = Ext.create(...);
    this.callParent(arguments);
}
如您所见,我正在创建一个组件,这是大多数情况下所需的组件。但是,您也可以想像一下并组成多个子组件。在这种情况下,我认为最好尽快返回知名区域:也就是说,创建一个容器作为子组件,并在其中进行组装。 渲染图 然后是渲染问题。起初,我考虑过使用“ 0”来渲染容器div,并让子组件在其中进行渲染。但是,在这种情况下,我们不需要模板功能,因此我们也可以使用
getSubTplMarkup
方法完全绕开它。 我探索了Ext中的其他组件,以了解它们如何管理子组件的呈现。我在BoundList及其分页工具栏中找到了一个很好的示例(请参见代码)。因此,为了获得子组件的标记,我们可以结合使用“ 10”和子组件的“ 11”方法。 因此,这是我们领域的ѭ9的实现:
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(\'\');
}
现在,这还不够。 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
方法。这将使表单的“ 20”方法生效,并且足以从表单中加载/更新记录。 要进行验证,必须实现
getErrors
。为了完善这一方面,您可能需要添加一些CSS规则以直观地表示字段的无效状态。 然后,如果您希望您的字段以将以实际形式提交的形式使用(而不是AJAX请求),则需要
getSubmitValue
来返回可以转换为字符串而不会造成损坏的值。 除此之外,据我所知,您不必担心
Ext.form.field.Base
引入的概念或原始值,因为它仅用于处理实际输入元素中值的表示形式。使用我们的Ext组件作为输入,我们就走了这条路! 大事记 您的最后一项工作是为您的字段实施事件。您可能会希望触发
Ext.form.field.Field
的三个事件,即
change
dirtychange
validitychange
。 同样,实现将非常针对您所使用的子组件,并且说实话,我没有对此方面进行过多探讨。因此,我会让您自己进行布线。 但是我的初步结论是,只要(1)必要时调用
checkChange
,并且(2)
isEqual
实现正在与您字段的值格式配合使用,ѭ1便会为您提供所有繁重的工作。 示例: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(\',\');
    }
});
    
嘿。发布赏金后,我发现
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
  }
});
    
        我已经做了几次。这是我使用的一般过程/伪代码: 创建提供最有用的重用性的字段扩展(通常是Ext.form.TextField,如果您只想获取/设置字符串值) 在字段的“ 38”中,隐藏文本字段,并使用“ 39”在此周围创建环绕元素。 将任何组件渲染到
this.wrap
(例如在配置中使用
renderTo: this.wrap
) 覆盖
getValue
setValue
以与您手动渲染的组件通信 如果表单的布局发生变化,您可能需要在
resize
侦听器中手动调整大小 不要忘记清理用you45ѭ方法创建的任何组件! 我迫不及待想将我们的代码库切换到ExtJS 4,在这里,这些事情很容易。 祝好运!     
        由于这个问题被问得很模糊-我只能提供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文件提供了可以覆盖的所有配置和功能的名称。     
        遵循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\'
}
    
        许多答案都使用Mixin Ext.form.field.Field或只是扩展一些已经满足其需求的类-很好。 但是我不建议完全覆盖setValue方法,这是IMO真正不好的形式! 除了设置和获取值之外,还有很多事情发生,如果您完全覆盖它,例如,您会弄乱脏状态,处理rawValue等。 我猜这里有两个选择,一种是在声明的方法内部调用Parent(arguments),以使事情保持精简,或者在完成时最后从任何获得的地方应用继承的方法(mixin或extend)。 但是,不要无视它已经做的方法在幕后所做的事情而覆盖它。 还要记住,如果在新类中使用其他字段类型-则将isFormField属性设置为false-否则表单上的getValues方法将使用这些值并与em一起运行!     
        这是扩展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);
    
        您能否描述一下您还有更多的UI要求?您确定甚至需要构建整个字段来支持TreePanel吗?为什么不通过普通树面板上的点击处理程序设置隐藏字段的值(请参阅API中的“ \ hidden \” xtype)? 为了更全面地回答您的问题,您可以找到许多有关扩展ExtJS组件的教程。您可以利用Ext.override()或Ext.Extend()方法来实现。 但是我的感觉是您可能会使设计过于复杂。您可以通过为此隐藏字段设置值来实现所需的操作。如果数据复杂,则可以将值设置为XML或JSON字符串。 编辑这是一些教程。我强烈建议您在UI设计中使用KISS规则。保持简单愚蠢! 使用面板扩展组件     

要回复问题请先登录注册