123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- <!DOCTYPE html>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>The source code</title>
- <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
- <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
- <style type="text/css">
- .highlight { display: block; background-color: #ddd; }
- </style>
- <script type="text/javascript">
- function highlight() {
- document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
- }
- </script>
- </head>
- <body onload="prettyPrint(); highlight();">
- <pre class="prettyprint lang-js"><span id='Ext-data-proxy-WebStorage'>/**
- </span> * @author Ed Spencer
- *
- * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
- * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
- * save {@link Ext.data.Model model instances} for offline use.
- * @private
- */
- Ext.define('Ext.data.proxy.WebStorage', {
- extend: 'Ext.data.proxy.Client',
- alternateClassName: 'Ext.data.WebStorageProxy',
- requires: [
- 'Ext.data.SequentialIdGenerator'
- ],
- <span id='Ext-data-proxy-WebStorage-cfg-id'> /**
- </span> * @cfg {String} id
- * The unique ID used as the key in which all record data are stored in the local storage object.
- */
- id: undefined,
- <span id='Ext-data-proxy-WebStorage-method-constructor'> /**
- </span> * Creates the proxy, throws an error if local storage is not supported in the current browser.
- * @param {Object} config (optional) Config object.
- */
- constructor: function(config) {
- this.callParent(arguments);
- <span id='Ext-data-proxy-WebStorage-property-cache'> /**
- </span> * @property {Object} cache
- * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
- */
- this.cache = {};
- //<debug>
- if (this.getStorageObject() === undefined) {
- Ext.Error.raise("Local Storage is not supported in this browser, please use another type of data proxy");
- }
- //</debug>
- //if an id is not given, try to use the store's id instead
- this.id = this.id || (this.store ? this.store.storeId : undefined);
- //<debug>
- if (this.id === undefined) {
- Ext.Error.raise("No unique id was provided to the local storage proxy. See Ext.data.proxy.LocalStorage documentation for details");
- }
- //</debug>
- this.initialize();
- },
- //inherit docs
- create: function(operation, callback, scope) {
- var me = this,
- records = operation.records,
- length = records.length,
- ids = me.getIds(),
- id, record, i;
- operation.setStarted();
- if(me.isHierarchical === undefined) {
- // if the storage object does not yet contain any data, this is the first point at which we can determine whether or not this proxy deals with hierarchical data.
- // it cannot be determined during initialization because the Model is not decorated with NodeInterface until it is used in a TreeStore
- me.isHierarchical = !!records[0].isNode;
- if(me.isHierarchical) {
- me.getStorageObject().setItem(me.getTreeKey(), true);
- }
- }
- for (i = 0; i < length; i++) {
- record = records[i];
- if (record.phantom) {
- record.phantom = false;
- id = me.getNextId();
- } else {
- id = record.getId();
- }
- me.setRecord(record, id);
- record.commit();
- ids.push(id);
- }
- me.setIds(ids);
- operation.setCompleted();
- operation.setSuccessful();
- if (typeof callback == 'function') {
- callback.call(scope || me, operation);
- }
- },
- //inherit docs
- read: function(operation, callback, scope) {
- //TODO: respect sorters, filters, start and limit options on the Operation
- var me = this,
- records = [],
- i = 0,
- success = true,
- Model = me.model,
- ids, length, record, data, id;
- operation.setStarted();
- if(me.isHierarchical) {
- records = me.getTreeData();
- } else {
- ids = me.getIds();
- length = ids.length;
- id = operation.id;
- //read a single record
- if (id) {
- data = me.getRecord(id);
- if (data !== null) {
- record = new Model(data, id, data);
- }
- if (record) {
- records.push(record);
- } else {
- success = false;
- }
- } else {
- for (; i < length; i++) {
- id = ids[i];
- data = me.getRecord(id);
- records.push(new Model(data, id, data));
- }
- }
- }
- if(success) {
- operation.setSuccessful();
- }
- operation.setCompleted();
- operation.resultSet = Ext.create('Ext.data.ResultSet', {
- records: records,
- total : records.length,
- loaded : true
- });
- if (typeof callback == 'function') {
- callback.call(scope || me, operation);
- }
- },
- //inherit docs
- update: function(operation, callback, scope) {
- var records = operation.records,
- length = records.length,
- ids = this.getIds(),
- record, id, i;
- operation.setStarted();
- for (i = 0; i < length; i++) {
- record = records[i];
- this.setRecord(record);
- record.commit();
- //we need to update the set of ids here because it's possible that a non-phantom record was added
- //to this proxy - in which case the record's id would never have been added via the normal 'create' call
- id = record.getId();
- if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
- ids.push(id);
- }
- }
- this.setIds(ids);
- operation.setCompleted();
- operation.setSuccessful();
- if (typeof callback == 'function') {
- callback.call(scope || this, operation);
- }
- },
- //inherit
- destroy: function(operation, callback, scope) {
- var me = this,
- records = operation.records,
- ids = me.getIds(),
- idLength = ids.length,
- newIds = [],
- removedHash = {},
- i = records.length,
- id;
- operation.setStarted();
- for (; i--;) {
- Ext.apply(removedHash, me.removeRecord(records[i]));
- }
- for(i = 0; i < idLength; i++) {
- id = ids[i];
- if(!removedHash[id]) {
- newIds.push(id);
- }
- }
- me.setIds(newIds);
- operation.setCompleted();
- operation.setSuccessful();
- if (typeof callback == 'function') {
- callback.call(scope || me, operation);
- }
- },
- <span id='Ext-data-proxy-WebStorage-method-getRecord'> /**
- </span> * @private
- * Fetches record data from the Proxy by ID.
- * @param {String} id The record's unique ID
- * @return {Object} The record data
- */
- getRecord: function(id) {
- var me = this,
- cache = me.cache,
- data = !cache[id] ? Ext.decode(me.getStorageObject().getItem(me.getRecordKey(id))) : cache[id];
- if(!data) {
- return null;
- }
- cache[id] = data;
- data[me.model.prototype.idProperty] = id;
- return data;
- },
- <span id='Ext-data-proxy-WebStorage-method-setRecord'> /**
- </span> * Saves the given record in the Proxy.
- * @param {Ext.data.Model} record The model instance
- * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
- */
- setRecord: function(record, id) {
- if (id) {
- record.setId(id);
- } else {
- id = record.getId();
- }
- var me = this,
- rawData = record.data,
- data = {},
- model = me.model,
- fields = model.prototype.fields.items,
- length = fields.length,
- i = 0,
- field, name, obj, key;
- for (; i < length; i++) {
- field = fields[i];
- name = field.name;
- if(field.persist) {
- data[name] = rawData[name];
- }
- }
- // no need to store the id in the data, since it is already stored in the record key
- delete data[me.model.prototype.idProperty];
- // if the record is a tree node and it's a direct child of the root node, do not store the parentId
- if(record.isNode && record.get('depth') === 1) {
- delete data.parentId;
- }
- obj = me.getStorageObject();
- key = me.getRecordKey(id);
- //keep the cache up to date
- me.cache[id] = data;
- //iPad bug requires that we remove the item before setting it
- obj.removeItem(key);
- obj.setItem(key, Ext.encode(data));
- },
- <span id='Ext-data-proxy-WebStorage-method-removeRecord'> /**
- </span> * @private
- * Physically removes a given record from the local storage and recursively removes children if the record is a tree node. Used internally by {@link #destroy}.
- * @param {Ext.data.Model} record The record to remove
- * @return {Object} a hash with the ids of the records that were removed as keys and the records that were removed as values
- */
- removeRecord: function(record) {
- var me = this,
- id = record.getId(),
- records = {},
- i, childNodes;
- records[id] = record;
- me.getStorageObject().removeItem(me.getRecordKey(id));
- delete me.cache[id];
- if(record.childNodes) {
- childNodes = record.childNodes;
- for(i = childNodes.length; i--;) {
- Ext.apply(records, me.removeRecord(childNodes[i]));
- }
- }
- return records;
- },
- <span id='Ext-data-proxy-WebStorage-method-getRecordKey'> /**
- </span> * @private
- * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
- * storing data in the local storage object and should prevent naming collisions.
- * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
- * @return {String} The unique key for this record
- */
- getRecordKey: function(id) {
- if (id.isModel) {
- id = id.getId();
- }
- return Ext.String.format("{0}-{1}", this.id, id);
- },
- <span id='Ext-data-proxy-WebStorage-method-getRecordCounterKey'> /**
- </span> * @private
- * Returns the unique key used to store the current record counter for this proxy. This is used internally when
- * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
- * @return {String} The counter key
- */
- getRecordCounterKey: function() {
- return Ext.String.format("{0}-counter", this.id);
- },
- <span id='Ext-data-proxy-WebStorage-method-getTreeKey'> /**
- </span> * @private
- * Returns the unique key used to store the tree indicator. This is used internally to determine if the stored data is hierarchical
- * @return {String} The counter key
- */
- getTreeKey: function() {
- return Ext.String.format("{0}-tree", this.id);
- },
- <span id='Ext-data-proxy-WebStorage-method-getIds'> /**
- </span> * @private
- * Returns the array of record IDs stored in this Proxy
- * @return {Number[]} The record IDs. Each is cast as a Number
- */
- getIds: function() {
- var me = this,
- ids = (me.getStorageObject().getItem(me.id) || "").split(","),
- model = me.model,
- length = ids.length,
- isString = model.prototype.fields.get(model.prototype.idProperty).type.type === 'string',
- i;
- if (length == 1 && ids[0] === "") {
- ids = [];
- } else {
- for (i = 0; i < length; i++) {
- ids[i] = isString ? ids[i] : +ids[i];
- }
- }
- return ids;
- },
- <span id='Ext-data-proxy-WebStorage-method-setIds'> /**
- </span> * @private
- * Saves the array of ids representing the set of all records in the Proxy
- * @param {Number[]} ids The ids to set
- */
- setIds: function(ids) {
- var obj = this.getStorageObject(),
- str = ids.join(",");
- obj.removeItem(this.id);
- if (!Ext.isEmpty(str)) {
- obj.setItem(this.id, str);
- }
- },
- <span id='Ext-data-proxy-WebStorage-method-getNextId'> /**
- </span> * @private
- * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
- * Increments the counter.
- * @return {Number} The id
- */
- getNextId: function() {
- var me = this,
- obj = me.getStorageObject(),
- key = me.getRecordCounterKey(),
- model = me.model,
- isString = model.prototype.fields.get(model.prototype.idProperty).type.type === 'string',
- id;
- id = me.idGenerator.generate();
- obj.setItem(key, id);
- if(!isString) {
- id = +id;
- }
- return id;
- },
- <span id='Ext-data-proxy-WebStorage-method-getTreeData'> /**
- </span> * Gets tree data and transforms it from key value pairs into a hierarchical structure.
- * @private
- * @return {Ext.data.NodeInterface[]}
- */
- getTreeData: function() {
- var me = this,
- ids = me.getIds(),
- length = ids.length,
- records = [],
- recordHash = {},
- root = [],
- i = 0,
- Model = me.model,
- idProperty = Model.prototype.idProperty,
- rootLength, record, parent, parentId, children, id;
- for(; i < length; i++) {
- id = ids[i];
- // get the record for each id
- record = me.getRecord(id);
- // push the record into the records array
- records.push(record);
- // add the record to the record hash so it can be easily retrieved by id later
- recordHash[id] = record;
- if(!record.parentId) {
- // push records that are at the root level (those with no parent id) into the "root" array
- root.push(record);
- }
- }
- rootLength = root.length;
- // sort the records by parent id for greater efficiency, so that each parent record only has to be found once for all of its children
- Ext.Array.sort(records, me.sortByParentId);
- // append each record to its parent, starting after the root node(s), since root nodes do not need to be attached to a parent
- for(i = rootLength; i < length; i++) {
- record = records[i];
- parentId = record.parentId;
- if(!parent || parent[idProperty] !== parentId) {
- // if this record has a different parent id from the previous record, we need to look up the parent by id.
- parent = recordHash[parentId];
- parent.children = children = [];
- }
- // push the record onto its parent's children array
- children.push(record);
- }
- for(i = length; i--;) {
- record = records[i];
- if(!record.children && !record.leaf) {
- // set non-leaf nodes with no children to loaded so the proxy won't try to dynamically load their contents when they are expanded
- record.loaded = true;
- }
- }
- // Create model instances out of all the "root-level" nodes.
- for(i = rootLength; i--;) {
- record = root[i];
- root[i] = new Model(record, record[idProperty], record);
- }
- return root;
- },
- <span id='Ext-data-proxy-WebStorage-method-sortByParentId'> /**
- </span> * Sorter function for sorting records by parentId
- * @private
- * @param {Object} node1
- * @param {Object} node2
- * @return {Number}
- */
- sortByParentId: function(node1, node2) {
- return (node1.parentId || 0) - (node2.parentId || 0);
- },
- <span id='Ext-data-proxy-WebStorage-method-initialize'> /**
- </span> * @private
- * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
- * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
- */
- initialize: function() {
- var me = this,
- storageObject = me.getStorageObject(),
- lastId = +storageObject.getItem(me.getRecordCounterKey());
- storageObject.setItem(me.id, storageObject.getItem(me.id) || "");
- if(storageObject.getItem(me.getTreeKey())) {
- me.isHierarchical = true;
- }
- me.idGenerator = new Ext.data.SequentialIdGenerator({
- seed: lastId ? lastId + 1 : 1
- });
- },
- <span id='Ext-data-proxy-WebStorage-method-clear'> /**
- </span> * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
- * storage object.
- */
- clear: function() {
- var me = this,
- obj = me.getStorageObject(),
- ids = me.getIds(),
- len = ids.length,
- i;
- //remove all the records
- for (i = 0; i < len; i++) {
- obj.removeItem(me.getRecordKey(ids[i]));
- }
- //remove the supporting objects
- obj.removeItem(me.getRecordCounterKey());
- obj.removeItem(me.getTreeKey());
- obj.removeItem(me.id);
- // clear the cache
- me.cache = {};
- },
- <span id='Ext-data-proxy-WebStorage-method-getStorageObject'> /**
- </span> * @private
- * Abstract function which should return the storage object that data will be saved to. This must be implemented
- * in each subclass.
- * @return {Object} The storage object
- */
- getStorageObject: function() {
- //<debug>
- Ext.Error.raise("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
- //</debug>
- }
- });</pre>
- </body>
- </html>
|