Подтвердить что ты не робот

Как создать пользовательский компонент поля формы ExtJS?

Я хочу создать пользовательские компоненты ExtJS формы формы с использованием других компонентов ExtJS в нем (например, TreePanel). Как я могу сделать это наиболее легко?

Я прочитал документы Ext.form.field.Base, но я не хочу определять тело тела fieldSubTpl. Я просто хочу написать код, который создает компоненты ExtJS и, возможно, другой код, который получает и устанавливает значения.

Обновление: Подводя итоги, выполните следующие действия:

  • Этот новый компонент должен соответствовать сформировать GUI как поле. Он должен иметь ярлык и одинаковое выравнивание (метка, якорь) других полей без необходимости дальнейшего взлома.

  • Возможно, у меня есть написать некоторый getValue, setValue логика. Я предпочел бы вставлять его в этот компонент, чем разделять код, который копирует вещи в дополнительные скрытые поля формы, которые мне также нужно управлять.

4b9b3361

Ответ 1

Чтобы продлить ответ @RobAgar, следуя действительно простому полю Date Time, которое я написал для ExtJS 3, и это quickport, которое я сделал для ExtJS 4. Важным является использование Ext.form.field.Field mixin. Этот mixin обеспечивает общий интерфейс для логического поведения и состояния полей формы, включая:

Методы Getter и setter для значений полей События и методы отслеживания изменений ценности и валидности Методы запуска проверки

Это может быть использовано для объединения нескольких полей и позволяет действовать как один. Для общего пользовательского поля я рекомендую расширить Ext.form.field.Base

Вот пример, который я упомянул выше. Он должен подгонять, как легко это можно сделать даже для чего-то вроде объекта даты, где нам нужно отформатировать данные в приемнике и сеттере.

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

Ответ 2

Теперь это круто. На днях я создал скрипку, чтобы ответить на другой вопрос, прежде чем осознать, что я не в тему. И вот ваши, наконец, доводя до моего внимания вопрос к моему ответу. Спасибо!

Итак, вот шаги, необходимые для реализации настраиваемого поля из другого компонента:

  • Создание дочернего компонента
  • Отобразить дочерний компонент
  • Обеспечение правильного размера и размера дочернего компонента.
  • Получение и установка значения
  • События ретрансляции

Создание дочернего компонента

Первая часть, создающая компонент, проста. Нет ничего особенного по сравнению с созданием компонента для любого другого использования.

Однако вы должны создать дочерний элемент в родительском поле initComponent (а не во время рендеринга). Это связано с тем, что внешний код может законно ожидать, что все зависимые объекты компонента будут созданы после initComponent (например, чтобы добавить к ним слушателей).

Кроме того, вы можете быть добры к себе и создать ребенка до вызова супер метода. Если вы создадите дочерний элемент после супер-метода, вы можете получить вызов своего метода setValue (см. Ниже) в то время, когда дочерний объект еще не создан.

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

Как вы видите, я создаю один компонент, который вам будет нужен в большинстве случаев. Но вы также можете захотеть придумать и составить несколько дочерних компонентов. В этом случае я думаю, что было бы разумно вернуться к известным территориям как можно быстрее: т.е. Создать один контейнер в качестве дочернего компонента и составить в нем.

Rendering

Тогда возникает вопрос рендеринга. Сначала я рассмотрел возможность использования 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();
}

Resizing

Теперь наш ребенок будет отображаться в нужном месте, но он не будет уважать размер его родительского поля. Это особенно раздражает в случае поля формы, потому что это означает, что он не соблюдает расположение якоря.

Это очень просто исправить, нам просто нужно изменить размер дочернего элемента, когда размер родительского поля будет изменен. По моему опыту, это значительно улучшилось с 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, поскольку это используется только для обрабатывать представление значения в фактическом элементе ввода. С нашим компонентом Ext в качестве входного сигнала, мы ушли с этой дороги!

События

Ваша последняя работа будет заключаться в реализации событий для ваших полей. Вероятно, вы захотите запустить три события Ext.form.field.Field, то есть change, dirtychange и validitychange.

Опять же, реализация будет очень специфичной для дочернего компонента, который вы используете, и, честно говоря, я не слишком разбирался в этом аспекте. Поэтому я позволю вам сделать это для себя.

Мое предварительное заключение состоит в том, что 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(',');
    }
});

Ответ 3

Хех. После публикации щедрости я обнаружил, что 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
  }
});

Ответ 4

Я сделал это несколько раз. Вот общий процесс/псевдокод, который я использую:

  • Создайте расширение поля, которое обеспечивает наиболее полезное повторное использование (обычно Ext.form.TextField, если вы просто хотите получить/установить строковое значение)
  • В поле afterrender этого поля спрячьте текстовое поле и создайте оберточный элемент вокруг this.el с помощью this.wrap = this.resizeEl = this.positionEl = this.el.wrap()
  • Отобразить любые компоненты в this.wrap (например, используя renderTo: this.wrap в конфигурации)
  • Переопределить getValue и setValue, чтобы поговорить с компонентами, которые вы отображали вручную
  • Вам может потребоваться внести некоторые изменения вручную в прослушиватель resize, если ваш макет формы изменится
  • Не забудьте очистить любые компоненты, созданные в методе beforeDestroy!

Я не могу дождаться, чтобы переключить нашу кодовую базу на ExtJS 4, где эти вещи просты.

Удачи!

Ответ 5

Поскольку вопрос задавался довольно расплывчато - я могу предоставить основной шаблон для 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 предоставляет имена всех конфигураций и функций, которые можно переопределить.

Ответ 6

Следуя документации на 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'
}

Ответ 7

Многие из ответов либо используют Mixin Ext.form.field.Field, либо просто распространяются на некоторый уже сделанный класс, который соответствует их потребностям - это нормально.

Но я не рекомендую полностью переписывать метод setValue, то есть IMO действительно плохая форма!

Намного больше, чем просто установка и получение значения, и если вы полностью перезапишете его - ну вы, например, испортите грязное состояние, обработаете rawValue и т.д.

Два варианта здесь, я думаю, один из них заключается в вызовеParent (аргументы) внутри метода, который вы объявляете, чтобы упростить процесс, или в конце, когда вы закончите применять унаследованный метод от того, где вы его получили (mixin или extend).

Но не просто переписывайте его без учета того, что сделал уже сделанный метод за кулисами.

Также помните, что если вы используете другие типы полей в своем новом классе - тогда установите для свойства isFormField значение false - в противном случае ваш метод getValues ​​в форме примет эти значения и запустится с em!

Ответ 9

Вот пример пользовательской панели, которая расширяет панель Ext. Вы можете расширить любой компонент, проверить документы для полей, методов и событий, с которыми вы можете играть.

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

Ответ 10

Не могли бы вы описать требования к пользовательскому интерфейсу, которые у вас есть немного больше? Вы уверены, что вам даже нужно собрать все поле для поддержки TreePanel? Почему бы не установить значение скрытого поля (см. "Скрытый" тип xtype в API) из обработчика щелчка на обычной панели дерева?

Чтобы более полно ответить на ваш вопрос, вы можете найти множество руководств по расширению компонентов ExtJS. Вы делаете это, используя методы Ext.override() или Ext.Extend().

Но я чувствую, что вы можете слишком усложнять свой дизайн. Вы можете добиться того, что вам нужно сделать, установив значение в это скрытое поле. Если у вас есть сложные данные, вы можете установить это значение как строку XML или JSON.

ИЗМЕНИТЬ Вот несколько руководств. Я настоятельно рекомендую перейти к правилу KISS, когда речь заходит о вашем дизайне интерфейса. Держать его просто глупо! Расширение компонентов с помощью панелей