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. */
6 * @requires Gears/gears_init.js
7 * @requires OpenLayers/Protocol/SQL.js
8 * @requires OpenLayers/Format/JSON.js
9 * @requires OpenLayers/Format/WKT.js
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>.
17 * The main advantage is that all the read, create, update and delete operations
18 * can be done offline.
21 * - <OpenLayers.Protocol.SQL>
23 OpenLayers.Protocol.SQL.Gears = OpenLayers.Class(OpenLayers.Protocol.SQL, {
26 * Property: FID_PREFIX
29 FID_PREFIX: '__gears_fid__',
32 * Property: NULL_GEOMETRY
35 NULL_GEOMETRY: '__gears_null_geometry__',
38 * Property: NULL_FEATURE_STATE
41 NULL_FEATURE_STATE: '__gears_null_feature_state__',
44 * Property: jsonParser
45 * {<OpenLayers.Format.JSON>}
51 * {<OpenLayers.Format.WKT>}
57 * {RegExp} Regular expression to know whether a feature was
58 * created in offline mode.
63 * Property: saveFeatureState
64 * {Boolean} Whether to save the feature state (<OpenLayers.State>)
65 * into the database, defaults to true.
67 saveFeatureState: true,
71 * {String} The type of the feature identifier, either "number" or
72 * "string", defaults to "string".
83 * Constructor: OpenLayers.Protocol.SQL.Gears
85 initialize: function(options) {
86 if (!this.supported()) {
89 OpenLayers.Protocol.SQL.prototype.initialize.apply(this, [options]);
90 this.jsonParser = new OpenLayers.Format.JSON();
91 this.wktParser = new OpenLayers.Format.WKT();
93 this.fidRegExp = new RegExp('^' + this.FID_PREFIX);
94 this.initializeDatabase();
100 * Method: initializeDatabase
102 initializeDatabase: function() {
103 this.db = google.gears.factory.create('beta.database');
104 this.db.open(this.databaseName);
106 "CREATE TABLE IF NOT EXISTS " + this.tableName +
107 " (fid TEXT UNIQUE, geometry TEXT, properties TEXT," +
113 * Clean up the protocol.
115 destroy: function() {
119 this.jsonParser = null;
120 this.wktParser = null;
122 OpenLayers.Protocol.SQL.prototype.destroy.apply(this);
126 * APIMethod: supported
127 * Determine whether a browser supports Gears
130 * {Boolean} The browser supports Gears
132 supported: function() {
133 return !!(window.google && google.gears);
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
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
153 * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
156 read: function(options) {
157 options = OpenLayers.Util.applyDefaults(options, this.options);
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;
167 features.push(feature);
173 var resp = new OpenLayers.Protocol.Response({
174 code: OpenLayers.Protocol.Response.SUCCESS,
179 if (options && options.callback) {
180 options.callback.call(options.scope, resp);
187 * Method: unfreezeFeature
193 * {<OpenLayers.Feature.Vector>}
195 unfreezeFeature: function(row) {
197 var wkt = row.fieldByName('geometry');
198 if (wkt == this.NULL_GEOMETRY) {
199 feature = new OpenLayers.Feature.Vector();
201 feature = this.wktParser.read(wkt);
204 feature.attributes = this.jsonParser.read(
205 row.fieldByName('properties'));
207 feature.fid = this.extractFidFromField(row.fieldByName('fid'));
209 var state = row.fieldByName('state');
210 if (state == this.NULL_FEATURE_STATE) {
213 feature.state = state;
219 * Method: extractFidFromField
225 * {String} or {Number} The fid.
227 extractFidFromField: function(field) {
228 if (!field.match(this.fidRegExp) && this.typeOfFid == "number") {
229 field = parseFloat(field);
236 * Create new features into the database.
239 * features - {Array({<OpenLayers.Feature.Vector>})} or
240 * {<OpenLayers.Feature.Vector>} The features to create in
242 * options - {Object} Optional object for configuring the request.
245 * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
248 create: function(features, options) {
249 options = OpenLayers.Util.applyDefaults(options, this.options);
251 var resp = this.createOrUpdate(features);
252 resp.requestType = "create";
254 if (options && options.callback) {
255 options.callback.call(options.scope, resp);
263 * Construct a request updating modified feature.
266 * features - {Array({<OpenLayers.Feature.Vector>})} or
267 * {<OpenLayers.Feature.Vector>} The features to update in
269 * options - {Object} Optional object for configuring the request.
272 * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
275 update: function(features, options) {
276 options = OpenLayers.Util.applyDefaults(options, this.options);
278 var resp = this.createOrUpdate(features);
279 resp.requestType = "update";
281 if (options && options.callback) {
282 options.callback.call(options.scope, resp);
289 * Method: createOrUpdate
290 * Construct a request for updating or creating features in the
294 * features - {Array({<OpenLayers.Feature.Vector>})} or
295 * {<OpenLayers.Feature.Vector>} The feature to create or update
299 * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
302 createOrUpdate: function(features) {
303 if (!(features instanceof Array)) {
304 features = [features];
307 var i, len = features.length, feature;
308 var insertedFeatures = new Array(len);
310 for (i = 0; i < len; i++) {
311 feature = features[i];
312 var params = this.freezeFeature(feature);
314 "REPLACE INTO " + this.tableName +
315 " (fid, geometry, properties, state)" +
316 " VALUES (?, ?, ?, ?)",
319 var clone = feature.clone();
320 clone.fid = this.extractFidFromField(params[0]);
321 insertedFeatures[i] = clone;
324 return new OpenLayers.Protocol.Response({
325 code: OpenLayers.Protocol.Response.SUCCESS,
326 features: insertedFeatures,
327 reqFeatures: features
332 * Method: freezeFeature
335 * feature - {<OpenLayers.Feature.Vector>}
336 * state - {String} The feature state to store in the database.
341 freezeFeature: function(feature) {
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);
349 var geometry = feature.geometry != null ?
350 feature.geometry.toString() : this.NULL_GEOMETRY;
352 var properties = this.jsonParser.write(feature.attributes);
354 var state = this.getFeatureStateForFreeze(feature);
356 return [feature.fid, geometry, properties, state];
360 * Method: getFeatureStateForFreeze
361 * Get the state of the feature to store into the database.
364 * feature - {<OpenLayers.Feature.Vector>} The feature.
369 getFeatureStateForFreeze: function(feature) {
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;
378 state = feature.state;
385 * Delete features from the database.
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.
394 * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
397 "delete": function(features, options) {
398 if (!(features instanceof Array)) {
399 features = [features];
402 options = OpenLayers.Util.applyDefaults(options, this.options);
405 for (i = 0, len = features.length; i < len; i++) {
406 feature = features[i];
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
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;
418 toDelete.state = feature.state;
419 this.createOrUpdate(toDelete);
422 "DELETE FROM " + this.tableName +
423 " WHERE fid = ?", [feature.fid]);
427 var resp = new OpenLayers.Protocol.Response({
428 code: OpenLayers.Protocol.Response.SUCCESS,
429 requestType: "delete",
430 reqFeatures: features
433 if (options && options.callback) {
434 options.callback.call(options.scope, resp);
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.
447 * feature - {<OpenLayers.Feature.Vector>}
452 createdOffline: function(feature) {
453 return (typeof feature.fid == "string" &&
454 !!(feature.fid.match(this.fidRegExp)));
459 * Go over the features and for each take action
460 * based on the feature state. Possible actions are create,
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.
473 * {Array({<OpenLayers.Protocol.Response>})} An array of
474 * <OpenLayers.Protocol.Response> objects, one per request made
477 commit: function(features, options) {
478 var opt, resp = [], nRequests = 0, nResponses = 0;
480 function callback(resp) {
481 if (++nResponses < nRequests) {
484 this.callUserCallback(options, resp);
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);
494 case OpenLayers.State.UPDATE:
495 toUpdate.push(feature);
497 case OpenLayers.State.DELETE:
498 toDelete.push(feature);
502 if (toCreate.length > 0) {
504 opt = OpenLayers.Util.applyDefaults(
505 {"callback": callback, "scope": this},
508 resp.push(this.create(toCreate, opt));
510 if (toUpdate.length > 0) {
512 opt = OpenLayers.Util.applyDefaults(
513 {"callback": callback, "scope": this},
516 resp.push(this.update(toUpdate, opt));
518 if (toDelete.length > 0) {
520 opt = OpenLayers.Util.applyDefaults(
521 {"callback": callback, "scope": this},
524 resp.push(this["delete"](toDelete, opt));
532 * Removes all rows of the table.
535 this.db.execute("DELETE FROM " + this.tableName);
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
545 * options - {Object} The map of options passed to the commit call.
546 * resp - {<OpenLayers.Protocol.Response>}
548 callUserCallback: function(options, resp) {
549 var opt = options[resp.requestType];
550 if (opt && opt.callback) {
551 opt.callback.call(opt.scope, resp);
553 if (resp.last && options.callback) {
554 options.callback.call(options.scope);
558 CLASS_NAME: "OpenLayers.Protocol.SQL.Gears"