the singularity of being and nothingness
ExtJS 4: Custom Editor for Property Grid
Yesterday, I was helping someone figure out how to add a custom, complex editor for use in a ExtJS Property Grid.
The Anatomy of a Property Grid
For some context, a Property Grid is like an editable grid, but instead of editing rows of data, you use a grid like interface to edit the properties of a single object. Think of it like the interface you’d use when defining database properties in SSMS…
By default, the Property Grid has several editors for simple types like strings, dates, booleans and numbers. Better yet, ExtJS will automatically try to detect the correct type, and specify the appropriate editor for you.
Out of the Box is Never Enough
But of course, out of the box never gets you 100% of the way. Imagine that we have a cluster of properties, one property of which is a complex data type. Consider this example:
source: { name: "My Object", created: Ext.Date.parse('10/15/2006', 'm/d/Y'), timeofday: "12:00 PM", available: false, version: 0.01, description: Ext.encode({ product: 'Clorox', tagline: 'The Shinyest White!' }) }
As you can see, most of the properties are very simple. However, the last one (description) is complex: in this example, it’s a serialized object. If we were heartless, we could just use a regular textfield editor; however, it’s unlikely that the people using the form would really like having to edit a serialized object. So to avoid angry looks from the end-user, a better alternative is to create a new field altogether that will enable us to edit this complex data in a friendlier manner.
A New Field Type
Since our complex data type has two properties (product and tagline…both simple text values), the ideal way to edit this would be by providing a window of some kind that would itself contain a form which would contain textfields for the properties of the object we want to edit. Before you reply that this sounds crazy (a form of fields as a fieldtype?), think about some of the other field types that ExtJS has. For example, when you use a datefield, the actual field component has not only internal awareness of its data type (e.g., it has methods for formatting text input into a date), but also a “picker” which, although technically not a part of a field that is submitted, is nonetheless integral for managing the data that will be input into the datefield component. If datefield can do this, why can’t our custom field? Well, it can, and it will 🙂
To give away the ending, here’s what we’re ultimately going to build (try editing the “Product Description” field):
First, let’s create a new field. Since we want to have some of the goodness of the “windowed” functionality that datefield has, we’ll extend Ext.form.field.Picker. By extending this class, we will have some out-of-the-box goodness (like creation of the “window”, management of focus between the cell editor and the picker, etc.) upon which we can build out our custom requirements.
Ext.define('CustomEditorField', { extend: 'Ext.form.field.Picker', alias: 'widget.customeditorfield', editable: false, hideTrigger: true, pickerOffset: [ 0, -20 ], listeners: { focus: function( fld, e, opts ) { fld.expand(); } } })
Pretty simple. Beyond extending the picker class, we’re marking the field as NOT editable, hiding the trigger, and setting a focus listener to automatically show the picker when editing begins. Now for the good stuff.
When extending the Ext.form.field.Picker class, you are required to implement your own createPicker() method. It is in this expected method that we can create whatever we need for our custom field. I’ll share the code, and then describe what we’re doing:
Ext.define('CustomEditorField', { ..., createPicker: function() { var me = this, format = Ext.String.format; // return a component that encapsulates what we want to appear in the picker return Ext.create('Ext.form.Panel', { title: 'Enter Product Details', bodypadding:5, pickerField: me, ownerCt: me.ownerCt, renderTo: document.body, floating: true, bodyPadding:8, items: [ { xtype: 'textfield', fieldLabel: 'Product', labelAlign: 'top', anchor: '100%', name: 'product' }, { xtype: 'textfield', fieldLabel: 'Tagline', labelAlign: 'top', anchor: '100%', name: 'tagline' } ], dockedItems: [ { xtype: 'toolbar', dock: 'bottom', items: [ { xtype: 'button', name:'cancel', text:'Cancel', iconCls: 'cancelicon', handler: function( btn, e, opts ) { me.cancelEdit(); } }, '->', { xtype: 'button', name:'save', text:'Save', iconCls: 'accepticon', handler: function( btn, e, opts ) { me.applyValues(); } } ] } ], listeners: { afterrender: function( panel, opts ) { panel.getForm().setValues( Ext.decode( me.getValue() ) ); } } }) } });
A lot of lines, but really not much going on here. I’ve simply created a new form component that contains two fields (one for each of the properties I want to edit) and a menu with some buttons to handle saving/canceling. FInally, I added a listener on afterrender to apply the underlying field’s value to the picker’s form fields. Now, when I edit the property on the Property Grid, a new little window will popup and display the two property-editing fields. Nice!.
To wrap this up, I’ve also added some methods to help with saving/canceling edits made to the property fields in the picker:
Ext.define('CustomEditorField', { ..., cancelEdit: function() { var me = this; me.fireEvent( 'blur' ); me.collapse(); }, applyValues: function() { var me = this, form = me.picker, vals = form.getForm().getValues(); // set the value of the editable field me.setValue( Ext.encode( vals ) ); me.fireEvent( 'blur' ); me.collapse(); }, createPicker: function() { ... } });
Putting it All Together
Awesome, so now we have a new custom, complex field type that we can use in our Property Grid. Let’s now configure the Property Grid to use it:
Ext.create('Ext.grid.property.Grid', { title: 'Properties Grid', width: 400, renderTo: Ext.getBody(), source: { name: "My Object", created: Ext.Date.parse('10/15/2006', 'm/d/Y'), timeofday: "12:00 PM", available: false, version: 0.01, description: Ext.encode({ product: 'Clorox', tagline: 'The Shinyest White!' }) }, customEditors: { timeofday: Ext.create('Ext.form.TimeField', {selectOnFocus: true}), description: { xtype: 'customeditorfield' } }, customRenderers: { description: function( v ) { var value = Ext.decode( v ), product = value.product, tagline = value.tagline, description=''; description += '<b>' + product + '</b>: '; description += '<i>' + tagline + '</i>'; return description; }, timeofday: function( v ) { return Ext.isDate( v ) ? Ext.Date.format( v, 'g:i A' ) : v; } }, propertyNames: { name: '(name)', created: 'Created Date', timeofday: 'Time of Day', available: 'Available?', version: 'Version', description: 'Product Description' }, listeners: { beforeedit: function( editor, e, opts ) { if( e.record.get( 'name' )=='name' || e.record.get( 'name' )=='version' ) { return false; } } } });
Pretty straightforward. First, in source, I’ve defined all the properties (and their values) that I want to manage in my Property Grid. Next, in customEditors, I’ve defined custom editors to be used by particular fields in the property definition. As you can see, for description I’ve specified that I want to use customeditorfield, the xtype of the new complex field type that we created earlier. Finally, I’ve specified some customRenderers to help make the rendering of the complex data a bit more meaningful.
NOTE: The approach with customRenderers, customEditors, and propertyNames is pre-ExtJS 4.1.3 and is deprecated for future versions. For ExtJS 4.1.3 and on, you would do something more like this:
Ext.create('Ext.grid.property.Grid', { ... source: { ... description: Ext.encode({ product: 'Clorox', tagline: 'The Shinyest White!' }) }, sourceConfig: { ... description: { renderer: function( v ) { ... }, displayName: 'Description', editor: Ext.create( 'CustomEditorField', {...} ) } } });I like the new approach better, but the deprecated approach works just fine for older versions…
Wrapping Up
So, there we have it. Creating a custom, complex field type is not only possible, but pretty easy too! Now your Property Grids can deal with kind of data you can throw at it. Have fun!
Print article | This entry was posted by existdissolve on December 30, 2012 at 3:44 pm, and is filed under ExtJS. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
about 12 years ago
Dude, u made my Day!!!
After a couple of hours of research – i think roundabout 7-8 – and tons of curses, i have to say a lot of thanks to you!!!!
I only wanted to give the possibility in the space of an form-editor to set a Textarea with dynamical content, believe it could be only a couple of minutes, because its only little thing in a much more complex project.
For now, inspirated by your code, i could integrate this textarea in the property grid!
As i see now, the “little thing” was 7-8 hours strong ^^
But im also a lucky guy to found your thread!!! No i can go to bed ^^
Thanks again,
Raphael
about 11 years ago
I’ve tried to implement this for an editor grid, but it’s not working. I have some rendering problems. I’ve put it in editor’s column.
about 11 years ago
Can you expand on your post? What exactly isn’t working? Do you have a snippet of testable code? Thanks!
about 11 years ago
I usually don’t comment on articles that I run across on the web. But, sir, I just had to comment on this one. This is an amazing article and extremely helpful. I was trying to figure out how to do something like this and POW, I came across this perfectly explained article with perfect code to go along with it.
Thank you so very much! Please, keep doing what you’re doing. It really helps a lot.