Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / grid / property / Grid.js
1 /**
2  * @class Ext.grid.property.Grid
3  * @extends Ext.grid.Panel
4  * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
5  * development IDEs.  Each row in the grid represents a property of some object, and the data is stored
6  * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}.  Example usage:
7  * <pre><code>
8 var grid = new Ext.grid.property.Grid({
9     title: 'Properties Grid',
10     width: 300,
11     renderTo: 'grid-ct',
12     source: {
13         "(name)": "My Object",
14         "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
15         "Available": false,
16         "Version": .01,
17         "Description": "A test object"
18     }
19 });
20 </code></pre>
21  * @constructor
22  * @param {Object} config The grid config object
23  */
24 Ext.define('Ext.grid.property.Grid', {
25
26     extend: 'Ext.grid.Panel',
27
28     alternateClassName: 'Ext.grid.PropertyGrid',
29
30     uses: [
31        'Ext.grid.plugin.CellEditing',
32        'Ext.grid.property.Store',
33        'Ext.grid.property.HeaderContainer',
34        'Ext.XTemplate',
35        'Ext.grid.CellEditor',
36        'Ext.form.field.Date',
37        'Ext.form.field.Text',
38        'Ext.form.field.Number'
39     ],
40
41    /**
42     * @cfg {Object} propertyNames An object containing custom property name/display name pairs.
43     * If specified, the display name will be shown in the name column instead of the property name.
44     */
45
46     /**
47     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
48     */
49
50     /**
51     * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
52     * the grid to support additional types of editable fields.  By default, the grid supports strongly-typed editing
53     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
54     * associated with a custom input control by specifying a custom editor.  The name of the editor
55     * type should correspond with the name of the property that will use the editor.  Example usage:
56     * <pre><code>
57 var grid = new Ext.grid.property.Grid({
58
59     // Custom editors for certain property names
60     customEditors: {
61         evtStart: Ext.create('Ext.form.TimeField' {selectOnFocus:true})
62     },
63
64     // Displayed name for property names in the source
65     propertyNames: {
66         evtStart: 'Start Time'
67     },
68
69     // Data object containing properties to edit
70     source: {
71         evtStart: '10:00 AM'
72     }
73 });
74 </code></pre>
75     */
76
77     /**
78     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
79     */
80
81     /**
82     * @cfg {Object} customRenderers An object containing name/value pairs of custom renderer type definitions that allow
83     * the grid to support custom rendering of fields.  By default, the grid supports strongly-typed rendering
84     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
85     * associated with the type of the value.  The name of the renderer type should correspond with the name of the property
86     * that it will render.  Example usage:
87     * <pre><code>
88 var grid = Ext.create('Ext.grid.property.Grid', {
89     customRenderers: {
90         Available: function(v){
91             if (v) {
92                 return '<span style="color: green;">Yes</span>';
93             } else {
94                 return '<span style="color: red;">No</span>';
95             }
96         }
97     },
98     source: {
99         Available: true
100     }
101 });
102 </code></pre>
103     */
104
105     /**
106      * @cfg {String} valueField
107      * Optional. The name of the field from the property store to use as the value field name. Defaults to <code>'value'</code>
108      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
109      */
110     valueField: 'value',
111
112     /**
113      * @cfg {String} nameField
114      * Optional. The name of the field from the property store to use as the property field name. Defaults to <code>'name'</code>
115      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
116      */
117     nameField: 'name',
118
119     // private config overrides
120     enableColumnMove: false,
121     columnLines: true,
122     stripeRows: false,
123     trackMouseOver: false,
124     clicksToEdit: 1,
125     enableHdMenu: false,
126
127     // private
128     initComponent : function(){
129         var me = this;
130
131         me.addCls(Ext.baseCSSPrefix + 'property-grid');
132         me.plugins = me.plugins || [];
133
134         // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
135         me.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {
136             clicksToEdit: me.clicksToEdit,
137
138             // Inject a startEdit which always edits the value column
139             startEdit: function(record, column) {
140                 // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
141                 Ext.grid.plugin.CellEditing.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
142             }
143         }));
144
145         me.selModel = {
146             selType: 'cellmodel',
147             onCellSelect: function(position) {
148                 if (position.column != 1) {
149                     position.column = 1;
150                     Ext.selection.CellModel.prototype.onCellSelect.call(this, position);
151                 }
152             }
153         };
154         me.customRenderers = me.customRenderers || {};
155         me.customEditors = me.customEditors || {};
156
157         // Create a property.Store from the source object unless configured with a store
158         if (!me.store) {
159             me.propStore = me.store = Ext.create('Ext.grid.property.Store', me, me.source);
160         }
161
162         me.store.sort('name', 'ASC');
163         me.columns = Ext.create('Ext.grid.property.HeaderContainer', me, me.store);
164
165         me.addEvents(
166             /**
167              * @event beforepropertychange
168              * Fires before a property value changes.  Handlers can return false to cancel the property change
169              * (this will internally call {@link Ext.data.Record#reject} on the property's record).
170              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
171              * as the {@link #source} config property).
172              * @param {String} recordId The record's id in the data store
173              * @param {Mixed} value The current edited property value
174              * @param {Mixed} oldValue The original property value prior to editing
175              */
176             'beforepropertychange',
177             /**
178              * @event propertychange
179              * Fires after a property value has changed.
180              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
181              * as the {@link #source} config property).
182              * @param {String} recordId The record's id in the data store
183              * @param {Mixed} value The current edited property value
184              * @param {Mixed} oldValue The original property value prior to editing
185              */
186             'propertychange'
187         );
188         me.callParent();
189
190         // Inject a custom implementation of walkCells which only goes up or down
191         me.getView().walkCells = this.walkCells;
192
193         // Set up our default editor set for the 4 atomic data types
194         me.editors = {
195             'date'    : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Date',   {selectOnFocus: true})}),
196             'string'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Text',   {selectOnFocus: true})}),
197             'number'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Number', {selectOnFocus: true})}),
198             'boolean' : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.ComboBox', {
199                 editable: false,
200                 store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
201             })})
202         };
203
204         // Track changes to the data so we can fire our events.
205         this.store.on('update', me.onUpdate, me);
206     },
207
208     // private
209     onUpdate : function(store, record, operation) {
210         var me = this,
211             v, oldValue;
212
213         if (operation == Ext.data.Model.EDIT) {
214             v = record.get(me.valueField);
215             oldValue = record.modified.value;
216             if (me.fireEvent('beforepropertychange', me.source, record.id, v, oldValue) !== false) {
217                 if (me.source) {
218                     me.source[record.id] = v;
219                 }
220                 record.commit();
221                 me.fireEvent('propertychange', me.source, record.id, v, oldValue);
222             } else {
223                 record.reject();
224             }
225         }
226     },
227
228     // Custom implementation of walkCells which only goes up and down.
229     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
230         if (direction == 'left') {
231             direction = 'up';
232         } else if (direction == 'right') {
233             direction = 'down';
234         }
235         var pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
236         if (!pos.column) {
237             pos.column = 1;
238         }
239         return pos;
240     },
241
242     // private
243     // returns the correct editor type for the property type, or a custom one keyed by the property name
244     getCellEditor : function(record, column) {
245         var me = this,
246             propName = record.get(me.nameField), 
247             val = record.get(me.valueField),
248             editor = me.customEditors[propName];
249
250         // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
251         // If it's not even a Field, just a config object, instantiate it before wrapping it.
252         if (editor) {
253             if (!(editor instanceof Ext.grid.CellEditor)) {
254                 if (!(editor instanceof Ext.form.field.Base)) {
255                     editor = Ext.ComponentManager.create(editor, 'textfield');
256                 }
257                 editor = me.customEditors[propName] = Ext.create('Ext.grid.CellEditor', { field: editor });
258             }
259         } else if (Ext.isDate(val)) {
260             editor = me.editors.date;
261         } else if (Ext.isNumber(val)) {
262             editor = me.editors.number;
263         } else if (Ext.isBoolean(val)) {
264             editor = me.editors['boolean'];
265         } else {
266             editor = me.editors.string;
267         }
268
269         // Give the editor a unique ID because the CellEditing plugin caches them
270         editor.editorId = propName;
271         return editor;
272     },
273
274     beforeDestroy: function() {
275         var me = this;
276         me.callParent();
277         me.destroyEditors(me.editors);
278         me.destroyEditors(me.customEditors);
279         delete me.source;
280     },
281
282     destroyEditors: function (editors) {
283         for (var ed in editors) {
284             Ext.destroy(editors[ed]);
285         }
286     },
287
288     /**
289      * Sets the source data object containing the property data.  The data object can contain one or more name/value
290      * pairs representing all of the properties of an object to display in the grid, and this data will automatically
291      * be loaded into the grid's {@link #store}.  The values should be supplied in the proper data type if needed,
292      * otherwise string type will be assumed.  If the grid already contains data, this method will replace any
293      * existing data.  See also the {@link #source} config value.  Example usage:
294      * <pre><code>
295 grid.setSource({
296     "(name)": "My Object",
297     "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),  // date type
298     "Available": false,  // boolean type
299     "Version": .01,      // decimal type
300     "Description": "A test object"
301 });
302 </code></pre>
303      * @param {Object} source The data object
304      */
305     setSource: function(source) {
306         this.source = source;
307         this.propStore.setSource(source);
308     },
309
310     /**
311      * Gets the source data object containing the property data.  See {@link #setSource} for details regarding the
312      * format of the data object.
313      * @return {Object} The data object
314      */
315     getSource: function() {
316         return this.propStore.getSource();
317     },
318
319     /**
320      * Sets the value of a property.
321      * @param {String} prop The name of the property to set
322      * @param {Mixed} value The value to test
323      * @param {Boolean} create (Optional) True to create the property if it doesn't already exist. Defaults to <tt>false</tt>.
324      */
325     setProperty: function(prop, value, create) {
326         this.propStore.setValue(prop, value, create);
327     },
328
329     /**
330      * Removes a property from the grid.
331      * @param {String} prop The name of the property to remove
332      */
333     removeProperty: function(prop) {
334         this.propStore.remove(prop);
335     }
336
337     /**
338      * @cfg store
339      * @hide
340      */
341     /**
342      * @cfg colModel
343      * @hide
344      */
345     /**
346      * @cfg cm
347      * @hide
348      */
349     /**
350      * @cfg columns
351      * @hide
352      */
353 });