commit extjs-2.2.1
[extjs.git] / source / data / Record.js
1 /*\r
2  * Ext JS Library 2.2.1\r
3  * Copyright(c) 2006-2009, Ext JS, LLC.\r
4  * licensing@extjs.com\r
5  * \r
6  * http://extjs.com/license\r
7  */\r
8 \r
9 /**\r
10 * @class Ext.data.Record\r
11  * <p>Instances of this class encapsulate both Record <em>definition</em> information, and Record\r
12  * <em>value</em> information for use in {@link Ext.data.Store} objects, or any code which needs\r
13  * to access Records cached in an {@link Ext.data.Store} object.</p>\r
14  * <p>Constructors for this class are generated by passing an Array of field definition objects to {@link #create}.\r
15  * Instances are usually only created by {@link Ext.data.Reader} implementations when processing unformatted data\r
16  * objects.</p>\r
17  * <p>Note that an instance of a Record class may only belong to one {@link Ext.data.Store Store} at a time.\r
18  * In order to copy data from one Store to another, use the {@link #copy} method to create an exact\r
19  * copy of the Record, and insert the new instance into the other Store.</p>\r
20  * <p>When serializing a Record for submission to the server, be aware that it contains many private\r
21  * properties, and also a reference to its owning Store which in turn holds references to its Records.\r
22  * This means that a Record may not be encoded using {@link Ext.util.JSON.encode}. Instead, use the\r
23  * {@link data} and {@link id} properties.</p>\r
24  * Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.\r
25  * @constructor\r
26  * This constructor should not be used to create Record objects. Instead, use the constructor generated by\r
27  * {@link #create}. The parameters are the same.\r
28  * @param {Array} data An object, the properties of which provide values for the new Record's fields.\r
29  * @param {Object} id (Optional) The id of the Record. This id should be unique, and is used by the\r
30  * {@link Ext.data.Store} object which owns the Record to index its collection of Records. If\r
31  * not specified an integer id is generated.\r
32  */\r
33 Ext.data.Record = function(data, id){\r
34     this.id = (id || id === 0) ? id : ++Ext.data.Record.AUTO_ID;\r
35     this.data = data;\r
36 };\r
37 \r
38 /**\r
39  * Generate a constructor for a specific Record layout.\r
40  * @param {Array} o An Array of field definition objects which specify field names, and optionally,\r
41  * data types, and a mapping for an {@link Ext.data.Reader} to extract the field's value from a data object.\r
42  * Each field definition object may contain the following properties: <ul>\r
43  * <li><b>name</b> : String<div class="sub-desc">The name by which the field is referenced within the Record. This is referenced by,\r
44  * for example, the <em>dataIndex</em> property in column definition objects passed to {@link Ext.grid.ColumnModel}</div></li>\r
45  * <li><b>mapping</b> : String<div class="sub-desc">(Optional) A path specification for use by the {@link Ext.data.Reader} implementation\r
46  * that is creating the Record to access the data value from the data object. If an {@link Ext.data.JsonReader}\r
47  * is being used, then this is a string containing the javascript expression to reference the data relative to\r
48  * the Record item's root. If an {@link Ext.data.XmlReader} is being used, this is an {@link Ext.DomQuery} path\r
49  * to the data item relative to the Record element. If the mapping expression is the same as the field name,\r
50  * this may be omitted.</div></li>\r
51  * <li><b>type</b> : String<div class="sub-desc">(Optional) The data type for conversion to displayable value. Possible values are\r
52  * <ul><li>auto (Default, implies no conversion)</li>\r
53  * <li>string</li>\r
54  * <li>int</li>\r
55  * <li>float</li>\r
56  * <li>boolean</li>\r
57  * <li>date</li></ul></div></li>\r
58  * <li><b>sortType</b> : Function<div class="sub-desc">(Optional) A function which converts a Field's value to a comparable value\r
59  * in order to ensure correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}.</div></li>\r
60  * <li><b>sortDir</b> : String<div class="sub-desc">(Optional) Initial direction to sort. "ASC" or "DESC"</div></li>\r
61  * <li><b>convert</b> : Function<div class="sub-desc">(Optional) A function which converts the value provided\r
62  * by the Reader into an object that will be stored in the Record. It is passed the\r
63  * following parameters:<ul>\r
64  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader.</div></li>\r
65  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.\r
66  * Depending on Reader type, this could be an Array, an object, or an XML element.</div></li>\r
67  * </ul></div></li>\r
68  * <li><b>dateFormat</b> : String<div class="sub-desc">(Optional) A format string for the {@link Date#parseDate Date.parseDate} function,\r
69  * or "timestamp" if the value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a \r
70  * javascript millisecond timestamp.</div></li>\r
71  * <li><b>defaultValue</b> : Mixed<div class="sub-desc">(Optional) The default value used <b>when a Record is being created by a\r
72  * {@link Ext.data.Reader Reader}</b> when the item referenced by the <b><tt>mapping</tt></b> does not exist in the data object\r
73  * (i.e. undefined). (defaults to "")</div></li>\r
74  * </ul>\r
75  * The constructor generated by this method may be used to create new Record instances. The data object must contain properties\r
76  * named after the field <b>names</b>. \r
77  * <br>usage:<br><pre><code>\r
78 var TopicRecord = Ext.data.Record.create([\r
79     {name: 'title', mapping: 'topic_title'},\r
80     {name: 'author', mapping: 'username'},\r
81     {name: 'totalPosts', mapping: 'topic_replies', type: 'int'},\r
82     {name: 'lastPost', mapping: 'post_time', type: 'date'},\r
83     {name: 'lastPoster', mapping: 'user2'},\r
84     {name: 'excerpt', mapping: 'post_text'}\r
85 ]);\r
86 \r
87 var myNewRecord = new TopicRecord({\r
88     title: 'Do my job please',\r
89     author: 'noobie',\r
90     totalPosts: 1,\r
91     lastPost: new Date(),\r
92     lastPoster: 'Animal',\r
93     excerpt: 'No way dude!'\r
94 });\r
95 myStore.add(myNewRecord);\r
96 </code></pre>\r
97  * <p>In the simplest case, if no properties other than <tt>name</tt> are required, a field definition\r
98  * may consist of just a field name string.</p>\r
99  * @method create\r
100  * @return {function} A constructor which is used to create new Records according\r
101  * to the definition.\r
102  * @static\r
103  */\r
104 Ext.data.Record.create = function(o){\r
105     var f = Ext.extend(Ext.data.Record, {});\r
106     var p = f.prototype;\r
107     p.fields = new Ext.util.MixedCollection(false, function(field){\r
108         return field.name;\r
109     });\r
110     for(var i = 0, len = o.length; i < len; i++){\r
111         p.fields.add(new Ext.data.Field(o[i]));\r
112     }\r
113     f.getField = function(name){\r
114         return p.fields.get(name);\r
115     };\r
116     return f;\r
117 };\r
118 \r
119 Ext.data.Record.AUTO_ID = 1000;\r
120 Ext.data.Record.EDIT = 'edit';\r
121 Ext.data.Record.REJECT = 'reject';\r
122 Ext.data.Record.COMMIT = 'commit';\r
123 \r
124 Ext.data.Record.prototype = {\r
125     /**\r
126      * <p><b>This property is stored in the Record definition's <u>prototype</u></b></p>\r
127      * A MixedCollection containing the defined {@link Ext.data.Field Field}s for this Record.  Read-only.\r
128      * @property fields\r
129      * @type Ext.util.MixedCollection\r
130      */\r
131     /**\r
132      * An object hash representing the data for this Record. Every field name in the Record definition\r
133      * is represented by a property of that name in this object. Note that unless you specified a field\r
134      * with name "id" in the Record definition, this will <b>not</b> contain an <tt>id</tt> property.\r
135      * @property data\r
136      * @type {Object}\r
137      */\r
138     /**\r
139      * The unique ID of the Record as specified at construction time.\r
140      * @property id\r
141      * @type {Object}\r
142      */\r
143     /**\r
144      * Readonly flag - true if this Record has been modified.\r
145      * @type Boolean\r
146      */\r
147     dirty : false,\r
148     editing : false,\r
149     error: null,\r
150     /**\r
151      * This object contains a key and value storing the original values of all modified fields or is null if no fields have been modified.\r
152      * @property modified\r
153      * @type {Object}\r
154      */\r
155     modified: null,\r
156 \r
157     // private\r
158     join : function(store){\r
159         this.store = store;\r
160     },\r
161 \r
162     /**\r
163      * Set the named field to the specified value.\r
164      * @param {String} name The name of the field to set.\r
165      * @param {Object} value The value to set the field to.\r
166      */\r
167     set : function(name, value){\r
168         if(String(this.data[name]) == String(value)){\r
169             return;\r
170         }\r
171         this.dirty = true;\r
172         if(!this.modified){\r
173             this.modified = {};\r
174         }\r
175         if(typeof this.modified[name] == 'undefined'){\r
176             this.modified[name] = this.data[name];\r
177         }\r
178         this.data[name] = value;\r
179         if(!this.editing && this.store){\r
180             this.store.afterEdit(this);\r
181         }\r
182     },\r
183 \r
184     /**\r
185      * Get the value of the named field.\r
186      * @param {String} name The name of the field to get the value of.\r
187      * @return {Object} The value of the field.\r
188      */\r
189     get : function(name){\r
190         return this.data[name];\r
191     },\r
192 \r
193     /**\r
194      * Begin an edit. While in edit mode, no events are relayed to the containing store.\r
195      */\r
196     beginEdit : function(){\r
197         this.editing = true;\r
198         this.modified = {};\r
199     },\r
200 \r
201     /**\r
202      * Cancels all changes made in the current edit operation.\r
203      */\r
204     cancelEdit : function(){\r
205         this.editing = false;\r
206         delete this.modified;\r
207     },\r
208 \r
209     /**\r
210      * End an edit. If any data was modified, the containing store is notified.\r
211      */\r
212     endEdit : function(){\r
213         this.editing = false;\r
214         if(this.dirty && this.store){\r
215             this.store.afterEdit(this);\r
216         }\r
217     },\r
218 \r
219     /**\r
220      * Usually called by the {@link Ext.data.Store} which owns the Record.\r
221      * Rejects all changes made to the Record since either creation, or the last commit operation.\r
222      * Modified fields are reverted to their original values.\r
223      * <p>\r
224      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified\r
225      * of reject operations.\r
226      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change (defaults to false)\r
227      */\r
228     reject : function(silent){\r
229         var m = this.modified;\r
230         for(var n in m){\r
231             if(typeof m[n] != "function"){\r
232                 this.data[n] = m[n];\r
233             }\r
234         }\r
235         this.dirty = false;\r
236         delete this.modified;\r
237         this.editing = false;\r
238         if(this.store && silent !== true){\r
239             this.store.afterReject(this);\r
240         }\r
241     },\r
242 \r
243     /**\r
244      * Usually called by the {@link Ext.data.Store} which owns the Record.\r
245      * Commits all changes made to the Record since either creation, or the last commit operation.\r
246      * <p>\r
247      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified\r
248      * of commit operations.\r
249      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change (defaults to false)\r
250      */\r
251     commit : function(silent){\r
252         this.dirty = false;\r
253         delete this.modified;\r
254         this.editing = false;\r
255         if(this.store && silent !== true){\r
256             this.store.afterCommit(this);\r
257         }\r
258     },\r
259 \r
260     /**\r
261      * Gets a hash of only the fields that have been modified since this Record was created or commited.\r
262      * @return Object\r
263      */\r
264     getChanges : function(){\r
265         var m = this.modified, cs = {};\r
266         for(var n in m){\r
267             if(m.hasOwnProperty(n)){\r
268                 cs[n] = this.data[n];\r
269             }\r
270         }\r
271         return cs;\r
272     },\r
273 \r
274     // private\r
275     hasError : function(){\r
276         return this.error != null;\r
277     },\r
278 \r
279     // private\r
280     clearError : function(){\r
281         this.error = null;\r
282     },\r
283 \r
284     /**\r
285      * Creates a copy of this Record.\r
286      * @param {String} id (optional) A new Record id if you don't want to use this Record's id\r
287      * @return {Record}\r
288      */\r
289     copy : function(newId) {\r
290         return new this.constructor(Ext.apply({}, this.data), newId || this.id);\r
291     },\r
292 \r
293     /**\r
294      * Returns true if the field passed has been modified since the load or last commit.\r
295      * @param {String} fieldName\r
296      * @return {Boolean}\r
297      */\r
298     isModified : function(fieldName){\r
299         return !!(this.modified && this.modified.hasOwnProperty(fieldName));\r
300     }\r
301 };