sencha docs forms extjs extjs4

forms - sencha - extjs docs



¿Cómo crear un componente de campo de formulario ExtJS personalizado? (9)

¿Podría describir los requisitos de UI que tiene un poco más? ¿Estás seguro de que incluso necesitas construir un campo completo para soportar TreePanel? ¿Por qué no establecer el valor de un campo oculto (ver el x tipo "oculto" en la API) desde un controlador de clics en un panel de árbol normal?

Para responder a su pregunta más completamente, puede encontrar muchos tutoriales sobre cómo extender los componentes de ExtJS. Para ello, utilice los métodos Ext.override () o Ext.Extend ().

Pero mi sensación es que puede estar complicando demasiado su diseño. Puede lograr lo que necesita hacer al establecer un valor para este campo oculto. Si tiene datos complejos, puede establecer el valor como una cadena XML o JSON.

EDITAR Aquí hay algunos tutoriales. Recomiendo ir con la regla de KISS cuando se trata de su diseño de interfaz de usuario. Keep It Simple Stupid! Extender componentes usando paneles

Deseo crear componentes de campo de formulario ExtJS personalizados utilizando otros componentes de ExtJS (por ejemplo, TreePanel). ¿Cómo puedo hacerlo más fácilmente?

He leído documentos de Ext.form.field.Base pero no quiero definir el cuerpo del campo por fieldSubTpl . Solo quiero escribir código que crea componentes ExtJS y tal vez algún otro código que obtiene y establece valores.

Actualización: los propósitos resumidos son los siguientes:

  • Este nuevo componente debería caber en la forma GUI como un campo. Debe tener una etiqueta y la misma alineación (etiqueta, anclaje) de otros campos, sin necesidad de más pirateo.

  • Posiblemente, tengo que escribir algunos getValue, setValue logic. Prefiero insertarlo en este componente que crear un código separado que copie las cosas en campos de formularios ocultos adicionales que también tengo que administrar.


Ahora eso es genial. El otro día, creé un violín para responder a otra pregunta antes de darme cuenta de que estaba fuera del tema. Y aquí estás, finalmente llamándome la atención sobre mi respuesta. ¡Gracias!

Por lo tanto, estos son los pasos necesarios para implementar un campo personalizado desde otro componente:

  1. Creando el componente hijo
  2. Renderizar el componente hijo
  3. Asegurar que el componente hijo tenga el tamaño y el tamaño correctos
  4. Obtener y establecer valor
  5. Eventos de retransmisión

Creando el componente hijo

La primera parte, crear el componente, es fácil. No hay nada en particular en comparación con la creación de un componente para cualquier otro uso.

Sin embargo, debe crear el elemento secundario en el método initComponent del campo initComponent (y no en el tiempo de representación). Esto se debe a que el código externo puede legítimamente esperar que todos los objetos dependientes de un componente se instancian después de initComponent (por ejemplo, para agregar oyentes a ellos).

Además, puedes ser amable contigo mismo y crear al niño antes de llamar al súper método. Si crea el elemento secundario después del método súper, puede recibir una llamada al método setValue su campo (ver más abajo) en un momento en que el niño todavía no está instanciado.

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

Como ve, estoy creando un único componente, que es lo que querrá en la mayoría de los casos. Pero también puede desear ser elegante y componer múltiples componentes secundarios. En este caso, creo que sería inteligente regresar a territorios conocidos lo más rápido posible: es decir, crear un contenedor como componente secundario y componer en él.

Representación

Luego viene la cuestión de renderizar. Al principio consideré usar fieldSubTpl para representar un contenedor div, y hacer que el componente hijo se represente en él. Sin embargo, no necesitamos las características de la plantilla en ese caso, así que también podemos getSubTplMarkup completo usando el método getSubTplMarkup .

Exploré otros componentes en Ext para ver cómo administran la representación de componentes secundarios. Encontré un buen ejemplo en BoundList y su barra de herramientas de paginación (ver el code ). Por lo tanto, para obtener el marcado del componente secundario, podemos usar Ext.DomHelper.generateMarkup en combinación con el método getRenderTree del niño.

Entonces, aquí está la implementación de getSubTplMarkup para nuestro campo:

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

Ahora, eso no es suficiente. El código de BoundList nos enseña que hay otra parte importante en la representación de componentes: llamar al método finishRender() del componente secundario. Afortunadamente, nuestro campo personalizado tendrá su propio método finishRenderChildren llamado justo cuando es necesario hacerlo.

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

Cambio de tamaño

Ahora nuestro hijo se representará en el lugar correcto, pero no respetará el tamaño del campo principal. Eso es especialmente molesto en el caso de un campo de formulario, porque eso significa que no respetará el diseño del ancla.

Es muy sencillo corregirlo, solo tenemos que cambiar el tamaño del niño cuando el campo principal se redimensiona. Desde mi experiencia, esto es algo que mejoró mucho desde Ext3. Aquí, simplemente no debemos olvidar el espacio adicional para la etiqueta:

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

Manejo de valor

Esta parte, por supuesto, dependerá de su (s) componente (s) secundario (s) y del campo que esté creando. Además, de ahora en adelante, solo se trata de usar los componentes de su hijo de manera regular, por lo que no detallaré demasiado esta parte.

Un mínimo, también necesita implementar los métodos getValue y setValue de su campo. Eso hará que el método getFieldValues del formulario funcione, y eso será suficiente para cargar / actualizar registros del formulario.

Para manejar la validación, debe implementar getErrors . Para pulir este aspecto, es posible que desee agregar un puñado de reglas de CSS para representar visualmente el estado no válido de su campo.

Luego, si desea que su campo sea utilizable en un formulario que se presentará como una forma real (en lugar de una solicitud AJAX), necesitará getSubmitValue para devolver un valor que se puede convertir en una cadena sin daños.

Aparte de eso, hasta donde yo sé, no tiene que preocuparse por el concepto o valor bruto introducido por Ext.form.field.Base ya que solo se utiliza para manejar la representación del valor en un elemento de entrada real. Con nuestro componente Ext como entrada, ¡estamos muy lejos de ese camino!

Eventos

Su último trabajo será implementar los eventos para sus campos. Probablemente quiera disparar los tres eventos de Ext.form.field.Field , es decir change , dirtychange y validitychange .

Una vez más, la implementación será muy específica para el componente secundario que use y, para ser honesto, no he explorado demasiado este aspecto. Así que te dejaré conectar esto por ti mismo.

Sin embargo, mi conclusión preliminar es que Ext.form.field.Field ofrece a hacer todo el trabajo pesado por usted, siempre que (1) llame checkChange cuando sea necesario, y (2) la aplicación isEqual funcione con el formato de valor de su campo.

Ejemplo: campo de lista TODO

Finalmente, aquí hay un ejemplo completo de código, usando una grilla para representar un campo de lista TODO.

Puedes verlo en vivo en jsFiddle , donde trato de mostrar que el campo se comporta de manera ordenada.

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


Aquí hay un ejemplo de un panel personalizado que extiende un Panel Ext. Puede ampliar cualquier componente, consultar los documentos de los campos, métodos y eventos con los que puede jugar.

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


Dado que la pregunta fue bastante vaga, solo puedo proporcionar el patrón básico para ExtJS v4.

Incluso si no es demasiado específico, tiene el avance de que es bastante universal así:

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

El archivo /ext/src/form/field/Base.js proporciona los nombres de todas las configuraciones y funciones que pueden anularse.


He hecho esto algunas veces. Aquí está el proceso / pseudo-código general que uso:

  • Cree una extensión de campo que proporcione la reutilización más útil (generalmente Ext.form.TextField si solo desea obtener / establecer un valor de cadena)
  • En la afterrender del campo, oculte el campo de texto y cree un elemento de ajuste alrededor de this.el con this.wrap = this.resizeEl = this.positionEl = this.el.wrap()
  • this.wrap cualquier componente a this.wrap (ej. Usando renderTo: this.wrap en la configuración)
  • Sobrescribe getValue y setValue para hablar con el (los) componente (s) que has renderizado manualmente
  • Es posible que deba hacer algunos ajustes manuales en un oyente de cambio de resize si el diseño de su formulario cambia
  • ¡No olvide limpiar los componentes que crea en el método beforeDestroy !

No puedo esperar a cambiar nuestra base de código a ExtJS 4, donde este tipo de cosas son fáciles.

¡Buena suerte!


Je. Después de publicar la recompensa, descubrí que Ext.form.FieldContainer no es solo un contenedor de campo , sino un contenedor de componentes totalmente desarrollado, por lo que hay una solución simple.

Todo lo que necesita hacer es extender FieldContainer , reemplazando initComponent para agregar los componentes secundarios e implementar setValue , getValue y los métodos de validación según corresponda para su tipo de datos de valor.

Aquí hay un ejemplo con una grilla cuyo valor es una lista de objetos de pares nombre / valor:

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


Muchas de las respuestas usan el Mixin Ext.form.field.Field o simplemente se extienden sobre alguna clase ya hecha que se adapte a sus necesidades, lo cual está bien.

Pero no recomiendo sobrescribir completamente el método setValue, ¡eso es IMO realmente malo!

Sucede mucho más que simplemente configurar y obtener el valor, y si lo sobrescribe por completo, bueno, por ejemplo, se estropeará el estado sucio, el procesamiento de rawValue, etc.

Supongo que hay dos opciones, una es llamar a Parent (argumentos) dentro del método que declaras para mantener las cosas simplificadas, o al final cuando termines de aplicar el método heredado desde donde lo hayas conseguido (mixin o extender).

Pero no lo sobrescriba sin tener en cuenta lo que ese método ya hecho tras bambalinas.

También recuerde que si usa otros tipos de campo en su nueva clase, establezca la propiedad isFormField en falso; de lo contrario, su método getValues ​​en el formulario tomará esos valores y se ejecutará con em.


Para extender la respuesta de @RobAgar, siguiendo un campo de fecha y hora realmente simple que escribí para ExtJS 3 y que es quickport que hice para ExtJS 4. Lo importante es el uso de Ext.form.field.Field mixin. Este mixin proporciona una interfaz común para el comportamiento lógico y el estado de los campos de formulario, que incluyen:

Métodos getter y setter para valores de campo Eventos y métodos para rastrear valores y cambios de validez Métodos para activar validación

Esto se puede usar para combinar campos múltiples y dejarlos actuar como uno solo. Para un tipo de campo personalizado total, recomiendo extender Ext.form.field.Base

Este es el ejemplo que mencioné anteriormente. Debería explicarse lo fácil que se puede hacer incluso para algo así como un objeto de fecha en el que tenemos que formatear los datos en el getter y el 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) } });


Siguiendo la documentación en Ext.form.field.Base

Este código creará un campo de estilo TypeAhead / Autocomplete reutilizable para seleccionar un idioma.

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

Puede usar el campo de una forma simplemente estableciendo el xtype en el nombre del widget:

{ xtype: ''LangSelector'' fieldLabel: ''Language'', name: ''lang'' }