UX - Component Column

Skirtle.grid.column.Component is an extension of Ext.grid.column.Column that allows for an ExtJS component to be returned from the renderer function. It is intended to be used in small grids where performance is not a major concern and the effort involved in simulating a component using HTML and CSS is an unnecessarily complicated alternative.

Widget Column did not exist when this component was first created. If possible you should use Widget Column instead.

Download

Note: You will also need to download a compatible version of CTemplate. Latest versions should be compatible.

VersionDate
1.228-Oct-2020Download
1.107-May-2013Download
1.022-Sep-2011Download

View release notes.

Examples

This first demo shows a selection of popular form fields rendered in a grid. Saving changes to values (even locally) would detract from demonstrating the core functionality so it isn't shown. Sorting is disabled as that would refresh the view and lose any unsaved changes.

            Ext.create('Ext.grid.Panel', {
                ...
                columns: [
                    {
                        ...
                        dataIndex: 'name',
                        xtype: 'componentcolumn',

                        renderer: function(name, meta, record) {
                            meta.tdCls = record.get('sex') + '-icon';

                            return {
                                value: name,
                                xtype: 'textfield',

                                listeners: {
                                    inputEl: {
                                        keydown: function(ev) {
                                            // Prevent the event propagating as far as the grid, otherwise
                                            // navigation keys won't function correctly in the textfield
                                            ev.stopPropagation();
                                        }
                                    }
                                }
                            };
                        }
                    }, {
                        ...
                        dataIndex: 'status',
                        xtype: 'componentcolumn',

                        renderer: function(status) {
                            return {
                                ...
                                store: ['Available', 'Away', 'Busy', 'Offline'],
                                value: status,
                                xtype: 'combobox'
                            };
                        }
                    }, {
                        ...
                        dataIndex: 'enabled',
                        xtype: 'componentcolumn',

                        renderer: function(enabled) {
                            return {
                                checked: enabled,
                                xtype: 'checkbox'
                            };
                        }
                    }
                ],

                store: {
                    fields: ['sex', 'name', 'status', 'enabled'],

                    data: [
                        {sex: 'male', name: 'Tom', status: 'Available', enabled: true},
                        ...
                    ]
                }
            });
        

This example shows buttons and progressbars. The status of the progressbar is saved on the record so sorting works and is enabled on the first two columns. This example also demonstrates a vertical scrollbar. Clicking the buttons will decrease the row heights and eventually the scrollbar should disappear.

            Ext.create('Ext.grid.Panel', {
                ...
                columns: [
                    {
                        ...
                    }, {
                        ...
                    }, {
                        ...
                        dataIndex: 'downloading',
                        xtype: 'componentcolumn',

                        renderer: function(value, m, record) {
                            if (value === 100) {
                                return 'Complete';
                            }

                            if (Ext.isDefined(value)) {
                                setTimeout(function() {
                                    // This works because calling set() causes a view refresh
                                    record.set('downloading', value + 5);
                                }, 250);

                                return {
                                    animate: false,
                                    value: value / 100,
                                    xtype: 'progressbar'
                                };
                            }

                            return {
                                text: 'Download',
                                xtype: 'button',

                                handler: function() {
                                    record.set('downloading', 0);
                                }
                            };
                        }
                    }
                ],

                store: {
                    fields: ['file', 'size'],

                    data: [
                        {file: 'readme.txt', size: 3013},
                        ...
                    ]
                }
            });
        

Compatibility

Version 1.2 has been tested against the following ExtJS versions:

  • 4.0.7
  • 4.2.2
  • 5.1.0
  • 7.3.0

It should work against other versions too but they haven't been tested.

Version 1.1 was tested against the following ExtJS versions:

  • 4.0.7 (only partial support for component queries)
  • 4.1.1
  • 4.2.0

Browsers tested (version 1.1):

  • Internet Explorer 6
  • Internet Explorer 7
  • Internet Explorer 8
  • Internet Explorer 9
  • Internet Explorer 10 (using ExtJS 4.2.0)
  • Firefox 20
  • Safari 6.0.1
  • Chrome 26
  • Opera 12.15

All of the browser-specific workarounds are still included in version 1.2, so it should continue to work with older browsers.

How It Works

Rendering components within a grid cell poses numerous problems including performance, sizing, state management, accessibility and component destruction. Whilst Component Column attempts to address some of these issues it should be used with consideration for those that remain.

The initial challenge is to render the component in the right place. Rendering a component into a grid cell is a common requirement and many people will be familiar with the techniques that can be used to inject a component after-the-fact. The core infrastructure for this in Component Column has been factored out into a separate class called CTemplate that attempts to address the slightly more general problem of component injection in an XTemplate.

After the initial rendering, the next challenge is sizing. By default the component's width will be managed to fit the column's width. Resizing is done using methods on the component so features like minimum and maximum widths should be respected. Whilst more advanced sizing could be done by overriding methods in Component Column, the config setting autoWidthComponents can be set to false to disable automatic sizing completely.

Another aspect of sizing is scrollbars. Attempts have been made to try to cope with vertical scrolling changes resulting from a component changing its height. Note that this relies upon the resize event and some components do not support this event or fire it before an animation is complete, resulting in scrollbar problems. While it is probably possible to handle each of these problem cases individually there does not currently appear to be a generic way to cover them all. Hopefully enough hooks have been provided to perform the required overrides.

One of the most serious problems facing any code that renders components in a grid is that of destroying the components. All sorts of things can cause a grid to re-render a row and it will discard the HTML without destroying the underlying component. Component Column attacks this problem in two ways.

  • If the column is destroyed it will destroy all of the components it has injected. This covers the cases where the grid is destroyed or the columns are reconfigured.
  • Relevant events on the grid's view are monitored. These events do not provide a direct way to discover which components are no longer used so Component Column maintains a list of all injected components for checking. If a component is no longer in the DOM it is assumed it can be destroyed. This can prove problematic if the same component is reused every time the same cell is rendered as it may be temporarily absent from the DOM but isn't suitable for garbage collection. The config option componentGC can be set to false to disable the automatic component destruction, though this can lead to components leaking if care is not taken to manage their destruction elsewhere.

As of version 1.1, Component Column supports component queries, allowing much better compatibility with the ExtJS MVC. The injected components will be treated as children of the column. Child queries are supported by a custom implementation of getRefItems. Internally this uses the same list of child ids as garbage collection. Parent queries are only supported against ExtJS 4.1+, relying on overriding getBubbleTarget or getRefOwner directly on the child component. This has a similar impact to setting the ownerCt but without the potential for side-effects resulting from the implied container-child relationship.

By default, grid cells have several CSS styles applied that will be inherited by the elements of the injected components. Currently Component Column makes no attempt to reset these styles. One of the more invasive properties is whitespace: nowrap, which can cause unexpected wrapping behaviour for long passages of text. Such problems should be easy to fix with a little CSS.

This leaves the related issues of state management and accessibility. The key problem is that, in theory, the grid may choose to re-render a row at any time. In many cases the solution is simple, state should be stored in the underlying record so that a re-render won't lose the state. However, the state of a component can be much more complicated than a simple checkbox value and once you start considering scrollbars and element focus it can be a little daunting. Component Column does not currently make any attempt to solve these problems.

Bugs

Feedback, bugs and feature requests should be submitted via GitHub issues at https://github.com/skirtles-code/extjs-component-column.

For discussion of older issues see the relevant forum page.

License

MIT.

Prior to version 1.2, this project used a bespoke license. You may continue using that license if you prefer.