Теперь это круто.На днях я создал скрипку, чтобы ответить на другой вопрос, прежде чем понял, что я не по теме.И вот, наконец, вы обратили мое внимание на вопрос к моему ответу.Спасибо!
Итак, вот шаги, необходимые для реализации настраиваемого поля из другого компонента:
- Создание дочернего компонента
- Визуализация дочернего компонента
- Обеспечение правильного размера и размера дочернего компонента
- Получение и установка значения
- Передача событий
Создание дочернего компонента
Первая часть, создание компонента, проста.Нет ничего особенного по сравнению с созданием компонента для любого другого использования.
Однако вы должны создать дочерний элемент в методе initComponent
родительского поля (а не во время рендеринга).Это связано с тем, что внешний код может на законных основаниях ожидать, что все зависимые объекты компонента будут созданы после initComponent
(например, для добавления к ним слушателей).
Более того, вы можете быть добрыми к себе и создать дочернего элемента перед вызовомсупер метод.Если вы создаете дочерний элемент после использования супер-метода, вы можете получить вызов метода setValue
вашего поля (см. Ниже) в тот момент, когда дочерний элемент еще не создан.
initComponent: function() {
this.childComponent = Ext.create(...);
this.callParent(arguments);
}
Как видите, я создаю отдельный компонент, который вам в большинстве случаев нужен.Но вы также можете захотеть создать несколько дочерних компонентов.В этом случае, я думаю, было бы разумно как можно быстрее вернуться к хорошо известным территориям: создать один контейнер как дочерний компонент и создать в нем.
Rendering
Затем возникает вопрос рендеринга.Сначала я подумал об использовании 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('');
}
Теперь этого недостаточно.Код 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
, поскольку оно используется только для обработки представления значения в фактический элемент ввода. С нашим компонентом 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(',');
}
});