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