Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / data / Field.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @author Ed Spencer
17  *
18  * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
19  * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
20  * Ext.data.Model Model}. For example, we might set up a model like this:
21  *
22  *     Ext.define('User', {
23  *         extend: 'Ext.data.Model',
24  *         fields: [
25  *             'name', 'email',
26  *             {name: 'age', type: 'int'},
27  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
28  *         ]
29  *     });
30  *
31  * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
32  * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
33  * up with the 'auto' type. It's as if we'd done this instead:
34  *
35  *     Ext.define('User', {
36  *         extend: 'Ext.data.Model',
37  *         fields: [
38  *             {name: 'name', type: 'auto'},
39  *             {name: 'email', type: 'auto'},
40  *             {name: 'age', type: 'int'},
41  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
42  *         ]
43  *     });
44  *
45  * # Types and conversion
46  *
47  * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
48  * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
49  * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
50  *
51  * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
52  * this using a {@link #convert} function. Here, we're going to create a new field based on another:
53  *
54  *     Ext.define('User', {
55  *         extend: 'Ext.data.Model',
56  *         fields: [
57  *             'name', 'email',
58  *             {name: 'age', type: 'int'},
59  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'},
60  *
61  *             {
62  *                 name: 'firstName',
63  *                 convert: function(value, record) {
64  *                     var fullName  = record.get('name'),
65  *                         splits    = fullName.split(" "),
66  *                         firstName = splits[0];
67  *
68  *                     return firstName;
69  *                 }
70  *             }
71  *         ]
72  *     });
73  *
74  * Now when we create a new User, the firstName is populated automatically based on the name:
75  *
76  *     var ed = Ext.create('User', {name: 'Ed Spencer'});
77  *
78  *     console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
79  *
80  * In fact, if we log out all of the data inside ed, we'll see this:
81  *
82  *     console.log(ed.data);
83  *
84  *     //outputs this:
85  *     {
86  *         age: 0,
87  *         email: "",
88  *         firstName: "Ed",
89  *         gender: "Unknown",
90  *         name: "Ed Spencer"
91  *     }
92  *
93  * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
94  * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
95  * that now. Let's correct that and satisfy ourselves that the types work as we expect:
96  *
97  *     ed.set('gender', 'Male');
98  *     ed.get('gender'); //returns 'Male'
99  *
100  *     ed.set('age', 25.4);
101  *     ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
102  */
103 Ext.define('Ext.data.Field', {
104     requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
105     alias: 'data.field',
106     
107     constructor : function(config) {
108         if (Ext.isString(config)) {
109             config = {name: config};
110         }
111         Ext.apply(this, config);
112         
113         var types = Ext.data.Types,
114             st = this.sortType,
115             t;
116
117         if (this.type) {
118             if (Ext.isString(this.type)) {
119                 this.type = types[this.type.toUpperCase()] || types.AUTO;
120             }
121         } else {
122             this.type = types.AUTO;
123         }
124
125         // named sortTypes are supported, here we look them up
126         if (Ext.isString(st)) {
127             this.sortType = Ext.data.SortTypes[st];
128         } else if(Ext.isEmpty(st)) {
129             this.sortType = this.type.sortType;
130         }
131
132         if (!this.convert) {
133             this.convert = this.type.convert;
134         }
135     },
136     
137     /**
138      * @cfg {String} name
139      *
140      * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
141      * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
142      *
143      * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
144      * just a String for the field name.
145      */
146     
147     /**
148      * @cfg {String/Object} type
149      *
150      * The data type for automatic conversion from received data to the *stored* value if
151      * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
152      * Possible values are
153      *
154      * - auto (Default, implies no conversion)
155      * - string
156      * - int
157      * - float
158      * - boolean
159      * - date
160      *
161      * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
162      *
163      * Developers may create their own application-specific data types by defining new members of the {@link
164      * Ext.data.Types} class.
165      */
166     
167     /**
168      * @cfg {Function} convert
169      *
170      * A function which converts the value provided by the Reader into an object that will be stored in the Model.
171      * It is passed the following parameters:
172      *
173      * - **v** : Mixed
174      *
175      *   The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
176      *   defaultValue}`.
177      *
178      * - **rec** : Ext.data.Model
179      *
180      *   The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
181      *   at this point as the fields are read in the order that they are defined in your
182      *   {@link Ext.data.Model#fields fields} array.
183      *
184      * Example of convert functions:
185      *
186      *     function fullName(v, record){
187      *         return record.name.last + ', ' + record.name.first;
188      *     }
189      *
190      *     function location(v, record){
191      *         return !record.city ? '' : (record.city + ', ' + record.state);
192      *     }
193      *
194      *     Ext.define('Dude', {
195      *         extend: 'Ext.data.Model',
196      *         fields: [
197      *             {name: 'fullname',  convert: fullName},
198      *             {name: 'firstname', mapping: 'name.first'},
199      *             {name: 'lastname',  mapping: 'name.last'},
200      *             {name: 'city', defaultValue: 'homeless'},
201      *             'state',
202      *             {name: 'location',  convert: location}
203      *         ]
204      *     });
205      *
206      *     // create the data store
207      *     var store = Ext.create('Ext.data.Store', {
208      *         reader: {
209      *             type: 'json',
210      *             model: 'Dude',
211      *             idProperty: 'key',
212      *             root: 'daRoot',
213      *             totalProperty: 'total'
214      *         }
215      *     });
216      *
217      *     var myData = [
218      *         { key: 1,
219      *           name: { first: 'Fat',    last:  'Albert' }
220      *           // notice no city, state provided in data object
221      *         },
222      *         { key: 2,
223      *           name: { first: 'Barney', last:  'Rubble' },
224      *           city: 'Bedrock', state: 'Stoneridge'
225      *         },
226      *         { key: 3,
227      *           name: { first: 'Cliff',  last:  'Claven' },
228      *           city: 'Boston',  state: 'MA'
229      *         }
230      *     ];
231      */
232
233     /**
234      * @cfg {String} dateFormat
235      *
236      * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
237      *
238      * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
239      * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
240      * timestamp. See {@link Ext.Date}.
241      */
242     dateFormat: null,
243     
244     /**
245      * @cfg {Boolean} useNull
246      *
247      * Use when converting received data into a Number type (either int or float). If the value cannot be
248      * parsed, null will be used if useNull is true, otherwise the value will be 0. Defaults to false.
249      */
250     useNull: false,
251     
252     /**
253      * @cfg {Object} defaultValue
254      *
255      * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
256      * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
257      * (i.e. undefined). Defaults to "".
258      */
259     defaultValue: "",
260
261     /**
262      * @cfg {String/Number} mapping
263      *
264      * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
265      * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
266      * as the field name, the mapping may be omitted.
267      *
268      * The form of the mapping expression depends on the Reader being used.
269      *
270      * - {@link Ext.data.reader.Json}
271      *
272      *   The mapping is a string containing the javascript expression to reference the data from an element of the data
273      *   item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.
274      *
275      * - {@link Ext.data.reader.Xml}
276      *
277      *   The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
278      *   {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
279      *
280      * - {@link Ext.data.reader.Array}
281      *
282      *   The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
283      *   Array position.
284      *
285      * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
286      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
287      * return the desired data.
288      */
289     mapping: null,
290
291     /**
292      * @cfg {Function} sortType
293      *
294      * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
295      * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
296      *
297      *     // current sort     after sort we want
298      *     // +-+------+          +-+------+
299      *     // |1|First |          |1|First |
300      *     // |2|Last  |          |3|Second|
301      *     // |3|Second|          |2|Last  |
302      *     // +-+------+          +-+------+
303      *
304      *     sortType: function(value) {
305      *        switch (value.toLowerCase()) // native toLowerCase():
306      *        {
307      *           case 'first': return 1;
308      *           case 'second': return 2;
309      *           default: return 3;
310      *        }
311      *     }
312      */
313     sortType : null,
314
315     /**
316      * @cfg {String} sortDir
317      *
318      * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
319      */
320     sortDir : "ASC",
321
322     /**
323      * @cfg {Boolean} allowBlank
324      * @private
325      *
326      * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
327      * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
328      */
329     allowBlank : true,
330
331     /**
332      * @cfg {Boolean} persist
333      *
334      * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
335      * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
336      * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
337      */
338     persist: true
339 });
340