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

发布于 2024-11-10 12:57:35 字数 527 浏览 0 评论 0 原文

我想使用其中的其他 ExtJS 组件(例如 TreePanel)创建自定义 ExtJS 表单字段 组件。我怎样才能最轻松地做到这一点?

我已阅读 Ext.form 的文档.field.Base 但我不想通过 fieldSubTpl 定义字段主体。我只想编写创建 ExtJS 组件的代码以及一些获取和设置值的其他代码。

更新:总结的目的如下:

  • 这个新组件应该适合 将 GUI 形成为字段。它应该有 标签和相同的对齐方式(标签, 不需要其他领域的anchor) 进一步的黑客攻击。

  • 也许,我有 写一些getValue、setValue 逻辑。我宁愿将其嵌入到这个组件中,而不是制作单独的代码,将内容复制到我还必须管理的进一步隐藏的表单字段中。

I want to create custom ExtJS form field components using other ExtJS components in it (e.g. TreePanel). How can I do it most easily?

I've read docs of Ext.form.field.Base but I don't want to define field body by fieldSubTpl. I just want to write code which creates ExtJS components and maybe some other code which gets and sets values.

Update: Summarized purposes are the followings:

  • This new component should fit in the
    form GUI as a field. It should have
    label and the same alignment (label,
    anchor) of other fields without need
    of further hacking.

  • Possibly, I have
    to write some getValue, setValue
    logic. I'd rather embed it into this component than making separated code which copies things into further hidden form fields that I also have to manage.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

何以心动 2024-11-17 12:57:35

为了扩展 @RobAgar 的答案,遵循我为 ExtJS 3 编写的一个非常简单的日期时间字段,它是我为 ExtJS 4 编写的快速端口。 sencha.com/extjs/4.2.1/#!/api/Ext.form.field.Field" rel="noreferrer">Ext.form.field.Field mixin。该 mixin 为表单字段的逻辑行为和状态提供了通用接口,包括:

字段值的 Getter 和 setter 方法
用于跟踪价值和有效性变化的事件和方法
触发验证的方法

这可用于组合多个字段并将它们作为一个字段。对于完全自定义字段类型,我建议扩展 < code>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)
    }
});

To extend @RobAgar 's answer, following a really simple Date Time field that I wrote for ExtJS 3 and it's quickport that I made for ExtJS 4. The important thing is the use of the Ext.form.field.Field mixin. This mixin provides a common interface for the logical behavior and state of form fields, including:

Getter and setter methods for field values
Events and methods for tracking value and validity changes
Methods for triggering validation

This can be used for combining multiple fields and let act them as one. For a total custom fieldtype I recommend to extend Ext.form.field.Base

Here is the example that I mentioned above. It should shoe how easy this can be done even for something like a date object where we need to format the data within the getter and 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)
    }
});
囚你心 2024-11-17 12:57:35

现在没关系。有一天,我创建了一个小提琴来回答另一个问题,然后才意识到我偏离了主题。你终于来了,让我注意到这个问题并回答我。谢谢!

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

  1. 创建子组件
  2. 渲染子组件
  3. 确保子组件正确调整大小和调整大小
  4. 获取和设置值
  5. 中继事件

创建子组件

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

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

此外,您可以善待自己并在调用 super 方法之前创建子级。如果您在 super 方法之后创建子级,则在子级尚未实例化时,您可能会调用字段的 setValue 方法(见下文)。

initComponent: function() {
    this.childComponent = Ext.create(...);
    this.callParent(arguments);
}

正如您所看到的,我正在创建一个组件,这正是您在大多数情况下所需要的。但您也可能想要组合多个子组件。在这种情况下,我认为尽快回到众所周知的领域是明智的:即创建一个容器作为子组件,并在其中进行组合。

渲染

接下来就是渲染的问题了。起初我考虑使用 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('');
}

现在,这还不够。 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< /a> 表单工作的方法,这足以从表单加载/更新记录。

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

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

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

活动

您的最后一项工作将是为您的领域实施活动。您可能想要触发 Ext.form.field.Field 的三个事件,即 更改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(',');
    }
});

Now that's cool. The other day, I created a fiddle to answer another question before realizing I was off-topic. And here your are, finally bringing to my attention the question to my answer. Thanks!

So, here are the steps required in implementing a custom field from another component:

  1. Creating the child component
  2. Render the child component
  3. Ensuring the child component is sized and resized correctly
  4. Getting and setting value
  5. Relaying events

Creating the child component

The first part, creating the component, is easy. There's nothing particular compared to creating a component for any other usage.

However, you must create the child in the parent field's initComponent method (and not at rendering time). This is because external code can legitimately expect that all dependent objects of a component are instantiated after initComponent (e.g. to add listeners to them).

Furthermore, you can be kind to yourself and create the child before calling the super method. If you create the child after the super method, you may get a call to your field's setValue method (see bellow) at a time when the child is not yet instantiated.

initComponent: function() {
    this.childComponent = Ext.create(...);
    this.callParent(arguments);
}

As you see, I am creating a single component, which is what you'll want in most case. But you can also want to go fancy and compose multiple child components. In this case, I think it would be clever to back to well known territories as quickly as possible: that is, create one container as the child component, and compose in it.

Rendering

Then comes the question of rendering. At first I considered using fieldSubTpl to render a container div, and have the child component render itself in it. However, we don't need the template features in that case, so we can as well bypass it completely using the getSubTplMarkup method.

I explored other components in Ext to see how they manage the rendering of child components. I found a good example in BoundList and its paging toolbar (see the code). So, in order to obtain the child component's markup, we can use Ext.DomHelper.generateMarkup in combination with the child's getRenderTree method.

So, here's the implementation of getSubTplMarkup for our field:

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('');
}

Now, that's not enough. The code of BoundList learns us that there's another important part in component rendering: calling the finishRender() method of the child component. Fortunately, our custom field will have its own finishRenderChildren method called just when that needs to be done.

finishRenderChildren: function() {
    this.callParent(arguments);
    this.childComponent.finishRender();
}

Resizing

Now our child will be rendered in the right place, but it will not respect its parent field size. That is especially annoying in the case of a form field, because that means it won't honor the anchor layout.

That's very straightforward to fix, we just need to resize the child when the parent field is resized. From my experience, this is something that was greatly improved since Ext3. Here, we just need to not forget the extra space for the label:

onResize: function(w, h) {
    this.callParent(arguments);
    this.childComponent.setSize(w - this.getLabelWidth(), h);
}

Handling value

This part will, of course, depend on your child component(s), and the field you're creating. Moreover, from now on, it's just a matter of using your child components in a regular way, so I won't detail this part too much.

A minima, you also need to implement the getValue and setValue methods of your field. That will make the getFieldValues method of the form work, and that will be enough to load/update records from the form.

To handle validation, you must implement getErrors. To polish this aspect, you may want to add a handful of CSS rules to visually represent the invalid state of your field.

Then, if you want your field to be usable in a form that will be submitted as an actual form (as opposed to with an AJAX request), you'll need getSubmitValue to return a value that can be casted to a string without damage.

Apart from that, as far as I know, you don't have to worry about the concept or raw value introduced by Ext.form.field.Base since that's only used to handle the representation of the value in an actual input element. With our Ext component as input, we're way off that road!

Events

Your last job will be to implement the events for your fields. You will probably want to fire the three events of Ext.form.field.Field, that is change, dirtychange and validitychange.

Again, the implementation will be very specific to the child component you use and, to be honest, I haven't explored this aspect too much. So I'll let you wire this for yourself.

My preliminary conclusion though, is that Ext.form.field.Field offers to do all the heavy lifting for you, provided that (1) you call checkChange when needed, and (2) isEqual implementation is working with your field's value format.

Example: TODO list field

Finally, here's a complete code example, using a grid to represent a TODO list field.

You can see it live on jsFiddle, where I tries to show that the field behaves in an orderly manner.

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(',');
    }
});
梦幻之岛 2024-11-17 12:57:35

呵呵。发布赏金后,我发现 Ext.form.FieldContainer 不仅仅是一个 field 容器,而是一个完全成熟的组件容器,所以有一个简单的解决方案。

您需要做的就是扩展 FieldContainer,重写 initComponent 以添加子组件,并实现 setValuegetValue以及适合您的值数据类型的验证方法。

下面是一个网格示例,其值是名称/值对对象的列表:

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
  }
});

Heh. After posting the bounty I found out that Ext.form.FieldContainer isn't just a field container, but a fully fledged component container, so there is a simple solution.

All you need to do is extend FieldContainer, overriding initComponent to add the child components, and implement setValue, getValue and the validation methods as appropriate for your value data type.

Here's an example with a grid whose value is a list of name/value pair objects:

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
  }
});
佼人 2024-11-17 12:57:35

我已经这样做过几次了。这是我使用的一般过程/伪代码:

  • 创建一个提供最有用的重用的字段扩展(如果您只想获取/设置一个字符串值,通常是 Ext.form.TextField
  • )字段的 ,隐藏文本字段,并使用 this.wrap = this.resizeEl = this.positionEl = this.el.wrap() 在 this.el 周围创建一个环绕元素
  • 渲染任何组件到this.wrap (例如在配置中使用 renderTo: this.wrap
  • 覆盖 getValuesetValue 进行对话 如果您的表单布局
  • 发生变化,您可能需要在 resize 侦听器中手动调整大小。
  • 不要忘记清理您在 beforeDestroy 中创建的任何组件代码>方法!

我迫不及待地想将我们的代码库切换到 ExtJS 4,在这里这些事情很容易实现。

祝你好运!

I've done this a few times. Here is the general process/pseudo-code I use:

  • Create an extension of field that provides the most useful re-use (typically Ext.form.TextField if you just want to get/set a string value)
  • In the afterrender of the field, hide the textfield, and create a wrapping element around this.el with this.wrap = this.resizeEl = this.positionEl = this.el.wrap()
  • Render any components to this.wrap (e.g. using renderTo: this.wrap in the config)
  • Override getValue and setValue to talk to the component(s) you rendered manually
  • You may need to do some manually sizing in a resize listener if your form's layout changes
  • Don't forget to cleanup any components you create in the beforeDestroy method!

I can't wait to switch our codebase to ExtJS 4, where these kinds of things are easy.

Good luck!

梦情居士 2024-11-17 12:57:35

由于问题提出得相当模糊 - 我只能提供 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 提供了可以覆盖的所有配置和函数的名称。

Since the question was asked rather vague - I only can provide the basic pattern for ExtJS v4.

Even if it's not too specific, it has the advance that it's rather universal like this:

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);
    }
});

The file /ext/src/form/field/Base.js provides the names of all configs and functions that can be overridden.

烟雨扶苏 2024-11-17 12:57:35

遵循 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'
}

Following the documentation at http://docs.sencha.com/ext-js/4-0/#/api/Ext.form.field.Base

This code will create a reusable TypeAhead/Autocomplete style field for selecting a language.

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
} );

You can use the field in a form simply by setting the xtype to the widget name:

{
    xtype: 'LangSelector'
    fieldLabel: 'Language',
    name: 'lang'
}
遗忘曾经 2024-11-17 12:57:35

许多答案要么使用 Mixin Ext.form.field.Field 要么只是扩展一些已经制作的适合他们需求的类 - 这很好。

但我不建议完全覆盖 setValue 方法,在我看来,这是非常糟糕的形式!

发生的事情不仅仅是设置和获取值,如果你完全覆盖它 - 那么你会弄乱脏状态,原始值的处理等。

我想这里有两个选项,一个是在里面调用Parent(arguments)您声明的方法可以使事情简化,或者在完成后应用从您获得的地方继承的方法(混合或扩展)。

但不要只是覆盖它而不考虑已经创建的方法在幕后的作用。

另请记住,如果您在新类中使用其他字段类型 - 则请将 isFormField 属性设置为 false - 否则表单上的 getValues 方法将采用这些值并使用它们运行!

Many of the answers either use the Mixin Ext.form.field.Field or just extends on some already made class that suits their needs - which is fine.

But I do not recommend fully overwriting the setValue method, that is IMO really bad form!

A lot more happens than just setting and getting the value, and if you fully overwrite it - well you will for instance mess up the dirty state, processing of rawValue etc..

Two options here I guess, one is to callParent(arguments) inside the method you declare to keep things streamlined, or to at the end when you are done apply the inherited method from where ever you got it (mixin or extend).

But do not just overwrite it with no regards for what that already made method does behind the scenes.

Also remember that if you use other field types in your new class - then do set the isFormField property to false - otherwise your getValues method on the form will take those values and run with em!

无远思近则忧 2024-11-17 12:57:35

另一个解决方案可能是树字段实现。
它的行为就像普通表单字段:

https://github.com/wencywww /Ext.ux.form.field.Tree

Another solution could be this tree-field implementation.
It behaves just like a normal form field:

https://github.com/wencywww/Ext.ux.form.field.Tree

╰◇生如夏花灿烂 2024-11-17 12:57:35

以下是扩展扩展面板的自定义面板的示例。您可以扩展任何组件,检查文档以了解您可以使用的字段、方法和事件。

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);

Here is an example of a custom panel that extends an Ext Panel. You can extend any component, check the docs for the fields, methods and events you can play with.

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);
·深蓝 2024-11-17 12:57:35

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

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

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

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

Could you describe the UI requirements that you have a bit more? Are you sure that you even need to do build an entire field to support the TreePanel? Why not set the value of a hidden field (see the "hidden" xtype in the API) from a click handler on a normal tree panel?

To answer your question more fully, you can find many tutorials on how to extend ExtJS components. You do this by leveraging the Ext.override() or Ext.Extend() methods.

But my feeling is that you may be over-complicating your design. You can achieve what you need to do by setting a value to this hidden field. If you have complex data, you can set the value as some XML or JSON string.

EDIT Here's a few tutorials. I highly recommend going with the KISS rule when it comes to your UI design. Keep It Simple Stupid!
Extending components using panels

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文