]>
git.proxmox.com Git - extjs.git/blob - extjs/packages/core/src/data/Model.js
2 * A Model or Entity represents some object that your application manages. For example, one
3 * might define a Model for Users, Products, Cars, or other real-world object that we want
4 * to model in the system. Models are used by {@link Ext.data.Store stores}, which are in
5 * turn used by many of the data-bound components in Ext.
9 * Models are defined as a set of fields and any arbitrary methods and properties relevant
10 * to the model. For example:
12 * Ext.define('User', {
13 * extend: 'Ext.data.Model',
15 * {name: 'name', type: 'string'},
16 * {name: 'age', type: 'int', convert: null},
17 * {name: 'phone', type: 'string'},
18 * {name: 'alive', type: 'boolean', defaultValue: true, convert: null}
21 * changeName: function() {
22 * var oldName = this.get('name'),
23 * newName = oldName + " The Barbarian";
25 * this.set('name', newName);
29 * Now we can create instances of our User model and call any model logic we defined:
31 * var user = Ext.create('User', {
35 * phone: '555-555-5555'
39 * user.get('name'); //returns "Conan The Barbarian"
41 * By default, the built in field types such as number and boolean coerce string values
42 * in the raw data by virtue of their {@link Ext.data.field.Field#method-convert} method.
43 * When the server can be relied upon to send data in a format that does not need to be
44 * converted, disabling this can improve performance. The {@link Ext.data.reader.Json Json}
45 * and {@link Ext.data.reader.Array Array} readers are likely candidates for this
46 * optimization. To disable field conversions you simply specify `null` for the field's
47 * {@link Ext.data.field.Field#cfg-convert convert config}.
49 * ## The "id" Field and `idProperty`
51 * A Model definition always has an *identifying field* which should yield a unique key
52 * for each instance. By default, a field named "id" will be created with a
53 * {@link Ext.data.Field#mapping mapping} of "id". This happens because of the default
54 * {@link #idProperty} provided in Model definitions.
56 * To alter which field is the identifying field, use the {@link #idProperty} config.
60 * Models have built-in support for field validators. Validators are added to models as in
63 * Ext.define('User', {
64 * extend: 'Ext.data.Model',
66 * { name: 'name', type: 'string' },
67 * { name: 'age', type: 'int' },
68 * { name: 'phone', type: 'string' },
69 * { name: 'gender', type: 'string' },
70 * { name: 'username', type: 'string' },
71 * { name: 'alive', type: 'boolean', defaultValue: true }
76 * name: { type: 'length', min: 2 },
77 * gender: { type: 'inclusion', list: ['Male', 'Female'] },
79 * { type: 'exclusion', list: ['Admin', 'Operator'] },
80 * { type: 'format', matcher: /([a-z]+)[0-9]{2,3}/i }
85 * The derived type of `Ext.data.field.Field` can also provide validation. If `validators`
86 * need to be duplicated on multiple fields, instead consider creating a custom field type.
90 * The results of the validators can be retrieved via the "associated" validation record:
92 * var instance = Ext.create('User', {
95 * username: 'edspencer'
98 * var validation = instance.getValidation();
100 * The returned object is an instance of `Ext.data.Validation` and has as its fields the
101 * result of the field `validators`. The validation object is "dirty" if there are one or
102 * more validation errors present.
104 * This record is also available when using data binding as a "pseudo-association" called
105 * "validation". This pseudo-association can be hidden by an explicitly declared
106 * association by the same name (for compatibility reasons), but doing so is not
109 * The `{@link Ext.Component#modelValidation}` config can be used to enable automatic
110 * binding from the "validation" of a record to the form fields that may be bound to its
115 * Models often have associations with other Models. These associations can be defined by
116 * fields (often called "foreign keys") or by other data such as a many-to-many (or "matrix").
118 * ## Foreign-Key Associations - One-to-Many
120 * The simplest way to define an association from one Model to another is to add a
121 * {@link Ext.data.field.Field#cfg-reference reference config} to the appropriate field.
123 * Ext.define('Post', {
124 * extend: 'Ext.data.Model',
127 * { name: 'user_id', reference: 'User' }
131 * Ext.define('Comment', {
132 * extend: 'Ext.data.Model',
135 * { name: 'user_id', reference: 'User' },
136 * { name: 'post_id', reference: 'Post' }
140 * Ext.define('User', {
141 * extend: 'Ext.data.Model',
148 * The placement of `reference` on the appropriate fields tells the Model which field has
149 * the foreign-key and the type of Model it identifies. That is, the value of these fields
150 * is set to value of the `idProperty` field of the target Model.
152 * ### One-to-Many Without Foreign-Keys
154 * To define an association without a foreign-key field, you will need to use either the
155 * `{@link #cfg-hasMany}` or `{@link #cfg-belongsTo}`.
157 * Ext.define('Post', {
158 * extend: 'Ext.data.Model',
163 * Ext.define('Comment', {
164 * extend: 'Ext.data.Model',
166 * belongsTo: [ 'Post', 'User' ]
169 * // User is as above
171 * These declarations have changed slightly from previous releases. In previous releases
172 * both "sides" of an association had to declare their particular roles. This is now only
173 * required if the defaults assumed for names are not satisfactory.
175 * ## Foreign-Key Associations - One-to-One
177 * A special case of one-to-many associations is the one-to-one case. This is defined as
178 * a `{@link Ext.data.field.Field#reference unique reference}`.
180 * Ext.define('Address', {
181 * extend: 'Ext.data.Model',
190 * Ext.define('User', {
191 * extend: 'Ext.data.Model',
195 * reference: 'Address',
202 * The classic use case for many-to-many is a User and Group. Users can belong to many
203 * Groups and Groups can contain many Users. This association is declared using the
204 * `{@link #cfg-manyToMany}` config like so:
207 * Ext.define('User', {
208 * extend: 'Ext.data.Model',
214 * manyToMany: 'Group'
217 * Ext.define('Group', {
218 * extend: 'Ext.data.Model',
227 * As with other associations, only one "side" needs to be declared.
229 * To manage the relationship between a `manyToMany` relationship, a {@link Ext.data.Session}
234 * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
235 * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
236 * can be set directly on the Model:
238 * Ext.define('User', {
239 * extend: 'Ext.data.Model',
240 * fields: ['id', 'name', 'email'],
248 * Here we've set up a {@link Ext.data.proxy.Rest Rest Proxy}, which knows how to load and save data to and from a
249 * RESTful backend. Let's see how this works:
251 * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
253 * user.save(); //POST /users
255 * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
256 * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
257 * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
258 * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
260 * Loading data via the Proxy is accomplished with the static `load` method:
262 * //Uses the configured RestProxy to make a GET request to /users/123
264 * success: function(user) {
265 * console.log(user.getId()); //logs 123
269 * Models can also be updated and destroyed easily:
271 * //the user Model we loaded in the last snippet:
272 * user.set('name', 'Edward Spencer');
274 * //tells the Proxy to save the Model. In this case it will perform a PUT request to /users/123 as this Model already has an id
276 * success: function() {
277 * console.log('The User was updated');
281 * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
283 * success: function() {
284 * console.log('The User was destroyed!');
288 * # HTTP Parameter names when using a {@link Ext.data.proxy.Ajax Ajax proxy}
290 * By default, the model ID is specified in an HTTP parameter named `id`. To change the
291 * name of this parameter use the Proxy's {@link Ext.data.proxy.Ajax#idParam idParam}
294 * Parameters for other commonly passed values such as
295 * {@link Ext.data.proxy.Ajax#pageParam page number} or
296 * {@link Ext.data.proxy.Ajax#startParam start row} may also be configured.
300 * It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this by
301 * creating a {@link Ext.data.Store Store}:
303 * var store = Ext.create('Ext.data.Store', {
307 * //uses the Proxy we set up on Model to load the Store data
310 * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
311 * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
312 * Ext.data.Store Store docs} for more information on Stores.
314 Ext
. define ( 'Ext.data.Model' , {
315 alternateClassName
: 'Ext.data.Record' ,
318 'Ext.data.ErrorCollection' ,
319 'Ext.data.operation.*' ,
321 'Ext.data.validator.Validator' ,
322 'Ext.data.schema.Schema' ,
323 'Ext.data.identifier.Generator' ,
324 'Ext.data.identifier.Sequential'
327 'Ext.data.Validation'
331 * @property {Boolean} isEntity
332 * The value `true` to identify this class and its subclasses.
338 * @property {Boolean} isModel
339 * The value `true` to identify this class and its subclasses.
344 // Record ids are more flexible.
349 observableType
: 'record' ,
351 constructor : function ( data
, session
) {
354 identifier
= cls
. identifier
,
355 Model
= Ext
. data
. Model
,
356 modelIdentifier
= Model
. identifier
,
357 idProperty
= me
. idField
. name
,
358 array
, id
, initializeFn
, internalId
, len
, i
, fields
;
360 // Yes, this is here on purpose. See EXTJS-16494. The second
361 // assignment seems to work around a strange JIT issue that prevents
362 // this.data being assigned in random scenarios, even though the data
363 // is passed into the constructor. The issue occurs on 4th gen iPads and
364 // lower, possibly other older iOS devices.
365 me
. data
= me
. data
= data
|| ( data
= {});
366 me
. session
= session
|| null ;
367 me
. internalId
= internalId
= modelIdentifier
. generate ();
370 var dataId
= data
[ idProperty
];
371 if ( session
&& ! session
. isSession
) {
372 Ext
. raise ( 'Bad Model constructor argument 2 - "session" is not a Session' );
376 if (( array
= data
) instanceof Array
) {
378 fields
= me
. getFields ();
379 len
= Math
. min ( fields
. length
, array
. length
);
380 for ( i
= 0 ; i
< len
; ++ i
) {
381 data
[ fields
[ i
]. name
] = array
[ i
];
385 if (!( initializeFn
= cls
. initializeFn
)) {
386 cls
. initializeFn
= initializeFn
= Model
. makeInitializeFn ( cls
);
388 if (! initializeFn
.$ nullFn
) {
389 cls
. initializeFn ( me
);
392 // Must do this after running the initializeFn due to converters on idField
393 if (!( me
. id
= id
= data
[ idProperty
]) && id
!== 0 ) {
396 Ext
. raise ( 'The model ID configured in data ("' + dataId
+ '") has been rejected by the ' + me
. fieldsMap
[ idProperty
]. type
+ ' field converter for the ' + idProperty
+ ' field' );
400 identifier
= session
. getIdentifier ( cls
);
401 id
= identifier
. generate ();
402 } else if ( modelIdentifier
=== identifier
) {
405 id
= identifier
. generate ();
408 data
[ idProperty
] = me
. id
= id
;
416 if ( me
. init
&& Ext
. isFunction ( me
. init
)) {
422 * @property {String} entityName
423 * The short name of this entity class. This name is derived from the `namespace` of
424 * the associated `schema` and this class name. By default, a class is not given a
427 * All entities in a given `schema` must have a unique `entityName`.
429 * For more details see "Relative Naming" in {@link Ext.data.schema.Schema}.
433 * @property {Boolean} editing
434 * Internal flag used to track whether or not the model instance is currently being edited.
440 * @property {Boolean} dirty
441 * True if this record has been modified.
447 * @property {Ext.data.Session} session
448 * The {@link Ext.data.Session} for this record.
454 * @property {Boolean} dropped
455 * True if this record is pending delete on the server. This is set by the `drop`
456 * method and transmitted to the server by the `save` method.
462 * @property {Boolean} erased
463 * True if this record has been erased on the server. This flag is set of the `erase`
470 * @cfg {String} [clientIdProperty]
471 * The name of the property a server will use to send back a client-generated id in a
472 * `create` or `update` `{@link Ext.data.operation.Operation operation}`.
474 * If specified, this property cannot have the same name as any other field.
478 * Ext.define('Person', {
479 * idProperty: 'id', // this is the default value (for clarity)
481 * clientIdProperty: 'clientId',
483 * identifier: 'negative', // to generate -1, -2 etc on the client
488 * var person = new Person({
489 * // no id provided, so -1 is generated
493 * The server is given this data during the `create`:
500 * The server allocates a real id and responds like so:
507 * This property is most useful when creating multiple entities in a single call to
508 * the server in a `{@link Ext.data.operation.Create create operation}`. Alternatively,
509 * the server could respond with records that correspond one-to-one to those sent in
512 * For example the client could send a `create` with this data:
514 * [ { id: -1, name: 'Clark Kent' },
515 * { id: -2, name: 'Peter Parker' },
516 * { id: -3, name: 'Bruce Banner' } ]
518 * And the server could respond in the same order:
520 * [ { id: 427 }, // updates id = -1
521 * { id: 428 }, // updates id = -2
522 * { id: 429 } ] // updates id = -3
524 * Or using `clientIdProperty` the server could respond in arbitrary order:
526 * [ { id: 427, clientId: -3 },
527 * { id: 428, clientId: -1 },
528 * { id: 429, clientId: -2 } ]
530 * **IMPORTANT:** When upgrading from previous versions be aware that this property
531 * used to perform the role of `{@link Ext.data.writer.Writer#clientIdProperty}` as
532 * well as that described above. To continue send a client-generated id as other than
533 * the `idProperty`, set `clientIdProperty` on the `writer`. A better solution, however,
534 * is most likely a properly configured `identifier` as that would work better with
537 clientIdProperty
: null ,
542 * @property {Boolean} phantom
543 * True when the record does not yet exist in a server-side database. Any record which
544 * has a real database identity set as its `idProperty` is NOT a phantom -- it's real.
549 * @cfg {String} [idProperty='id']
550 * The name of the field treated as this Model's unique id.
552 * If changing the idProperty in a subclass, the generated id field will replace the one
553 * generated by the superclass, for example;
555 * Ext.define('Super', {
556 * extend: 'Ext.data.Model',
560 * Ext.define('Sub', {
562 * idProperty: 'customId'
565 * var fields = Super.getFields();
566 * // Has 2 fields, "name" & "id"
567 * console.log(fields[0].name, fields[1].name, fields.length);
569 * fields = Sub.getFields();
570 * // Has 2 fields, "name" & "customId", "id" is replaced
571 * console.log(fields[0].name, fields[1].name, fields.length);
573 * The data values for this field must be unique or there will be id value collisions
574 * in the {@link Ext.data.Store Store}.
579 * @cfg {Object} manyToMany
580 * A config object for a {@link Ext.data.schema.ManyToMany ManyToMany} association.
581 * See the class description for {@link Ext.data.schema.ManyToMany ManyToMany} for
582 * configuration examples.
587 * @cfg {String/Object} identifier
588 * The id generator to use for this model. The `identifier` generates values for the
589 * {@link #idProperty} when no value is given. Records with client-side generated
590 * values for {@link #idProperty} are called {@link #phantom} records since they are
591 * not yet known to the server.
593 * This can be overridden at the model level to provide a custom generator for a model.
594 * The simplest form of this would be:
596 * Ext.define('MyApp.data.MyModel', {
597 * extend: 'Ext.data.Model',
598 * requires: ['Ext.data.identifier.Sequential'],
599 * identifier: 'sequential',
603 * The above would generate {@link Ext.data.identifier.Sequential sequential} id's such
606 * Another useful id generator is {@link Ext.data.identifier.Uuid}:
608 * Ext.define('MyApp.data.MyModel', {
609 * extend: 'Ext.data.Model',
610 * requires: ['Ext.data.identifier.Uuid'],
611 * identifier: 'uuid',
615 * An id generator can also be further configured:
617 * Ext.define('MyApp.data.MyModel', {
618 * extend: 'Ext.data.Model',
620 * type: 'sequential',
626 * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
628 * If multiple models share an id space, a single generator can be shared:
630 * Ext.define('MyApp.data.MyModelX', {
631 * extend: 'Ext.data.Model',
633 * type: 'sequential',
638 * Ext.define('MyApp.data.MyModelY', {
639 * extend: 'Ext.data.Model',
641 * type: 'sequential',
646 * For more complex, shared id generators, a custom generator is the best approach.
647 * See {@link Ext.data.identifier.Generator} for details on creating custom id generators.
651 // Fields config and property
652 // @cmd-auto-dependency {aliasPrefix: "data.field."}
654 * @cfg {Object[]/String[]} fields
655 * An Array of `Ext.data.field.Field` config objects, simply the field
656 * {@link Ext.data.field.Field#name name}, or a mix of config objects and strings.
657 * If just a name is given, the field type defaults to `auto`.
659 * In a {@link Ext.data.field.Field Field} config object you may pass the alias of
660 * the `Ext.data.field.*` type using the `type` config option.
662 * // two fields are set:
663 * // - an 'auto' field with a name of 'firstName'
664 * // - and an Ext.data.field.Integer field with a name of 'age'
665 * fields: ['firstName', {
670 * Fields will automatically be created at read time for any for any keys in the
671 * data passed to the Model's {@link #proxy proxy's}
672 * {@link Ext.data.reader.Reader reader} whose name is not explicitly configured in
673 * the `fields` config.
675 * Extending a Model class will inherit all the `fields` from the superclass /
679 * @property {Ext.data.field.Field[]} fields
680 * An array fields defined for this Model (including fields defined in superclasses)
681 * in ordinal order; that is in declaration order.
687 * @property {Object} fieldOrdinals
688 * This property is indexed by field name and contains the ordinal of that field. The
689 * ordinal often has meaning to servers and is derived based on the position in the
692 * This can be used like so:
694 * Ext.define('MyApp.models.User', {
695 * extend: 'Ext.data.Model',
702 * var nameOrdinal = MyApp.models.User.fieldOrdinals.name;
704 * // or, if you have an instance:
706 * var user = new MyApp.models.User();
707 * var nameOrdinal = user.fieldOrdinals.name;
714 * @property {Object} modified
715 * A hash of field values which holds the initial values of fields before a set of edits
716 * are {@link #commit committed}.
720 * @property {Object} previousValues
721 * This object is similar to the `modified` object except it holds the data values as
722 * they were prior to the most recent change.
726 previousValues
: undefined , // Not "null" so getPrevious returns undefined first time
728 // @cmd-auto-dependency { aliasPrefix : "proxy.", defaultPropertyName : "defaultProxyType"}
730 * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
731 * The {@link Ext.data.proxy.Proxy proxy} to use for this class.
736 * @cfg {String/Object} [schema='default']
737 * The name of the {@link Ext.data.schema.Schema schema} to which this entity and its
738 * associations belong. For details on custom schemas see `Ext.data.schema.Schema`.
741 * @property {Ext.data.schema.Schema} schema
742 * The `Ext.data.schema.Schema` to which this entity and its associations belong.
748 * @cfg {String} [versionProperty]
749 * If specified, this is the name of the property that contains the entity "version".
750 * The version property is used to manage a long-running transaction and allows the
751 * detection of simultaneous modification.
753 * The way a version property is used is that the client receives the version as it
754 * would any other entity property. When saving an entity, this property is always
755 * included in the request and the server uses the value in a "conditional update".
756 * If the current version of the entity on the server matches the version property
757 * sent by the client, the update is allowed. Otherwise, the update fails.
759 * On successful update, both the client and server increment the version. This is
760 * done on the server in the conditional update and on the client when it receives a
761 * success on its update request.
763 versionProperty
: null ,
766 * @property {Number} generation
767 * This property is incremented on each modification of a record.
774 * @cfg {Object[]} validators
775 * An array of {@link Ext.data.validator.Validator validators} for this model.
779 * @cfg {String} [validationSeparator=null]
780 * If specified this property is used to concatenate multiple errors for each field
781 * as reported by the `validators`.
783 validationSeparator
: null ,
786 * @cfg {Boolean} [convertOnSet=true]
787 * Set to `false` to prevent any converters from being called on fields specified in
788 * a {@link Ext.data.Model#set set} operation.
790 * **Note:** Setting the config to `false` will only prevent the convert / calculate
791 * call when the set `fieldName` param matches the field's `{@link #name}`. In the
792 * following example the calls to set `salary` will not execute the convert method
793 * on `set` while the calls to set `vested` will execute the convert method on the
794 * initial read as well as on `set`.
796 * Example model definition:
798 * Ext.define('MyApp.model.Employee', {
799 * extend: 'Ext.data.Model',
800 * fields: ['yearsOfService', {
802 * convert: function (val) {
803 * var startingBonus = val * .1;
804 * return val + startingBonus;
808 * convert: function (val, record) {
809 * return record.get('yearsOfService') >= 4;
811 * depends: 'yearsOfService'
813 * convertOnSet: false
816 * var tina = Ext.create('MyApp.model.Employee', {
821 * console.log(tina.get('salary')); // logs 55000
822 * console.log(tina.get('vested')); // logs false
828 * console.log(tina.get('salary')); // logs 60000
829 * console.log(tina.get('vested')); // logs true
833 // Associations configs and properties
835 * @cfg {Object[]} associations
836 * An array of {@link Ext.data.schema.Association associations} for this model.
839 * @cfg {String/Object/String[]/Object[]} hasMany
840 * One or more {@link #hasMany HasMany associations} for this model.
843 * @cfg {String/Object/String[]/Object[]} belongsTo
844 * One or more {@link #belongsTo BelongsTo associations} for this model.
848 * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are
849 * relayed to the containing store. When an edit has begun, it must be followed by
850 * either `endEdit` or `cancelEdit`.
852 beginEdit : function () {
854 modified
= me
. modified
,
855 previousValues
= me
. previousValues
;
862 data
: Ext
. apply ({}, me
. data
),
863 generation
: me
. generation
,
864 modified
: modified
&& Ext
. apply ({}, modified
),
865 previousValues
: previousValues
&& Ext
. apply ({}, previousValues
)
871 * Cancels all changes made in the current edit operation.
873 cancelEdit : function () {
875 editMemento
= me
. editMemento
;
880 // reset the modified state, nothing changed since the edit began
881 Ext
. apply ( me
, editMemento
);
882 me
. editMemento
= null ;
887 * Ends an edit. If any data was modified, the containing store is notified
888 * (ie, the store's `update` event will fire).
889 * @param {Boolean} [silent] True to not notify any stores of the change.
890 * @param {String[]} [modifiedFieldNames] Array of field names changed during edit.
892 endEdit : function ( silent
, modifiedFieldNames
) {
894 editMemento
= me
. editMemento
;
898 me
. editMemento
= null ;
900 // Since these reflect changes we never notified others about, the real set
901 // of "previousValues" is what we captured in the memento:
902 me
. previousValues
= editMemento
. previousValues
;
905 if (! modifiedFieldNames
) {
906 modifiedFieldNames
= me
. getModifiedFieldNames ( editMemento
. data
);
909 if ( me
. dirty
|| ( modifiedFieldNames
&& modifiedFieldNames
. length
)) {
910 me
. callJoined ( 'afterEdit' , [ modifiedFieldNames
]);
916 getField : function ( name
) {
917 return this . self
. getField ( name
);
921 * Get the fields array for this model.
922 * @return {Ext.data.field.Field[]} The fields array
924 getFields : function () {
925 return this . self
. getFields ();
928 getFieldsMap : function () {
929 return this . fieldsMap
;
933 * Get the idProperty for this model.
934 * @return {String} The idProperty
936 getIdProperty : function () {
937 return this . idProperty
;
941 * Returns the unique ID allocated to this model instance as defined by `idProperty`.
942 * @return {Number/String} The id
949 * Return a unique observable ID. Model is not observable but tree nodes (`Ext.data.NodeInterface`) are, so
950 * they must be globally unique within the {@link #observableType}.
953 getObservableId : function () {
954 return this . internalId
;
958 * Sets the model instance's id field to the given id.
959 * @param {Number/String} id The new id.
960 * @param {Object} [options] See {@link #set}.
962 setId : function ( id
, options
) {
963 this . set ( this . idProperty
, id
, options
);
967 * This method returns the value of a field given its name prior to its most recent
969 * @param {String} fieldName The field's {@link Ext.data.field.Field#name name}.
970 * @return {Object} The value of the given field prior to its current value. `undefined`
971 * if there is no previous value;
973 getPrevious : function ( fieldName
) {
974 var previousValues
= this . previousValues
;
975 return previousValues
&& previousValues
[ fieldName
];
979 * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
980 * @param {String} fieldName The field's {@link Ext.data.field.Field#name name}.
983 isModified : function ( fieldName
) {
984 var modified
= this . modified
;
985 return !!( modified
&& modified
. hasOwnProperty ( fieldName
));
989 * Returns the original value of a modified field. If there is no modified value,
990 * `undefined` will be return. Also see {@link #isModified}.
991 * @param {String} fieldName The name of the field for which to return the original value.
992 * @return {Object} modified
994 getModified : function ( fieldName
) {
996 if ( this . isModified ( fieldName
)) {
997 out
= this . modified
[ fieldName
];
1003 * Returns the value of the given field.
1004 * @param {String} fieldName The name of the field.
1005 * @return {Object} The value of the specified field.
1007 get : function ( fieldName
) {
1008 return this . data
[ fieldName
];
1011 // This object is used whenever the set() method is called and given a string as the
1012 // first argument. This approach saves memory (and GC costs) since we could be called
1022 * Sets the given field to the given value. For example:
1024 * record.set('name', 'value');
1026 * This method can also be passed an object containing multiple values to set at once.
1034 * The following store events are fired when the modified record belongs to a store:
1036 * - {@link Ext.data.Store#event-beginupdate beginupdate}
1037 * - {@link Ext.data.Store#event-update update}
1038 * - {@link Ext.data.Store#event-endupdate endupdate}
1040 * @param {String/Object} fieldName The field to set, or an object containing key/value
1042 * @param {Object} newValue The value for the field (if `fieldName` is a string).
1043 * @param {Object} [options] Options for governing this update.
1044 * @param {Boolean} [options.convert=true] Set to `false` to prevent any converters from
1045 * being called during the set operation. This may be useful when setting a large bunch of
1047 * @param {Boolean} [options.dirty=true] Pass `false` if the field values are to be
1048 * understood as non-dirty (fresh from the server). When `true`, this change will be
1049 * reflected in the `modified` collection.
1050 * @param {Boolean} [options.commit=false] Pass `true` to call the {@link #commit} method
1051 * after setting fields. If this option is passed, the usual after change processing will
1052 * be bypassed. {@link #commit Commit} will be called even if there are no field changes.
1053 * @param {Boolean} [options.silent=false] Pass `true` to suppress notification of any
1054 * changes made by this call. Use with caution.
1055 * @return {String[]} The array of modified field names or null if nothing was modified.
1057 set : function ( fieldName
, newValue
, options
) {
1061 modified
= me
. modified
,
1062 prevVals
= me
. previousValues
,
1063 session
= me
. session
,
1064 single
= Ext
. isString ( fieldName
),
1065 opt
= ( single
? options
: newValue
),
1066 convertOnSet
= opt
? opt
. convert
!== false : me
. convertOnSet
,
1067 fieldsMap
= me
. fieldsMap
,
1068 silent
= opt
&& opt
. silent
,
1069 commit
= opt
&& opt
. commit
,
1070 updateRefs
= !( opt
&& opt
. refs
=== false ) && session
,
1071 // Don't need to do dirty processing with commit, since we'll always
1072 // end up with nothing modified and not dirty
1073 dirty
= !( opt
&& opt
. dirty
=== false && ! commit
),
1074 modifiedFieldNames
= null ,
1075 currentValue
, field
, idChanged
, key
, name
, oldId
, comparator
, dep
, dependents
,
1076 i
, dirtyRank
= 0 , numFields
, newId
, rankedFields
, reference
, value
, values
;
1079 values
= me
. _singleProp
;
1080 values
[ fieldName
] = newValue
;
1085 if (!( rankedFields
= cls
. rankedFields
)) {
1086 // On the first edit of a record of this type we need to ensure we have the
1088 rankedFields
= cls
. rankFields ();
1090 numFields
= rankedFields
. length
;
1093 for ( name
in values
) {
1094 value
= values
[ name
];
1095 currentValue
= data
[ name
];
1097 field
= fieldsMap
[ name
];
1100 if ( convertOnSet
&& field
. convert
) {
1101 value
= field
. convert ( value
, me
);
1104 reference
= field
. reference
;
1109 if ( comparator
. isEqual ( currentValue
, value
)) {
1110 continue ; // new value is the same, so no change...
1114 ( modifiedFieldNames
|| ( modifiedFieldNames
= [])). push ( name
);
1115 ( prevVals
|| ( me
. previousValues
= prevVals
= {}))[ name
] = currentValue
;
1117 // We need the cls to be present because it means the association class is loaded,
1118 // otherwise it could be pending.
1119 if ( reference
&& reference
. cls
) {
1121 session
. updateReference ( me
, field
, value
, currentValue
);
1123 reference
. onValueChange ( me
, session
, value
, currentValue
);
1126 i
= ( dependents
= field
&& field
. dependents
) && dependents
. length
;
1128 // we use the field instance to hold the dirty bit to avoid any
1129 // extra allocations... we'll clear this before we depart. We do
1130 // this so we can perform the fewest recalculations possible as
1131 // each dependent field only needs to be recalculated once.
1132 ( dep
= dependents
[ i
]). dirty
= true ;
1133 dirtyRank
= dirtyRank
? Math
. min ( dirtyRank
, dep
. rank
) : dep
. rank
;
1136 if (! field
|| field
. persist
) {
1137 if ( modified
&& modified
. hasOwnProperty ( name
)) {
1138 if (! dirty
|| comparator
. isEqual ( modified
[ name
], value
)) {
1139 // The original value in me.modified equals the new value, so
1140 // the field is no longer modified:
1141 delete modified
[ name
];
1142 me
. dirty
= - 1 ; // fix me.dirty later (still truthy)
1146 me
. modified
= modified
= {}; // create only when needed
1149 modified
[ name
] = currentValue
;
1153 if ( name
=== me
. idField
. name
) {
1155 oldId
= currentValue
;
1161 // Unless there are dependent fields to process we can break now. This is
1162 // what will happen for all code pre-dating the depends or simply not
1163 // using it, so it will add very little overhead when not used.
1167 // dirtyRank has the minimum rank (a 1-based value) of any dependent field
1168 // that needs recalculating due to changes above. The way we go about this
1169 // is to use our helper object for processing single argument invocations
1170 // to process just this one field. This is because the act of setting it
1171 // may cause another field to be invalidated, so while we cannot know at
1172 // this moment all the fields we need to recalculate, we know that only
1173 // those following this field in rankedFields can possibly be among them.
1175 field
= rankedFields
[ dirtyRank
- 1 ]; // dirtyRank is 1-based
1176 field
. dirty
= false ; // clear just this field's dirty state
1179 delete values
[ fieldName
]; // cleanup last value
1181 values
= me
. _singleProp
; // switch over
1185 fieldName
= field
. name
;
1186 values
[ fieldName
] = data
[ fieldName
];
1187 // We are now processing a dependent field, so we want to force a
1188 // convert to occur because it's the only way it will get a value
1189 convertOnSet
= true ;
1191 // Since dirtyRank is 1-based and refers to the field we need to handle
1192 // on this pass, we can treat it like an index for a minute and look at
1193 // the next field on towards the end to find the index of the next dirty
1195 for ( ; dirtyRank
< numFields
; ++ dirtyRank
) {
1196 if ( rankedFields
[ dirtyRank
]. dirty
) {
1201 if ( dirtyRank
< numFields
) {
1202 // We found a field after this one marked as dirty so make the index
1203 // a proper 1-based rank:
1206 // We did not find any more dirty fields after this one, so clear the
1207 // dirtyRank and we will perhaps fall out after the next update
1213 // We might have removed the last modified field, so check to see if there
1214 // are any modified fields remaining and correct me.dirty:
1216 for ( key
in modified
) {
1217 if ( modified
. hasOwnProperty ( key
)) {
1225 // cleanup our reused object for next time... important to do this before
1226 // we fire any events or call anyone else (like afterEdit)!
1227 delete values
[ fieldName
];
1234 me
. callJoined ( 'onIdChanged' , [ oldId
, newId
]);
1238 me
. commit ( silent
, modifiedFieldNames
);
1239 } else if (! silent
&& ! me
. editing
&& modifiedFieldNames
) {
1240 me
. callJoined ( 'afterEdit' , [ modifiedFieldNames
]);
1243 return modifiedFieldNames
;
1247 * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
1248 * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
1249 * reverted to their original values.
1251 * Developers should subscribe to the {@link Ext.data.Store#event-update} event to have their code notified of reject
1254 * @param {Boolean} [silent=false] `true` to skip notification of the owning store of the change.
1256 reject : function ( silent
) {
1258 modified
= me
. modified
;
1262 Ext
. raise ( 'Cannot reject once a record has been erased.' );
1267 me
. set ( modified
, me
. _rejectOptions
);
1274 me
. callJoined ( 'afterReject' );
1279 * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
1280 * instance since either creation or the last commit operation.
1282 * Developers should subscribe to the {@link Ext.data.Store#event-update} event to have their code notified of commit
1285 * @param {Boolean} [silent=false] Pass `true` to skip notification of the owning store of the change.
1286 * @param {String[]} [modifiedFieldNames] Array of field names changed during sync with server if known.
1287 * Omit or pass `null` if unknown. An empty array means that it is known that no fields were modified
1288 * by the server's response.
1289 * Defaults to false.
1291 commit : function ( silent
, modifiedFieldNames
) {
1293 versionProperty
= me
. versionProperty
,
1298 if ( versionProperty
&& ! me
. phantom
&& ! isNaN ( data
[ versionProperty
])) {
1299 ++ data
[ versionProperty
];
1304 me
. erased
= erased
= true ;
1309 me
. callJoined ( 'afterErase' );
1311 me
. callJoined ( 'afterCommit' , [ modifiedFieldNames
]);
1316 clearState : function () {
1319 me
. dirty
= me
. editing
= false ;
1320 me
. editMemento
= me
. modified
= null ;
1324 * Marks this record as `dropped` and waiting to be deleted on the server. When a
1325 * record is dropped, it is automatically removed from all association stores and
1326 * any child records associated to this record are also dropped (a "cascade delete")
1327 * depending on the `cascade` parameter.
1329 * @param {Boolean} [cascade=true] Pass `false` to disable the cascade to drop child
1333 drop : function ( cascade
) {
1335 associations
= me
. associations
,
1336 session
= me
. session
,
1339 if ( me
. erased
|| me
. dropped
) {
1344 if ( associations
&& cascade
!== false ) {
1345 for ( roleName
in associations
) {
1346 associations
[ roleName
]. onDrop ( me
, session
);
1349 me
. callJoined ( 'afterDrop' );
1356 * Tells this model instance that an observer is looking at it.
1357 * @param {Ext.data.Store} item The store to which this model has been added.
1359 join : function ( item
) {
1363 // Optimize this, gets called a lot
1365 joined
= me
. joined
= [ item
];
1366 } else if (! joined
. length
) {
1369 // TODO: do we need joined here? Perhaps push will do.
1370 Ext
. Array
. include ( joined
, item
);
1373 if ( item
. isStore
&& ! me
. store
) {
1375 * @property {Ext.data.Store} store
1376 * The {@link Ext.data.Store Store} to which this instance belongs.
1378 * **Note:** If this instance is bound to multiple stores, this property
1379 * will reference only the first.
1386 * Tells this model instance that it has been removed from the store.
1387 * @param {Ext.data.Store} store The store from which this model has been removed.
1389 unjoin : function ( item
) {
1393 // TreeModels are never joined to their TreeStore.
1394 // But unjoin is called by the base class's onCollectionRemove, so joined may be undefined.
1395 len
= joined
&& joined
. length
,
1399 if ( len
=== 1 && joined
[ 0 ] === item
) {
1402 Ext
. Array
. remove ( joined
, item
);
1405 if ( store
=== item
) {
1408 for ( i
= 0 , len
= joined
. length
; i
< len
; ++ i
) {
1421 * Creates a clone of this record. States like `dropped`, `phantom` and `dirty` are
1422 * all preserved in the cloned record.
1424 * @param {Ext.data.Session} [session] The session to which the new record
1426 * @return {Ext.data.Model} The cloned record.
1428 clone : function ( session
) {
1430 modified
= me
. modified
,
1431 ret
= me
. copy ( me
. id
, session
);
1434 // Restore the modified fields state
1435 ret
. modified
= Ext
. apply ({}, modified
);
1438 ret
. dirty
= me
. dirty
;
1439 ret
. dropped
= me
. dropped
;
1440 ret
. phantom
= me
. phantom
;
1446 * Creates a clean copy of this record. The returned record will not consider any its
1447 * fields as modified.
1449 * To generate a phantom instance with a new id pass `null`:
1451 * var rec = record.copy(null); // clone the record but no id (one is generated)
1453 * @param {String} [newId] A new id, defaults to the id of the instance being copied.
1454 * See `{@link Ext.data.Model#idProperty idProperty}`.
1455 * @param {Ext.data.Session} [session] The session to which the new record
1458 * @return {Ext.data.Model}
1460 copy : function ( newId
, session
) {
1462 data
= Ext
. apply ({}, me
. data
),
1463 idProperty
= me
. idProperty
,
1466 if ( newId
|| newId
=== 0 ) {
1467 data
[ idProperty
] = newId
;
1468 } else if ( newId
=== null ) {
1469 delete data
[ idProperty
];
1472 return new T ( data
, session
);
1476 * Returns the configured Proxy for this Model.
1477 * @return {Ext.data.proxy.Proxy} The proxy
1479 getProxy : function () {
1480 return this . self
. getProxy ();
1484 * Returns the `Ext.data.Validation` record holding the results of this record's
1485 * `validators`. This record is lazily created on first request and is then kept on
1486 * this record to be updated later.
1488 * See the class description for more about `validators`.
1490 * @param {Boolean} [refresh] Pass `false` to not call the `refresh` method on the
1491 * validation instance prior to returning it. Pass `true` to force a `refresh` of the
1492 * validation instance. By default the returned record is only refreshed if changes
1493 * have been made to this record.
1494 * @return {Ext.data.Validation} The `Validation` record for this record.
1497 getValidation : function ( refresh
) {
1499 ret
= me
. validation
;
1502 me
. validation
= ret
= new Ext
. data
. Validation ();
1506 if ( refresh
=== true || ( refresh
!== false && ret
. syncGeneration
!== me
. generation
)) {
1507 ret
. refresh ( refresh
);
1514 * Validates the current data against all of its configured {@link #validators}. The
1515 * returned collection holds an object for each reported problem from a `validator`.
1517 * @return {Ext.data.ErrorCollection} The errors collection.
1518 * @deprecated 5.0 Use `getValidation` instead.
1520 validate : function () {
1521 return new Ext
. data
. ErrorCollection (). init ( this );
1525 * Checks if the model is valid. See {@link #getValidation}.
1526 * @return {Boolean} True if the model is valid.
1528 isValid : function () {
1529 return this . getValidation (). isValid ();
1533 * Returns a url-suitable string for this model instance. By default this just returns the name of the Model class
1534 * followed by the instance ID - for example an instance of MyApp.model.User with ID 123 will return 'user/123'.
1535 * @return {String} The url string for this model instance.
1538 var pieces
= this .$ className
. split ( '.' ),
1539 name
= pieces
[ pieces
. length
- 1 ]. toLowerCase ();
1541 return name
+ '/' + this . getId ();
1545 * @localdoc Destroys the model using the configured proxy. The erase action is
1546 * asynchronous. Any processing of the erased record should be done in a callback.
1548 * Ext.define('MyApp.model.User', {
1549 * extend: 'Ext.data.Model',
1551 * {name: 'id', type: 'int'},
1552 * {name: 'name', type: 'string'}
1560 * var user = new MyApp.model.User({
1564 * // pass the phantom record data to the server to be saved
1566 * success: function(record, operation) {
1567 * // do something if the save succeeded
1568 * // erase the created record
1570 * failure: function(record, operation) {
1571 * // do something if the erase failed
1573 * success: function(record, operation) {
1574 * // do something if the erase succeeded
1576 * callback: function(record, operation, success) {
1577 * // do something if the erase succeeded or failed
1583 * **NOTE:** If a {@link #phantom} record is erased it will not be processed via the
1584 * proxy. However, any passed `success` or `callback` functions will be called.
1586 * The options param is an {@link Ext.data.operation.Destroy} config object
1587 * containing success, failure and callback functions, plus optional scope.
1589 * @inheritdoc #method-load
1590 * @return {Ext.data.operation.Destroy} The destroy operation
1592 erase : function ( options
) {
1597 // Drop causes a removal from the backing Collection.
1598 // The store's onCollectionRemove will respond to this by adding the record to its "to remove" stack and setting its needsSync
1599 // flag unless the above "erasing" flag is set.
1603 return me
. save ( options
);
1606 setErased : function () {
1608 this . callJoined ( 'afterErase' );
1612 * Gets an object of only the fields that have been modified since this record was
1613 * created or committed. Only persistent fields are tracked in the `modified` set so
1614 * this method will only return changes to persistent fields.
1616 * For more control over the returned data, see `{@link #getData}`.
1619 getChanges : function () {
1620 return this . getData ( this . _getChangesOptions
);
1624 * Returns the array of fields that are declared as critical (must always send).
1625 * @return {Ext.data.field.Field[]}
1627 getCriticalFields : function () {
1628 var cls
= this . self
,
1629 ret
= cls
. criticalFields
;
1633 ret
= cls
. criticalFields
;
1640 * This method is called by the {@link Ext.data.reader.Reader} after loading a model from
1641 * the server. This is after processing any inline associations that are available.
1650 * Gets all of the data from this Models *loaded* associations. It does this
1651 * recursively. For example if we have a User which hasMany Orders, and each Order
1652 * hasMany OrderItems, it will return an object like this:
1658 * status: 'shipped',
1666 * @param {Object} [result] The object on to which the associations will be added. If
1667 * no object is passed one is created. This object is then returned.
1668 * @param {Boolean/Object} [options] An object containing options describing the data
1670 * @param {Boolean} [options.associated=true] Pass `true` to include associated data from
1671 * other associated records.
1672 * @param {Boolean} [options.changes=false] Pass `true` to only include fields that
1673 * have been modified. Note that field modifications are only tracked for fields that
1674 * are not declared with `persist` set to `false`. In other words, only persistent
1675 * fields have changes tracked so passing `true` for this means `options.persist` is
1677 * @param {Boolean} [options.critical] Pass `true` to include fields set as `critical`.
1678 * This is only meaningful when `options.changes` is `true` since critical fields may
1679 * not have been modified.
1680 * @param {Boolean} [options.persist] Pass `true` to only return persistent fields.
1681 * This is implied when `options.changes` is set to `true`.
1682 * @param {Boolean} [options.serialize=false] Pass `true` to invoke the `serialize`
1683 * method on the returned fields.
1684 * @return {Object} The nested data set for the Model's loaded associations.
1686 getAssociatedData : function ( result
, options
) {
1688 associations
= me
. associations
,
1689 deep
, i
, item
, items
, itemData
, length
,
1690 record
, role
, roleName
, opts
, clear
, associated
;
1692 result
= result
|| {};
1697 options
= Ext
. Object
. chain ( options
);
1700 for ( roleName
in associations
) {
1701 role
= associations
[ roleName
];
1702 item
= role
. getAssociatedItem ( me
);
1703 if (! item
|| item
.$ gathering
) {
1708 item
.$ gathering
= 1 ;
1710 items
= item
. getData (). items
; // get the records for the store
1711 length
= items
. length
;
1714 for ( i
= 0 ; i
< length
; ++ i
) {
1715 // NOTE - we don't check whether the record is gathering here because
1716 // we cannot remove it from the store (it would invalidate the index
1717 // values and misrepresent the content). Instead we tell getData to
1718 // only get the fields vs descend further.
1720 deep
= ! record
.$ gathering
;
1721 record
.$ gathering
= 1 ;
1723 associated
= options
. associated
;
1724 if ( associated
=== undefined ) {
1725 options
. associated
= deep
;
1728 options
. associated
= false ;
1733 opts
= deep
? me
. _getAssociatedOptions
: me
. _getNotAssociatedOptions
;
1735 itemData
. push ( record
. getData ( opts
));
1737 options
. associated
= associated
;
1740 delete record
.$ gathering
;
1743 delete item
.$ gathering
;
1745 opts
= options
|| me
. _getAssociatedOptions
;
1746 if ( options
&& options
. associated
=== undefined ) {
1747 opts
. associated
= true ;
1749 itemData
= item
. getData ( opts
);
1752 result
[ roleName
] = itemData
;
1755 delete me
.$ gathering
;
1761 * Gets all values for each field in this model and returns an object containing the
1762 * current data. This can be tuned by passing an `options` object with various
1763 * properties describing the desired result. Passing `true` simply returns all fields
1764 * *and* all associated record data.
1766 * @param {Boolean/Object} [options] An object containing options describing the data
1767 * desired. If `true` is passed it is treated as an object with `associated` set to
1769 * @param {Boolean} [options.associated=false] Pass `true` to include associated data.
1770 * This is equivalent to pass `true` as the only argument. See `getAssociatedData`.
1771 * @param {Boolean} [options.changes=false] Pass `true` to only include fields that
1772 * have been modified. Note that field modifications are only tracked for fields that
1773 * are not declared with `persist` set to `false`. In other words, only persistent
1774 * fields have changes tracked so passing `true` for this means `options.persist` is
1776 * @param {Boolean} [options.critical] Pass `true` to include fields set as `critical`.
1777 * This is only meaningful when `options.changes` is `true` since critical fields may
1778 * not have been modified.
1779 * @param {Boolean} [options.persist] Pass `true` to only return persistent fields.
1780 * This is implied when `options.changes` is set to `true`.
1781 * @param {Boolean} [options.serialize=false] Pass `true` to invoke the `serialize`
1782 * method on the returned fields.
1783 * @return {Object} An object containing all the values in this model.
1785 getData : function ( options
) {
1788 opts
= ( options
=== true ) ? me
. _getAssociatedOptions
: ( options
|| ret
), //cheat
1790 associated
= opts
. associated
,
1791 changes
= opts
. changes
,
1792 critical
= changes
&& opts
. critical
,
1793 content
= changes
? me
. modified
: data
,
1794 fieldsMap
= me
. fieldsMap
,
1795 persist
= opts
. persist
,
1796 serialize
= opts
. serialize
,
1797 criticalFields
, field
, n
, name
, value
;
1799 // DON'T use "opts" from here on...
1801 // Keep in mind the two legacy use cases:
1802 // - getData() ==> Ext.apply({}, me.data)
1803 // - getData(true) ==> Ext.apply(Ext.apply({}, me.data), me.getAssociatedData())
1805 if ( content
) { // when processing only changes, me.modified could be null
1806 for ( name
in content
) {
1809 field
= fieldsMap
[ name
];
1811 if ( persist
&& ! field
. persist
) {
1814 if ( serialize
&& field
. serialize
) {
1815 value
= field
. serialize ( value
, me
);
1824 criticalFields
= me
. self
. criticalFields
|| me
. getCriticalFields ();
1825 for ( n
= criticalFields
. length
; n
-- > 0 ; ) {
1826 name
= ( field
= criticalFields
[ n
]). name
;
1828 if (!( name
in ret
)) {
1830 if ( serialize
&& field
. serialize
) {
1831 value
= field
. serialize ( value
, me
);
1839 me
. getAssociatedData ( ret
, opts
); // pass ret so new data is added to our object
1846 * Returns the array of fields that are declared as non-persist or "transient".
1847 * @return {Ext.data.field.Field[]}
1850 getTransientFields : function () {
1851 var cls
= this . self
,
1852 ret
= cls
. transientFields
;
1855 cls
. rankFields (); // populates transientFields as well as rank
1856 ret
= cls
. transientFields
;
1863 * Checks whether this model is loading data from the {@link #proxy}.
1864 * @return {Boolean} `true` if in a loading state.
1866 isLoading : function () {
1867 return !! this . loadOperation
;
1871 * Aborts a pending {@link #load} operation. If the record is not loading, this does nothing.
1874 var operation
= this . loadOperation
;
1881 * @localdoc Loads the model instance using the configured proxy. The load action
1882 * is asynchronous. Any processing of the loaded record should be done in a
1885 * Ext.define('MyApp.model.User', {
1886 * extend: 'Ext.data.Model',
1888 * {name: 'id', type: 'int'},
1889 * {name: 'name', type: 'string'}
1897 * var user = new MyApp.model.User();
1900 * failure: function(record, operation) {
1901 * // do something if the load failed
1903 * success: function(record, operation) {
1904 * // do something if the load succeeded
1906 * callback: function(record, operation, success) {
1907 * // do something whether the load succeeded or failed
1911 * The options param is an {@link Ext.data.operation.Read} config object containing
1912 * success, failure and callback functions, plus optional scope.
1914 * @param {Object} [options] Options to pass to the proxy.
1915 * @param {Function} options.success A function to be called when the
1916 * model is processed by the proxy successfully.
1917 * The callback is passed the following parameters:
1918 * @param {Ext.data.Model} options.success.record The record.
1919 * @param {Ext.data.operation.Operation} options.success.operation The operation.
1921 * @param {Function} options.failure A function to be called when the
1922 * model is unable to be processed by the server.
1923 * The callback is passed the following parameters:
1924 * @param {Ext.data.Model} options.failure.record The record.
1925 * @param {Ext.data.operation.Operation} options.failure.operation The operation.
1927 * @param {Function} options.callback A function to be called whether the proxy
1928 * transaction was successful or not.
1929 * The callback is passed the following parameters:
1930 * @param {Ext.data.Model} options.callback.record The record.
1931 * @param {Ext.data.operation.Operation} options.callback.operation The operation.
1932 * @param {Boolean} options.callback.success `true` if the operation was successful.
1934 * @param {Object} options.scope The scope in which to execute the callback
1935 * functions. Defaults to the model instance.
1937 * @return {Ext.data.operation.Read} The read operation.
1939 load : function ( options
) {
1940 options
= Ext
. apply ({}, options
);
1943 scope
= options
. scope
|| me
,
1944 proxy
= me
. getProxy (),
1945 callback
= options
. callback
,
1946 operation
= me
. loadOperation
,
1951 // Already loading, push any callbacks on and jump out
1952 extras
= operation
. extraCalls
;
1954 extras
= operation
. extraCalls
= [];
1956 extras
. push ( options
);
1961 var doIdCheck
= true ;
1969 // Always set the recordCreator. If we have a session, we're already
1970 // part of said session, so we don't need to handle that.
1971 options
. recordCreator = function ( data
, type
, readOptions
) {
1972 // Important to change this here, because we might be loading associations,
1973 // so we do not want this to propagate down. If we have a session, use that
1974 // so that we end up getting the same record. Otherwise, just remove it.
1975 var session
= me
. session
;
1977 readOptions
. recordCreator
= session
? session
. recordCreator
: null ;
1979 me
. set ( data
, me
. _commitOptions
);
1981 // Do the id check after set since converters may have run
1982 if ( doIdCheck
&& me
. getId () !== id
) {
1983 Ext
. raise ( 'Invalid record id returned for ' + id
+ '@' + me
. entityName
);
1989 options
. internalCallback = function ( operation
) {
1990 var success
= operation
. wasSuccessful () && operation
. getRecords (). length
> 0 ,
1991 op
= me
. loadOperation
,
1992 extras
= op
. extraCalls
,
1993 successFailArgs
= [ me
, operation
],
1994 callbackArgs
= [ me
, operation
, success
],
1997 me
. loadOperation
= null ;
2000 Ext
. callback ( options
. success
, scope
, successFailArgs
);
2002 Ext
. callback ( options
. failure
, scope
, successFailArgs
);
2004 Ext
. callback ( callback
, scope
, callbackArgs
);
2006 // Some code repetition here, however in a vast majority of cases
2007 // we'll only have a single callback, so optimize for that case rather
2008 // than setup arrays for all the callback options
2010 for ( i
= 0 , len
= extras
. length
; i
< len
; ++ i
) {
2011 options
= extras
[ i
];
2013 Ext
. callback ( options
. success
, scope
, successFailArgs
);
2015 Ext
. callback ( options
. failure
, scope
, successFailArgs
);
2017 Ext
. callback ( options
. callback
, scope
, callbackArgs
);
2020 me
. callJoined ( 'afterLoad' );
2022 delete options
. callback
;
2024 me
. loadOperation
= operation
= proxy
. createOperation ( 'read' , options
);
2025 operation
. execute ();
2031 * @localdoc Saves the model instance using the configured proxy. The save action
2032 * is asynchronous. Any processing of the saved record should be done in a callback.
2036 * Ext.define('MyApp.model.User', {
2037 * extend: 'Ext.data.Model',
2039 * {name: 'id', type: 'int'},
2040 * {name: 'name', type: 'string'}
2048 * var user = new MyApp.model.User({
2052 * // pass the phantom record data to the server to be saved
2054 * failure: function(record, operation) {
2055 * // do something if the save failed
2057 * success: function(record, operation) {
2058 * // do something if the save succeeded
2060 * callback: function(record, operation, success) {
2061 * // do something whether the save succeeded or failed
2065 * The response from a create operation should include the ID for the newly created
2068 * // sample response
2074 * // the id may be nested if the proxy's reader has a rootProperty config
2075 * Ext.define('MyApp.model.User', {
2076 * extend: 'Ext.data.Model',
2079 * url: 'server.url',
2082 * rootProperty: 'data'
2087 * // sample nested response
2095 * (Create + ) Update example:
2097 * Ext.define('MyApp.model.User', {
2098 * extend: 'Ext.data.Model',
2100 * {name: 'id', type: 'int'},
2101 * {name: 'name', type: 'string'}
2109 * var user = new MyApp.model.User({
2113 * success: function(record, operation) {
2114 * record.set('name', 'Bar');
2115 * // updates the remote record via the proxy
2120 * (Create + ) Destroy example - see also {@link #erase}:
2122 * Ext.define('MyApp.model.User', {
2123 * extend: 'Ext.data.Model',
2125 * {name: 'id', type: 'int'},
2126 * {name: 'name', type: 'string'}
2134 * var user = new MyApp.model.User({
2138 * success: function(record, operation) {
2140 * // destroys the remote record via the proxy
2145 * **NOTE:** If a {@link #phantom} record is {@link #drop dropped} and subsequently
2146 * saved it will not be processed via the proxy. However, any passed `success`
2147 * or `callback` functions will be called.
2149 * The options param is an Operation config object containing success, failure and
2150 * callback functions, plus optional scope. The type of Operation depends on the
2151 * state of the model being saved.
2153 * - {@link #phantom} model - {@link Ext.data.operation.Create}
2154 * - {@link #isModified modified} model - {@link Ext.data.operation.Update}
2155 * - {@link #dropped} model - {@link Ext.data.operation.Destroy}
2157 * @inheritdoc #method-load
2158 * @return {Ext.data.operation.Create/Ext.data.operation.Update/Ext.data.operation.Destroy}
2159 * The operation instance for saving this model. The type of operation returned
2160 * depends on the model state at the time of the action.
2162 * - {@link #phantom} model - {@link Ext.data.operation.Create}
2163 * - {@link #isModified modified} model - {@link Ext.data.operation.Update}
2164 * - {@link #dropped} model - {@link Ext.data.operation.Destroy}
2166 save : function ( options
) {
2167 options
= Ext
. apply ({}, options
);
2170 phantom
= me
. phantom
,
2171 dropped
= me
. dropped
,
2172 action
= dropped
? 'destroy' : ( phantom
? 'create' : 'update' ),
2173 scope
= options
. scope
|| me
,
2174 callback
= options
. callback
,
2175 proxy
= me
. getProxy (),
2178 options
. records
= [ me
];
2179 options
. internalCallback = function ( operation
) {
2180 var args
= [ me
, operation
],
2181 success
= operation
. wasSuccessful ();
2183 Ext
. callback ( options
. success
, scope
, args
);
2185 Ext
. callback ( options
. failure
, scope
, args
);
2188 Ext
. callback ( callback
, scope
, args
);
2190 delete options
. callback
;
2192 operation
= proxy
. createOperation ( action
, options
);
2194 // Not a phantom, then we must perform this operation on the remote datasource.
2195 // Record will be removed from the store in the callback upon a success response
2196 if ( dropped
&& phantom
) {
2197 // If it's a phantom, then call the callback directly with a dummy successful ResultSet
2198 operation
. setResultSet ( Ext
. data
. reader
. Reader
. prototype . nullResultSet
);
2200 operation
. setSuccessful ( true );
2202 operation
. execute ();
2207 //-------------------------------------------------------------------------
2210 inheritableStatics
: {
2212 * This method adds the given set of fields to this model class.
2214 * @param {String[]/Object[]} newFields The new fields to add. Based on the `name`
2215 * of a field this may replace a previous field definition.
2222 addFields : function ( newFields
) {
2223 this . replaceFields ( newFields
);
2227 * This method replaces the specified set of fields with a given set of new fields.
2228 * Fields should normally be considered immutable, but if the timing is right (that
2229 * is, before derived classes are declared), it is permissible to change the fields
2232 * @param {String[]/Object[]} newFields The new fields to add. Based on the `name`
2233 * of a field this may replace a previous field definition.
2234 * @param {Boolean/String[]} removeFields The names of fields to remove or `true`
2235 * to remove all existing fields. Removes are processed first followed by adds so
2236 * if a field name appears in `newFields` as well that field will effectively be
2237 * added (however, in that case there is no need to include the field in this
2245 replaceFields : function ( newFields
, removeFields
) {
2247 proto
= me
. prototype ,
2248 Field
= Ext
. data
. field
. Field
,
2250 fieldsMap
= me
. fieldsMap
,
2251 ordinals
= me
. fieldOrdinals
,
2252 field
, i
, idField
, len
, name
, ordinal
;
2254 if ( removeFields
=== true ) {
2256 me
. fieldsMap
= fieldsMap
= {};
2257 me
. fieldOrdinals
= ordinals
= {};
2258 } else if ( removeFields
) {
2259 for ( i
= removeFields
. length
; i
-- > 0 ; ) {
2260 name
= removeFields
[ i
];
2261 if ( name
in ordinals
) {
2262 delete ordinals
[ name
];
2263 delete fieldsMap
[ name
];
2267 for ( i
= 0 , len
= fields
. length
; i
< len
; ++ i
) {
2268 name
= ( field
= fields
[ i
]). name
;
2270 if ( name
in ordinals
) {
2273 // This field is being removed (it is no longer in ordinals).
2274 fields
. splice ( i
, 1 );
2277 // we need to do this forwards so that ordinals don't become
2278 // invalid due to a splice
2283 for ( i
= 0 , len
= newFields
? newFields
. length
: 0 ; i
< len
; i
++) {
2284 name
= ( field
= newFields
[ i
]). name
;
2286 if (!( name
in ordinals
)) {
2287 ordinals
[ name
] = ordinal
= fields
. length
; // 0-based
2288 fields
. push ( field
= Field
. create ( field
));
2290 fieldsMap
[ name
] = field
;
2291 field
. ordinal
= ordinal
;
2292 field
. definedBy
= field
. owner
= this ; // Ext.data.NodeInterface
2296 // The idField could have been replaced, so reacquire it.
2297 me
. idField
= proto
. idField
= idField
= fieldsMap
[ proto
. idProperty
];
2298 idField
. allowNull
= idField
. critical
= idField
. identifier
= true ;
2299 idField
. defaultValue
= null ;
2301 // In case we've created the initializer we need to zap it so we recreate it
2302 // next time. Likewise with field ranking.
2303 me
. initializeFn
= me
. rankedFields
= me
. transientFields
= me
. criticalFields
= null ;
2307 * Removes the given set of fields from this model.
2309 * @param {Boolean/String[]} removeFields The names of fields to remove or `true`
2310 * to remove all existing fields. Removes are processed first followed by adds so
2311 * if a field name appears in `newFields` as well that field will effectively be
2312 * added (however, in that case there is no need to include the field in this
2320 removeFields : function ( removeFields
) {
2321 this . replaceFields ( null , removeFields
);
2329 getIdFromData : function ( data
) {
2331 idField
= T
. idField
,
2332 id
= idField
. calculated
? ( new T ( data
)). id
: data
[ idField
. name
];
2342 createWithId : function ( id
, data
, session
) {
2346 if ( id
|| id
=== 0 ) {
2352 d
[ T
. idField
. name
] = id
;
2355 return new T ( d
, session
);
2363 getFields : function () {
2372 getFieldsMap : function () {
2373 return this . fieldsMap
;
2381 getField : function ( name
) {
2382 return this . fieldsMap
[ name
] || null ;
2386 * Returns the configured Proxy for this Model.
2387 * @return {Ext.data.proxy.Proxy} The proxy
2391 getProxy : function () {
2394 defaultProxy
= me
. defaultProxy
,
2398 // Check what was defined by the class (via onClassExtended):
2399 proxy
= me
. proxyConfig
;
2401 if (! proxy
&& defaultProxy
) {
2402 proxy
= defaultProxy
;
2405 if (! proxy
|| ! proxy
. isProxy
) {
2406 if ( typeof proxy
=== 'string' ) {
2411 // We have nothing or a config for the proxy. Get some defaults from
2412 // the Schema and smash anything we've provided over the top.
2413 defaults
= me
. schema
. constructProxy ( me
);
2414 proxy
= proxy
? Ext
. merge ( defaults
, proxy
) : defaults
;
2417 proxy
= me
. setProxy ( proxy
);
2424 * Sets the Proxy to use for this model. Accepts any options that can be accepted by
2425 * {@link Ext#createByAlias Ext.createByAlias}.
2426 * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
2427 * @return {Ext.data.proxy.Proxy}
2431 setProxy : function ( proxy
) {
2436 if (! proxy
. isProxy
) {
2437 proxy
= Ext
. Factory
. proxy ( proxy
);
2439 model
= proxy
. getModel ();
2440 if ( model
&& model
!== me
) {
2441 proxy
= proxy
. clone ();
2448 return ( me
. prototype . proxy
= me
. proxy
= proxy
);
2452 * Asynchronously loads a model instance by id. Any processing of the loaded
2453 * record should be done in a callback.
2457 * Ext.define('MyApp.User', {
2458 * extend: 'Ext.data.Model',
2460 * {name: 'id', type: 'int'},
2461 * {name: 'name', type: 'string'}
2465 * MyApp.User.load(10, {
2467 * failure: function(record, operation) {
2468 * //do something if the load failed
2470 * success: function(record, operation) {
2471 * //do something if the load succeeded
2473 * callback: function(record, operation, success) {
2474 * //do something whether the load succeeded or failed
2478 * @param {Number/String} id The ID of the model to load.
2479 * **NOTE:** The model returned must have an ID matching the param in the load
2482 * @param {Object} [options] The options param is an
2483 * {@link Ext.data.operation.Read} config object containing success, failure and
2484 * callback functions, plus optional scope.
2486 * @param {Function} options.success A function to be called when the
2487 * model is processed by the proxy successfully.
2488 * The callback is passed the following parameters:
2489 * @param {Ext.data.Model} options.success.record The record.
2490 * @param {Ext.data.operation.Operation} options.success.operation The operation.
2492 * @param {Function} options.failure A function to be called when the
2493 * model is unable to be processed by the server.
2494 * The callback is passed the following parameters:
2495 * @param {Ext.data.Model} options.failure.record The record.
2496 * @param {Ext.data.operation.Operation} options.failure.operation The operation.
2498 * @param {Function} options.callback A function to be called whether the proxy
2499 * transaction was successful or not.
2500 * The callback is passed the following parameters:
2501 * @param {Ext.data.Model} options.callback.record The record.
2502 * @param {Ext.data.operation.Operation} options.callback.operation The
2504 * @param {Boolean} options.callback.success `true` if the operation was
2507 * @param {Object} options.scope The scope in which to execute the callback
2508 * functions. Defaults to the model instance.
2510 * @param {Ext.data.Session} [session] The session for this record.
2512 * @return {Ext.data.Model} The newly created model. Note that the model will
2513 * (probably) still be loading once it is returned from this method. To do any
2514 * post-processing on the data, the appropriate place to do see is in the
2520 load : function ( id
, options
, session
) {
2524 data
[ this . prototype . idProperty
] = id
;
2525 rec
= new this ( data
, session
);
2538 eachStore : function ( callback
, scope
) {
2541 len
= stores
. length
,
2544 for ( i
= 0 ; i
< len
; ++ i
) {
2545 callback
. call ( scope
, stores
[ i
]);
2549 join : function ( item
) {
2555 joined
= me
. joined
= [ item
];
2561 me
. store
= me
. store
|| item
;
2563 stores
= me
. stores
= [];
2569 unjoin : function ( item
) {
2574 if ( joined
. length
=== 1 ) {
2577 Ext
. Array
. remove ( joined
, item
);
2581 Ext
. Array
. remove ( stores
, item
);
2582 me
. store
= stores
[ 0 ] || null ;
2587 persistenceProperty
: null
2589 inheritableStatics
: {
2597 //-------------------------------------------------------------------------
2602 _getChangesOptions
: {
2605 _getAssociatedOptions
: {
2608 _getNotAssociatedOptions
: {
2613 * Copies data from the passed record into this record. If the passed record is undefined, does nothing.
2615 * If this is a phantom record (represented only in the client, with no corresponding database entry), and
2616 * the source record is not a phantom, then this record acquires the id of the source record.
2618 * @param {Ext.data.Model} sourceRecord The record to copy data from.
2619 * @return {String[]} The names of the fields which changed value.
2622 copyFrom : function ( sourceRecord
) {
2625 fieldCount
= fields
. length
,
2626 modifiedFieldNames
= [],
2630 idProperty
= me
. idProperty
,
2636 sourceData
= sourceRecord
. data
;
2637 for (; i
< fieldCount
; i
++) {
2641 // Do not use setters.
2642 // Copy returned values in directly from the data object.
2643 // Converters have already been called because new Records
2644 // have been created to copy from.
2645 // This is a direct record-to-record value copy operation.
2646 // don't copy the id, we'll do it at the end
2647 if ( name
!== idProperty
) {
2648 value
= sourceData
[ name
];
2650 // If source property is specified, and value is different
2651 // copy field value in and build updatedFields
2652 if ( value
!== undefined && ! me
. isEqual ( myData
[ name
], value
)) {
2653 myData
[ name
] = value
;
2654 modifiedFieldNames
. push ( name
);
2659 // If this is a phantom record being updated from a concrete record, copy the ID in.
2660 if ( me
. phantom
&& ! sourceRecord
. phantom
) {
2661 // beginEdit to prevent events firing
2662 // commit at the end to prevent dirty being set
2664 me
. setId ( sourceRecord
. getId ());
2669 return modifiedFieldNames
;
2673 * Helper function used by afterEdit, afterReject and afterCommit. Calls the given
2674 * method on the `Ext.data.Store` that this instance has {@link #join joined}, if any.
2675 * The store function will always be called with the model instance as its single
2676 * argument. If this model is joined to a Ext.data.NodeStore, then this method calls
2677 * the given method on the NodeStore and the associated Ext.data.TreeStore.
2678 * @param {String} funcName The name function to call on each store.
2679 * @param {Array} [args] The arguments to pass to the method. This instance is
2680 * always inserted as the first argument.
2683 callJoined : function ( funcName
, args
) {
2686 session
= me
. session
,
2689 if (! joined
&& ! session
) {
2700 for ( i
= 0 , len
= joined
. length
; i
< len
; ++ i
) {
2702 if ( item
&& ( fn
= item
[ funcName
])) {
2703 fn
. apply ( item
, args
);
2708 fn
= session
&& session
[ funcName
];
2710 fn
. apply ( session
, args
);
2715 * Set the session for this record.
2716 * @param {Ext.data.Session} session The session
2718 setSession : function ( session
) {
2722 Ext
. raise ( 'This model already belongs to a session.' );
2725 Ext
. raise ( 'The model must have an id to participate in a session.' );
2729 this . session
= session
;
2736 * Gets the names of all the fields that were modified during an edit.
2737 * @param {Object} [old] The saved data from `beginEdit`.
2738 * @return {String[]} The array of modified field names.
2741 getModifiedFieldNames : function ( old
) {
2745 oldData
= old
|| me
. editMemento
. data
,
2749 if ( data
. hasOwnProperty ( key
)) {
2750 if (! me
. isEqual ( data
[ key
], oldData
[ key
], key
)) {
2760 * Checks if two values are equal, taking into account certain special factors, for
2762 * @param {Object} lhs The first value.
2763 * @param {Object} rhs The second value.
2764 * @return {Boolean} True if the values are equal.
2767 isEqual : function ( lhs
, rhs
, field
) {
2771 f
= field
. isField
? field
: this . fieldsMap
[ field
];
2773 return f
. isEqual ( lhs
, rhs
);
2777 // instanceof is ~10 times faster then Ext.isDate. Values here will not be
2778 // cross-document objects
2779 if ( lhs
instanceof Date
&& rhs
instanceof Date
) {
2780 return lhs
. getTime () === rhs
. getTime ();
2792 * The update operation of type 'edit'. Used by {@link Ext.data.Store#event-update Store.update} event.
2801 * The update operation of type 'reject'. Used by {@link Ext.data.Store#event-update Store.update} event.
2810 * The update operation of type 'commit'. Used by {@link Ext.data.Store#event-update Store.update} event.
2815 * @property {String/Object}
2818 * The default proxy to use for instances of this Model when no proxy is configured
2819 * on the instance. When specified, the model will use this proxy instead of
2820 * requesting one from the {@link Ext.data.Session Session}.
2822 * Can be a string "type", or a {@link Ext.data.proxy.Proxy Proxy} config object.
2824 * This proxy is not inherited by subclasses.
2826 defaultProxy
: 'memory' ,
2828 rankFields : function () {
2830 prototype = cls
. prototype ,
2831 fields
= cls
. fields
,
2832 length
= fields
. length
,
2834 criticalFields
= [],
2835 transientFields
= [],
2836 evilFields
, field
, i
;
2838 cls
. rankedFields
= prototype . rankedFields
= rankedFields
;
2839 cls
. criticalFields
= prototype . criticalFields
= criticalFields
;
2840 cls
. transientFields
= prototype . transientFields
= transientFields
;
2842 // This first pass brings over any fields that have no dependencies at all
2843 // and gathers the evil fields to the side (the fields that could depend on
2844 // anything). This avoids the call to topoAdd that we must perform on all of
2845 // the fields that do have depends (which is good since most fields will be
2847 for ( i
= 0 ; i
< length
; ++ i
) {
2849 if ( field
. critical
) {
2850 criticalFields
. push ( field
);
2852 if (! field
. persist
) {
2853 transientFields
. push ( field
);
2856 ( evilFields
|| ( evilFields
= [])). push ( field
);
2857 } else if (! field
. depends
) {
2858 rankedFields
. push ( field
);
2859 field
. rank
= rankedFields
. length
; // 1-based
2863 for ( i
= 0 ; i
< length
; ++ i
) {
2864 if (!( field
= fields
[ i
]). rank
&& ! field
. evil
) {
2870 for ( i
= 0 , length
= evilFields
. length
; i
< length
; ++ i
) {
2871 rankedFields
. push ( field
= evilFields
[ i
]);
2872 field
. rank
= rankedFields
. length
; // 1-based
2877 cls
. topoStack
= null ; // cleanup diagnostic stack
2880 return rankedFields
;
2883 topoAdd : function ( field
) {
2885 dep
= field
. depends
,
2886 dependsLength
= dep
? dep
. length
: 0 ,
2887 rankedFields
= cls
. rankedFields
,
2891 var topoStack
= cls
. topoStack
|| ( cls
. topoStack
= []);
2892 topoStack
. push ( field
. name
);
2894 if ( field
. rank
=== 0 ) { // if (adding)
2895 Ext
. raise ( cls
.$ className
+ " has circular field dependencies: " +
2896 topoStack
. join ( " --> " ));
2899 if ( topoStack
. length
&& field
. evil
) {
2900 Ext
. raise ( cls
.$ className
+ ": Field " +
2901 topoStack
[ topoStack
. length
- 1 ] +
2902 " cannot depend on depends-less field " + field
. name
);
2905 field
. rank
= 0 ; // adding (falsey but we can still detect cycles)
2908 for ( i
= 0 ; i
< dependsLength
; ++ i
) {
2909 // Get the targetField on which we depend and add this field to the
2910 // targetField.dependents[]
2911 targetField
= cls
. fieldsMap
[ dep
[ i
]];
2914 Ext
. raise ( cls
.$ className
+ ": Field " + field
. name
+ " depends on undefined field " + dep
[ i
]);
2917 ( targetField
. dependents
|| ( targetField
. dependents
= [])). push ( field
);
2919 if (! targetField
. rank
) { // if (!added)
2920 cls
. topoAdd ( targetField
);
2924 rankedFields
. push ( field
);
2925 field
. rank
= rankedFields
. length
; // 1-based (truthy to track "added" state)
2932 initFields : function ( data
, cls
, proto
) {
2933 var Field
= Ext
. data
. field
. Field
,
2934 fieldDefs
= data
. fields
,
2935 // allocate fields [] and ordinals {} for the new class:
2940 superFields
= proto
. fields
,
2941 versionProperty
= data
. versionProperty
|| proto
. versionProperty
,
2942 idProperty
= cls
. idProperty
,
2943 idField
, field
, i
, length
, name
, ordinal
,
2944 reference
, superIdField
, superIdFieldName
, idDeclared
;
2946 // Process any inherited fields to produce a fields [] and ordinals {} for
2948 cls
. fields
= proto
. fields
= fields
;
2949 cls
. fieldOrdinals
= proto
. fieldOrdinals
= fieldOrdinals
;
2950 cls
. fieldsMap
= proto
. fieldsMap
= fieldsMap
;
2951 cls
. references
= proto
. references
= references
;
2954 // We chain the super field so we can write to it
2955 for ( i
= 0 , length
= superFields
. length
; i
< length
; ++ i
) {
2956 fields
[ i
] = field
= Ext
. Object
. chain ( superFields
[ i
]);
2958 field
. dependents
= null ; // we need to recalculate these
2960 fieldOrdinals
[ name
= field
. name
] = i
;
2961 fieldsMap
[ name
] = field
;
2962 // Clear the rank because it needs to be set on the first pass through
2963 // the fields in the subclass, don't inherit it from the parent
2966 if ( field
. generated
) {
2967 superIdField
= field
;
2968 superIdFieldName
= field
. name
;
2973 // Merge in any fields from this class:
2976 for ( i
= 0 , length
= fieldDefs
. length
; i
< length
; ++ i
) {
2977 field
= fieldDefs
[ i
];
2978 reference
= field
. reference
;
2979 // Create a copy of the reference since we'll modify
2980 // the reference on the field. Needed for subclasses
2981 if ( reference
&& typeof reference
!== 'string' ) {
2982 // Can have child objects, so merge it deeply
2983 reference
= Ext
. merge ({}, reference
);
2985 field
.$ reference
= reference
;
2986 field
= Field
. create ( fieldDefs
[ i
]);
2988 ordinal
= fieldOrdinals
[ name
];
2989 if ( ordinal
=== undefined ) {
2990 // If the field is new, add it to the end of the fields[]
2991 fieldOrdinals
[ name
] = ordinal
= fields
. length
;
2993 // else, overwrite the field at the established ordinal
2995 fieldsMap
[ name
] = field
;
2996 fields
[ ordinal
] = field
;
2997 field
. definedBy
= field
. owner
= cls
;
2998 field
. ordinal
= ordinal
;
2999 if ( name
=== idProperty
) {
3005 // Lookup the idProperty in the ordinals map and create a synthetic field if
3006 // we don't have one.
3007 idField
= fieldsMap
[ idProperty
];
3009 if ( superIdField
&& superIdField
. generated
) {
3010 ordinal
= superIdField
. ordinal
;
3012 ordinal
= fields
. length
;
3014 delete fieldsMap
[ superIdFieldName
];
3015 delete fieldOrdinals
[ superIdFieldName
]
3016 idField
= new Field ( idProperty
);
3017 fields
[ ordinal
] = idField
;
3018 fieldOrdinals
[ idProperty
] = ordinal
;
3019 fieldsMap
[ idProperty
] = idField
;
3020 idField
. definedBy
= cls
;
3021 idField
. ordinal
= ordinal
;
3022 idField
. generated
= true ;
3023 } else if ( idDeclared
&& superIdField
&& superIdField
. generated
) {
3024 // If we're declaring the id as a field in our fields array and it's different to
3025 // the super id field that has been generated, pull it out and fix up the ordinals. This
3026 // likely won't happen often, to do it earlier we would need to know the contents of the fields
3027 // which would mean iterating over them twice.
3028 Ext
. Array
. remove ( fields
, superIdField
);
3029 delete fieldsMap
[ superIdFieldName
];
3030 delete fieldOrdinals
[ superIdFieldName
];
3031 fieldsMap
[ idProperty
] = idDeclared
;
3032 for ( i
= 0 , length
= fields
. length
; i
< length
; ++ i
) {
3035 fieldOrdinals
[ field
. name
] = i
;
3039 idField
. allowNull
= idField
. critical
= idField
. identifier
= true ;
3040 idField
. defaultValue
= null ;
3042 cls
. idField
= proto
. idField
= idField
;
3044 if ( versionProperty
) {
3045 field
= fieldsMap
[ versionProperty
];
3047 ordinal
= fields
. length
;
3049 name
: versionProperty
,
3052 fields
[ ordinal
] = field
;
3053 fieldOrdinals
[ versionProperty
] = ordinal
;
3054 fieldsMap
[ versionProperty
] = field
;
3055 field
. definedBy
= cls
;
3056 field
. ordinal
= ordinal
;
3057 field
. generated
= true ;
3059 field
. defaultValue
= 1 ;
3060 field
. critical
= true ;
3063 // NOTE: Be aware that the one fellow that manipulates these after this
3064 // point is Ext.data.NodeInterface.
3067 initValidators : function ( data
, cls
, proto
) {
3068 var superValidators
= proto
. validators
,
3069 validators
, field
, copy
, validatorDefs
,
3070 i
, length
, fieldValidator
, name
, validator
, item
;
3072 if ( superValidators
) {
3074 for ( field
in superValidators
) {
3075 validators
[ field
] = Ext
. Array
. clone ( superValidators
[ field
]);
3079 validatorDefs
= data
. validators
|| data
. validations
;
3081 if ( data
. validations
) {
3082 delete data
. validations
;
3083 Ext
. log
. warn (( cls
.$ className
|| 'Ext.data.Model' ) +
3084 ': validations has been deprecated. Please use validators instead.' );
3087 if ( validatorDefs
) {
3088 delete data
. validators
;
3090 validators
= validators
|| {};
3092 // Support older array syntax
3093 if ( Ext
. isArray ( validatorDefs
)) {
3095 for ( i
= 0 , length
= validatorDefs
. length
; i
< length
; ++ i
) {
3096 item
= validatorDefs
[ i
];
3101 // Check for function form
3102 item
= item
. fn
|| item
;
3103 copy
[ name
]. push ( item
);
3105 validatorDefs
= copy
;
3108 for ( name
in validatorDefs
) {
3109 fieldValidator
= validatorDefs
[ name
];
3110 if (! Ext
. isArray ( fieldValidator
)) {
3111 fieldValidator
= [ fieldValidator
];
3114 validator
= validators
[ name
];
3115 if ( validators
[ name
]) {
3116 // Declared in super
3117 Ext
. Array
. push ( validator
, fieldValidator
);
3119 validators
[ name
] = fieldValidator
;
3124 for ( name
in validators
) {
3125 field
= cls
. getField ( name
);
3127 field
. setModelValidators ( validators
[ name
]);
3131 cls
. validators
= proto
. validators
= validators
;
3134 initAssociations : function ( schema
, data
, cls
) {
3135 // The 4 legacy associations:
3136 var associations
= data
. associations
,
3137 belongsTo
= data
. belongsTo
,
3138 hasMany
= data
. hasMany
,
3139 hasOne
= data
. hasOne
,
3141 matrices
= data
. manyToMany
,
3145 if ( data
. belongsTo
) {
3146 Ext
. log
. warn ( 'Use of "belongsTo" is obsolete' +
3147 ( cls
.$ className
? ' in ' + cls
.$ className
: '' ));
3148 delete data
. belongsTo
;
3152 delete data
. manyToMany
;
3154 schema
. addMatrices ( cls
, matrices
);
3158 delete data
. associations
;
3159 delete data
. belongsTo
;
3160 delete data
. hasMany
;
3164 associations
= Ext
. isArray ( associations
) ? associations
: [ associations
];
3165 for ( i
= 0 , length
= associations
. length
; i
< length
; ++ i
) {
3166 assoc
= associations
[ i
];
3167 switch ( assoc
. type
) {
3169 schema
. addLegacyBelongsTo ( cls
, assoc
);
3172 schema
. addLegacyHasMany ( cls
, assoc
);
3175 schema
. addLegacyHasOne ( cls
, assoc
);
3180 Ext
. raise ( 'Invalid association type: "' + assoc
. type
+ '"' );
3187 belongsTo
= Ext
. isArray ( belongsTo
) ? belongsTo
: [ belongsTo
];
3188 for ( i
= 0 , length
= belongsTo
. length
; i
< length
; ++ i
) {
3189 schema
. addLegacyBelongsTo ( cls
, belongsTo
[ i
]);
3194 hasMany
= Ext
. isArray ( hasMany
) ? hasMany
: [ hasMany
];
3195 for ( i
= 0 , length
= hasMany
. length
; i
< length
; ++ i
) {
3196 schema
. addLegacyHasMany ( cls
, hasMany
[ i
]);
3201 hasOne
= Ext
. isArray ( hasOne
) ? hasOne
: [ hasOne
];
3202 for ( i
= 0 , length
= hasOne
. length
; i
< length
; ++ i
) {
3203 schema
. addLegacyHasOne ( cls
, hasOne
[ i
]);
3206 schema
. afterLegacyAssociations ( cls
);
3209 initIdentifier : function ( data
, cls
, proto
) {
3210 var identifier
= data
. identifier
|| data
. idgen
,
3211 superIdent
= proto
. identifier
|| cls
. schema
. _defaultIdentifier
,
3216 Ext
. log
. warn ( 'Ext.data.Model: idgen has been deprecated. Please use identifier instead.' );
3221 delete data
. identifier
;
3224 // An idgen was specified on the definition, use it explicitly.
3225 identifier
= Ext
. Factory
. dataIdentifier ( identifier
);
3226 } else if ( superIdent
) {
3227 // If we have a cloneable instance, and we don't have an id
3228 // clone it. If we have an id, then we should use the same
3229 // instance since it's the same as looking it up via id.
3230 if ( superIdent
. clone
&& ! superIdent
. getId ()) {
3231 identifier
= superIdent
. clone ();
3232 } else if ( superIdent
. isGenerator
) {
3233 identifier
= superIdent
;
3235 identifier
= Ext
. Factory
. dataIdentifier ( superIdent
);
3239 cls
. identifier
= proto
. identifier
= identifier
;
3242 // If we didn't find one, create it and push it onto the class.
3243 // Don't put it on the prototype, so a subclass will create
3244 // it's own generator. If we have an anonymous model, go ahead and
3245 // generate a unique prefix for it.
3246 generatorPrefix
= cls
. entityName
;
3247 if (! generatorPrefix
) {
3248 generatorPrefix
= Ext
. id ( null , 'extModel' );
3250 cls
. identifier
= Ext
. Factory
. dataIdentifier ({
3252 prefix
: generatorPrefix
+ '-'
3257 findValidator : function ( validators
, name
, cfg
) {
3258 var type
= cfg
. type
|| cfg
,
3259 field
= validators
[ name
],
3263 for ( i
= 0 , len
= field
. length
; i
< len
; ++ i
) {
3265 if ( item
. type
=== type
) {
3274 * This method produces the `initializeFn` for this class. If there are no fields
3275 * requiring {@link Ext.data.field.Field#cfg-convert conversion} and no fields requiring
3276 * a {@link Ext.data.field.Field#defaultValue default value} then this method will
3278 * @return {Function} The `initializeFn` for this class (or null).
3281 makeInitializeFn : function ( cls
) {
3282 var code
= [ 'var ' ],
3283 body
= [ ' \n return function (e) { \n var data = e.data, v; \n ' ],
3286 bc
, ec
, // == beginClone, endClone
3287 convert
, expr
, factory
, field
, fields
, fs
, hasDefValue
, i
, length
;
3289 if (!( fields
= cls
. rankedFields
)) {
3290 // On the first edit of a record of this type we need to ensure we have the
3292 fields
= cls
. rankFields ();
3295 for ( i
= 0 , length
= fields
. length
; i
< length
; ++ i
) {
3296 // The generated method declares vars for each field using "f0".."fN' as the
3297 // name. These are used to access properties of the field (e.g., the convert
3298 // method or defaultValue).
3300 fieldVars
[ i
] = fs
= 'f' + i
;
3301 convert
= field
. convert
;
3306 code
. push ( fs
, ' = $fields[' + i
+ ']' );
3308 // this can be helpful when debugging (at least in Chrome):
3309 code
. push ( ' /* ' , field
. name
, ' */' );
3312 // NOTE: added string literals are "folded" by the compiler so we
3313 // are better off doing an "'foo' + 'bar'" then "'foo', 'bar'". But
3314 // for variables we are better off pushing them into the array for
3317 if (( hasDefValue
= ( field
. defaultValue
!== undefined )) || convert
) {
3318 // For non-calculated fields that have some work required (a convert method
3319 // and/or defaultValue), generate a chunk of logic appropriate for the
3321 //expr = data["fieldName"];
3322 expr
= 'data["' + field
. name
+ '"]' ;
3326 if ( field
. cloneDefaultValue
) {
3332 if ( convert
&& hasDefValue
) {
3333 // v = data.fieldName;
3334 // if (v !== undefined) {
3335 // v = f2.convert(v, e);
3337 // if (v === undefined) {
3338 // v = f2.defaultValue;
3340 // v = Ext.clone(f2.defaultValue);
3342 // data.fieldName = v;
3344 body
. push ( ' v = ' , expr
, '; \n ' +
3345 ' if (v !== undefined) { \n ' +
3346 ' v = ' , fs
, '.convert(v, e); \n ' +
3348 ' if (v === undefined) { \n ' +
3349 ' v = ' , bc
, fs
, '.defaultValue' , ec
, '; \n ' +
3351 ' ' , expr
, ' = v;' );
3352 } else if ( convert
) { // no defaultValue
3353 // v = f2.convert(data.fieldName,e);
3354 // if (v !== undefined) {
3355 // data.fieldName = v;
3358 body
. push ( ' v = ' , fs
, '.convert(' , expr
, ',e); \n ' +
3359 ' if (v !== undefined) { \n ' +
3360 ' ' , expr
, ' = v; \n ' +
3362 } else if ( hasDefValue
) { // no convert
3363 // if (data.fieldName === undefined) {
3364 // data.fieldName = f2.defaultValue;
3366 // data.fieldName = Ext.clone(f2.defaultValue);
3369 body
. push ( ' if (' , expr
, ' === undefined) { \n ' +
3370 ' ' , expr
, ' = ' , bc
, fs
, '.defaultValue' , ec
, '; \n ' +
3377 // There are no fields that need special processing
3382 code
. push
. apply ( code
, body
);
3384 code
= code
. join ( '' );
3386 // Ensure that Ext in the function code refers to the same Ext that we are using here.
3387 // If we are in a sandbox, global.Ext might be different.
3388 factory
= new Function ( '$fields' , 'Ext' , code
);
3390 return factory ( fields
, Ext
);
3397 proto
= Model
. prototype ,
3398 Schema
= Ext
. data
. schema
. Schema
,
3401 Model
. proxyConfig
= proto
. proxy
;
3404 // Base Model class may be used. It needs an empty fields array.
3407 // Base Model class may be used. It needs an empty fieldsMap hash.
3408 Model
. fieldsMap
= proto
. fieldsMap
= {};
3410 Model
. schema
= proto
. schema
= Schema
. get ( proto
. schema
);
3411 proto
. idField
= new Ext
. data
. field
. Field ( proto
. idProperty
);
3412 Model
. identifier
= new Ext
. data
. identifier
. Sequential ();
3414 Model
. onExtended ( function ( cls
, data
) {
3415 var proto
= cls
. prototype ,
3416 schemaName
= data
. schema
,
3417 superCls
= proto
. superclass
. self
,
3418 schema
, entityName
, proxy
;
3420 cls
. idProperty
= data
. idProperty
|| proto
. idProperty
;
3424 schema
= Schema
. get ( schemaName
);
3425 } else if (!( schema
= proto
. schema
)) {
3426 schema
= defaultSchema
|| ( defaultSchema
= Schema
. get ( 'default' ));
3429 // These are in "privates" so we manually make them inherited:
3430 cls
. rankFields
= Model
. rankFields
;
3431 cls
. topoAdd
= Model
. topoAdd
;
3433 // if we picked up a schema from cls.prototype.schema, it is because it was found
3434 // in the prototype chain on a base class.
3435 proto
. schema
= cls
. schema
= schema
;
3437 // Unless specified on the declaration data, we need to provide the entityName of
3438 // the new Entity-derived class. Store it on the prototype and the class.
3439 if (!( entityName
= data
. entityName
)) {
3440 proto
. entityName
= entityName
= schema
. getEntityName ( cls
);
3443 if ( data
. associations
) {
3444 Ext
. raise ( 'Anonymous entities cannot specify "associations"' );
3446 if ( data
. belongsTo
) {
3447 Ext
. raise ( 'Anonymous entities cannot specify "belongsTo"' );
3450 Ext
. raise ( 'Anonymous entities cannot specify "hasMany"' );
3453 Ext
. raise ( 'Anonymous entities cannot specify "hasOne"' );
3455 if ( data
. matrices
) {
3456 Ext
. raise ( 'Anonymous entities cannot specify "manyToMany"' );
3461 cls
. entityName
= entityName
;
3462 cls
. fieldExtractors
= {};
3464 Model
. initIdentifier ( data
, cls
, proto
);
3465 Model
. initFields ( data
, cls
, proto
);
3466 Model
. initValidators ( data
, cls
, proto
);
3468 // This is a compat hack to allow "rec.fields.items" to work as it used to when
3469 // fields was a MixedCollection
3470 cls
. fields
. items
= cls
. fields
;
3473 schema
. addEntity ( cls
);
3474 Model
. initAssociations ( schema
, data
, cls
);
3480 } else if ( superCls
!== Model
) {
3481 proxy
= superCls
. proxyConfig
|| superCls
. proxy
;
3484 cls
. proxyConfig
= proxy
;