]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Protocol/SQL/Gears.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Protocol / SQL / Gears.js
1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2  * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3  * full text of the license. */
4
5 /**
6  * @requires Gears/gears_init.js
7  * @requires OpenLayers/Protocol/SQL.js
8  * @requires OpenLayers/Format/JSON.js
9  * @requires OpenLayers/Format/WKT.js
10  */
11
12 /**
13  * Class: OpenLayers.Protocol.SQL.Gears
14  * This Protocol stores feature in the browser via the Gears Database module 
15  * <http://code.google.com/apis/gears/api_database.html>.
16  *
17  * The main advantage is that all the read, create, update and delete operations 
18  * can be done offline.
19  *
20  * Inherits from:
21  *  - <OpenLayers.Protocol.SQL>
22  */
23 OpenLayers.Protocol.SQL.Gears = OpenLayers.Class(OpenLayers.Protocol.SQL, {
24
25     /**
26      * Property: FID_PREFIX
27      * {String}
28      */
29     FID_PREFIX: '__gears_fid__',
30
31     /**
32      * Property: NULL_GEOMETRY
33      * {String}
34      */
35     NULL_GEOMETRY: '__gears_null_geometry__',
36
37     /**
38      * Property: NULL_FEATURE_STATE
39      * {String}
40      */
41     NULL_FEATURE_STATE: '__gears_null_feature_state__',
42
43     /**
44      * Property: jsonParser
45      * {<OpenLayers.Format.JSON>}
46      */
47     jsonParser: null,
48
49     /**
50      * Property: wktParser
51      * {<OpenLayers.Format.WKT>}
52      */
53     wktParser: null,
54
55     /**
56      * Property: fidRegExp
57      * {RegExp} Regular expression to know whether a feature was
58      *      created in offline mode.
59      */
60     fidRegExp: null,
61
62     /**
63      * Property: saveFeatureState
64      * {Boolean} Whether to save the feature state (<OpenLayers.State>)
65      *      into the database, defaults to true.
66      */    
67     saveFeatureState: true,
68
69     /**
70      * Property: typeOfFid
71      * {String} The type of the feature identifier, either "number" or
72      *      "string", defaults to "string".
73      */
74     typeOfFid: "string",
75
76     /**
77      * Property: db
78      * {GearsDatabase}
79      */
80     db: null,
81
82     /**
83      * Constructor: OpenLayers.Protocol.SQL.Gears
84      */
85     initialize: function(options) {
86         if (!this.supported()) {
87             return;
88         }
89         OpenLayers.Protocol.SQL.prototype.initialize.apply(this, [options]);
90         this.jsonParser = new OpenLayers.Format.JSON();
91         this.wktParser = new OpenLayers.Format.WKT();
92
93         this.fidRegExp = new RegExp('^' + this.FID_PREFIX);
94         this.initializeDatabase();
95
96         
97     },
98
99     /**
100      * Method: initializeDatabase
101      */
102     initializeDatabase: function() {
103         this.db = google.gears.factory.create('beta.database');
104         this.db.open(this.databaseName);
105         this.db.execute(
106             "CREATE TABLE IF NOT EXISTS " + this.tableName +
107             " (fid TEXT UNIQUE, geometry TEXT, properties TEXT," +
108             "  state TEXT)");
109    },
110
111     /**
112      * APIMethod: destroy
113      * Clean up the protocol.
114      */
115     destroy: function() {
116         this.db.close();
117         this.db = null;
118
119         this.jsonParser = null;
120         this.wktParser = null;
121
122         OpenLayers.Protocol.SQL.prototype.destroy.apply(this);
123     },
124
125     /**
126      * APIMethod: supported
127      * Determine whether a browser supports Gears
128      *
129      * Returns:
130      * {Boolean} The browser supports Gears
131      */
132     supported: function() {
133         return !!(window.google && google.gears);
134     },
135
136     /**
137      * APIMethod: read
138      * Read all features from the database and return a
139      * <OpenLayers.Protocol.Response> instance. If the options parameter
140      * contains a callback attribute, the function is called with the response
141      * as a parameter.
142      *
143      * Parameters:
144      * options - {Object} Optional object for configuring the request; it
145      *      can have the {Boolean} property "noFeatureStateReset" which
146      *      specifies if the state of features read from the Gears
147      *      database must be reset to null, if "noFeatureStateReset"
148      *      is undefined or false then each feature's state is reset
149      *      to null, if "noFeatureStateReset" is true the feature state
150      *      is preserved.
151      *
152      * Returns:
153      * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
154      *      object.
155      */
156     read: function(options) {
157         options = OpenLayers.Util.applyDefaults(options, this.options);
158
159         var feature, features = [];
160         var rs = this.db.execute("SELECT * FROM " + this.tableName);
161         while (rs.isValidRow()) {
162             feature = this.unfreezeFeature(rs);
163             if (this.evaluateFilter(feature, options.filter)) {
164                 if (!options.noFeatureStateReset) {
165                     feature.state = null;
166                 }
167                 features.push(feature);
168             }
169             rs.next();
170         }
171         rs.close();
172
173         var resp = new OpenLayers.Protocol.Response({
174             code: OpenLayers.Protocol.Response.SUCCESS,
175             requestType: "read",
176             features: features
177         });
178
179         if (options && options.callback) {
180             options.callback.call(options.scope, resp);
181         }
182
183         return resp;
184     },
185
186     /**
187      * Method: unfreezeFeature
188      *
189      * Parameters:
190      * row - {ResultSet}
191      *
192      * Returns:
193      * {<OpenLayers.Feature.Vector>}
194      */
195     unfreezeFeature: function(row) {
196         var feature;
197         var wkt = row.fieldByName('geometry');
198         if (wkt == this.NULL_GEOMETRY) {
199             feature = new OpenLayers.Feature.Vector();
200         } else {
201             feature = this.wktParser.read(wkt);
202         }
203
204         feature.attributes = this.jsonParser.read(
205             row.fieldByName('properties'));
206
207         feature.fid = this.extractFidFromField(row.fieldByName('fid'));
208
209         var state = row.fieldByName('state');
210         if (state == this.NULL_FEATURE_STATE) {
211             state = null;
212         }
213         feature.state = state;
214
215         return feature;
216     },
217
218     /**
219      * Method: extractFidFromField
220      *
221      * Parameters:
222      * field - {String}
223      *
224      * Returns
225      * {String} or {Number} The fid.
226      */
227     extractFidFromField: function(field) {
228         if (!field.match(this.fidRegExp) && this.typeOfFid == "number") {
229             field = parseFloat(field);
230         }
231         return field;
232     },
233
234     /**
235      * APIMethod: create
236      * Create new features into the database.
237      *
238      * Parameters:
239      * features - {Array({<OpenLayers.Feature.Vector>})} or
240      *            {<OpenLayers.Feature.Vector>} The features to create in
241      *            the database.
242      * options - {Object} Optional object for configuring the request.
243      *
244      * Returns:
245      *  {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
246      *          object.
247      */
248     create: function(features, options) {
249         options = OpenLayers.Util.applyDefaults(options, this.options);
250
251         var resp = this.createOrUpdate(features);
252         resp.requestType = "create";
253
254         if (options && options.callback) {
255             options.callback.call(options.scope, resp);
256         }
257
258         return resp;
259     },
260
261     /**
262      * APIMethod: update
263      * Construct a request updating modified feature.
264      *
265      * Parameters:
266      * features - {Array({<OpenLayers.Feature.Vector>})} or
267      *            {<OpenLayers.Feature.Vector>} The features to update in
268      *            the database.
269      * options - {Object} Optional object for configuring the request.
270      *
271      * Returns:
272      *  {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
273      *          object.
274      */
275     update: function(features, options) {
276         options = OpenLayers.Util.applyDefaults(options, this.options);
277
278         var resp = this.createOrUpdate(features);
279         resp.requestType = "update";
280
281         if (options && options.callback) {
282             options.callback.call(options.scope, resp);
283         }
284
285         return resp;
286     },
287
288     /**
289      * Method: createOrUpdate
290      * Construct a request for updating or creating features in the
291      * database.
292      *
293      * Parameters:
294      * features - {Array({<OpenLayers.Feature.Vector>})} or
295      *      {<OpenLayers.Feature.Vector>} The feature to create or update
296      *      in the database.
297      *
298      * Returns:
299      *  {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
300      *          object.
301      */
302     createOrUpdate: function(features) {
303         if (!(features instanceof Array)) {
304             features = [features];
305         }
306
307         var i, len = features.length, feature;
308         var insertedFeatures = new Array(len);
309  
310         for (i = 0; i < len; i++) {
311             feature = features[i];
312             var params = this.freezeFeature(feature);
313             this.db.execute(
314                 "REPLACE INTO " + this.tableName + 
315                 " (fid, geometry, properties, state)" + 
316                 " VALUES (?, ?, ?, ?)",
317                 params);
318
319             var clone = feature.clone();
320             clone.fid = this.extractFidFromField(params[0]);
321             insertedFeatures[i] = clone;
322         }
323
324         return new OpenLayers.Protocol.Response({
325             code: OpenLayers.Protocol.Response.SUCCESS,
326             features: insertedFeatures,
327             reqFeatures: features
328         });
329     },
330
331     /**
332      * Method: freezeFeature
333      *
334      * Parameters:
335      * feature - {<OpenLayers.Feature.Vector>}
336      * state - {String} The feature state to store in the database.
337      *
338      * Returns:
339      * {Array}
340      */
341     freezeFeature: function(feature) {
342         // 2 notes:
343         // - fid might not be a string
344         // - getFeatureStateForFreeze needs the feature fid to it's stored
345         //   in the feature here
346         feature.fid = feature.fid != null ?
347             "" + feature.fid : OpenLayers.Util.createUniqueID(this.FID_PREFIX);
348
349         var geometry = feature.geometry != null ?
350             feature.geometry.toString() : this.NULL_GEOMETRY;
351
352         var properties = this.jsonParser.write(feature.attributes);
353
354         var state = this.getFeatureStateForFreeze(feature);
355
356         return [feature.fid, geometry, properties, state];
357     },
358
359     /**
360      * Method: getFeatureStateForFreeze
361      * Get the state of the feature to store into the database.
362      *
363      * Parameters:
364      * feature - {<OpenLayers.Feature.Vector>} The feature.
365      *
366      * Returns
367      * {String} The state
368      */
369     getFeatureStateForFreeze: function(feature) {
370         var state;
371         if (!this.saveFeatureState) {
372             state = this.NULL_FEATURE_STATE;
373         } else if (this.createdOffline(feature)) {
374             // if the feature was created in offline mode, its
375             // state must remain INSERT
376             state = OpenLayers.State.INSERT;
377         } else {
378             state = feature.state;
379         }
380         return state;
381     },
382
383     /**
384      * APIMethod: delete
385      * Delete features from the database.
386      *
387      * Parameters:
388      * features - {Array({<OpenLayers.Feature.Vector>})} or
389      *            {<OpenLayers.Feature.Vector>}
390      * options - {Object} Optional object for configuring the request.
391      *       This object is modified and should not be reused.
392      *
393      * Returns:
394      *  {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
395      *          object.
396      */
397     "delete": function(features, options) {
398         if (!(features instanceof Array)) {
399             features = [features];
400         }
401
402         options = OpenLayers.Util.applyDefaults(options, this.options);
403
404         var i, len, feature;
405         for (i = 0, len = features.length; i < len; i++) {
406             feature = features[i];
407
408             // if saveFeatureState is set to true and if the feature wasn't created
409             // in offline mode we don't delete it in the database but just update 
410             // it state column
411             if (this.saveFeatureState && !this.createdOffline(feature)) {
412                 var toDelete = feature.clone();
413                 toDelete.fid = feature.fid;
414                 if (toDelete.geometry) {
415                     toDelete.geometry.destroy();
416                     toDelete.geometry = null;
417                 }
418                 toDelete.state = feature.state;
419                 this.createOrUpdate(toDelete);
420             } else {
421                 this.db.execute(
422                     "DELETE FROM " + this.tableName +
423                     " WHERE fid = ?", [feature.fid]);
424             }
425         }
426
427         var resp = new OpenLayers.Protocol.Response({
428             code: OpenLayers.Protocol.Response.SUCCESS,
429             requestType: "delete",
430             reqFeatures: features
431         });
432
433         if (options && options.callback) {
434             options.callback.call(options.scope, resp);
435         }
436
437         return resp;
438     },
439
440     /**
441      * Method: createdOffline
442      * Returns true if the feature had a feature id when it was created in
443      *      the Gears database, false otherwise; this is determined by
444      *      checking the form of the feature's fid value.
445      *
446      * Parameters:
447      * feature - {<OpenLayers.Feature.Vector>}
448      *
449      * Returns:
450      * {Boolean}
451      */
452     createdOffline: function(feature) {
453         return (typeof feature.fid == "string" &&
454                 !!(feature.fid.match(this.fidRegExp)));
455     },
456
457     /**
458      * APIMethod: commit
459      * Go over the features and for each take action
460      * based on the feature state. Possible actions are create,
461      * update and delete.
462      *
463      * Parameters:
464      * features - {Array({<OpenLayers.Feature.Vector>})}
465      * options - {Object} Object whose possible keys are "create", "update",
466      *      "delete", "callback" and "scope", the values referenced by the
467      *      first three are objects as passed to the "create", "update", and
468      *      "delete" methods, the value referenced by the "callback" key is
469      *      a function which is called when the commit operation is complete
470      *      using the scope referenced by the "scope" key.
471      *
472      * Returns:
473      * {Array({<OpenLayers.Protocol.Response>})} An array of
474      *       <OpenLayers.Protocol.Response> objects, one per request made
475      *       to the database.
476      */
477     commit: function(features, options) {
478         var opt, resp = [], nRequests = 0, nResponses = 0;
479
480         function callback(resp) {
481             if (++nResponses < nRequests) {
482                 resp.last = false;
483             }
484             this.callUserCallback(options, resp);
485         }
486
487         var feature, toCreate = [], toUpdate = [], toDelete = [];
488         for (var i = features.length - 1; i >= 0; i--) {
489             feature = features[i];
490             switch (feature.state) {
491             case OpenLayers.State.INSERT:
492                 toCreate.push(feature);
493                 break;
494             case OpenLayers.State.UPDATE:
495                 toUpdate.push(feature);
496                 break;
497             case OpenLayers.State.DELETE:
498                 toDelete.push(feature);
499                 break;
500             }
501         }
502         if (toCreate.length > 0) {
503             nRequests++;
504             opt = OpenLayers.Util.applyDefaults(
505                 {"callback": callback, "scope": this},
506                 options.create
507             );
508             resp.push(this.create(toCreate, opt));
509         }
510         if (toUpdate.length > 0) {
511             nRequests++;
512             opt = OpenLayers.Util.applyDefaults(
513                 {"callback": callback, "scope": this},
514                 options.update
515             );
516             resp.push(this.update(toUpdate, opt));
517         }
518         if (toDelete.length > 0) {
519             nRequests++;
520             opt = OpenLayers.Util.applyDefaults(
521                 {"callback": callback, "scope": this},
522                 options["delete"]
523             );
524             resp.push(this["delete"](toDelete, opt));
525         }
526
527         return resp;
528     },
529
530     /**
531      * Method: clear
532      * Removes all rows of the table.
533      */
534     clear: function() {
535         this.db.execute("DELETE FROM " + this.tableName);
536     },
537
538     /**
539      * Method: callUserCallback
540      * This method is called from within commit each time a request is made
541      * to the database, it is responsible for calling the user-supplied
542      * callbacks.
543      *
544      * Parameters:
545      * options - {Object} The map of options passed to the commit call.
546      * resp - {<OpenLayers.Protocol.Response>}
547      */
548     callUserCallback: function(options, resp) {
549         var opt = options[resp.requestType];
550         if (opt && opt.callback) {
551             opt.callback.call(opt.scope, resp);
552         }
553         if (resp.last && options.callback) {
554             options.callback.call(options.scope);
555         }
556     },
557
558     CLASS_NAME: "OpenLayers.Protocol.SQL.Gears"
559 });