|
/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for |
|
* full list of contributors). Published under the Clear BSD license. |
|
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the |
|
* full text of the license. */ |
|
|
|
/** |
|
* @requires Gears/gears_init.js |
|
* @requires OpenLayers/Protocol/SQL.js |
|
* @requires OpenLayers/Format/JSON.js |
|
* @requires OpenLayers/Format/WKT.js |
|
*/ |
|
|
|
/** |
|
* Class: OpenLayers.Protocol.SQL.Gears |
|
* This Protocol stores feature in the browser via the Gears Database module |
|
* <http://code.google.com/apis/gears/api_database.html>. |
|
* |
|
* The main advantage is that all the read, create, update and delete operations |
|
* can be done offline. |
|
* |
|
* Inherits from: |
|
* - <OpenLayers.Protocol.SQL> |
|
*/ |
|
OpenLayers.Protocol.SQL.Gears = OpenLayers.Class(OpenLayers.Protocol.SQL, { |
|
|
|
/** |
|
* Property: FID_PREFIX |
|
* {String} |
|
*/ |
|
FID_PREFIX: '__gears_fid__', |
|
|
|
/** |
|
* Property: NULL_GEOMETRY |
|
* {String} |
|
*/ |
|
NULL_GEOMETRY: '__gears_null_geometry__', |
|
|
|
/** |
|
* Property: NULL_FEATURE_STATE |
|
* {String} |
|
*/ |
|
NULL_FEATURE_STATE: '__gears_null_feature_state__', |
|
|
|
/** |
|
* Property: jsonParser |
|
* {<OpenLayers.Format.JSON>} |
|
*/ |
|
jsonParser: null, |
|
|
|
/** |
|
* Property: wktParser |
|
* {<OpenLayers.Format.WKT>} |
|
*/ |
|
wktParser: null, |
|
|
|
/** |
|
* Property: fidRegExp |
|
* {RegExp} Regular expression to know whether a feature was |
|
* created in offline mode. |
|
*/ |
|
fidRegExp: null, |
|
|
|
/** |
|
* Property: saveFeatureState |
|
* {Boolean} Whether to save the feature state (<OpenLayers.State>) |
|
* into the database, defaults to true. |
|
*/ |
|
saveFeatureState: true, |
|
|
|
/** |
|
* Property: typeOfFid |
|
* {String} The type of the feature identifier, either "number" or |
|
* "string", defaults to "string". |
|
*/ |
|
typeOfFid: "string", |
|
|
|
/** |
|
* Property: db |
|
* {GearsDatabase} |
|
*/ |
|
db: null, |
|
|
|
/** |
|
* Constructor: OpenLayers.Protocol.SQL.Gears |
|
*/ |
|
initialize: function(options) { |
|
if (!this.supported()) { |
|
return; |
|
} |
|
OpenLayers.Protocol.SQL.prototype.initialize.apply(this, [options]); |
|
this.jsonParser = new OpenLayers.Format.JSON(); |
|
this.wktParser = new OpenLayers.Format.WKT(); |
|
|
|
this.fidRegExp = new RegExp('^' + this.FID_PREFIX); |
|
this.initializeDatabase(); |
|
|
|
|
|
}, |
|
|
|
/** |
|
* Method: initializeDatabase |
|
*/ |
|
initializeDatabase: function() { |
|
this.db = google.gears.factory.create('beta.database'); |
|
this.db.open(this.databaseName); |
|
this.db.execute( |
|
"CREATE TABLE IF NOT EXISTS " + this.tableName + |
|
" (fid TEXT UNIQUE, geometry TEXT, properties TEXT," + |
|
" state TEXT)"); |
|
}, |
|
|
|
/** |
|
* APIMethod: destroy |
|
* Clean up the protocol. |
|
*/ |
|
destroy: function() { |
|
this.db.close(); |
|
this.db = null; |
|
|
|
this.jsonParser = null; |
|
this.wktParser = null; |
|
|
|
OpenLayers.Protocol.SQL.prototype.destroy.apply(this); |
|
}, |
|
|
|
/** |
|
* APIMethod: supported |
|
* Determine whether a browser supports Gears |
|
* |
|
* Returns: |
|
* {Boolean} The browser supports Gears |
|
*/ |
|
supported: function() { |
|
return !!(window.google && google.gears); |
|
}, |
|
|
|
/** |
|
* APIMethod: read |
|
* Read all features from the database and return a |
|
* <OpenLayers.Protocol.Response> instance. If the options parameter |
|
* contains a callback attribute, the function is called with the response |
|
* as a parameter. |
|
* |
|
* Parameters: |
|
* options - {Object} Optional object for configuring the request; it |
|
* can have the {Boolean} property "noFeatureStateReset" which |
|
* specifies if the state of features read from the Gears |
|
* database must be reset to null, if "noFeatureStateReset" |
|
* is undefined or false then each feature's state is reset |
|
* to null, if "noFeatureStateReset" is true the feature state |
|
* is preserved. |
|
* |
|
* Returns: |
|
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response> |
|
* object. |
|
*/ |
|
read: function(options) { |
|
OpenLayers.Protocol.prototype.read.apply(this, arguments); |
|
options = OpenLayers.Util.applyDefaults(options, this.options); |
|
|
|
var feature, features = []; |
|
var rs = this.db.execute("SELECT * FROM " + this.tableName); |
|
while (rs.isValidRow()) { |
|
feature = this.unfreezeFeature(rs); |
|
if (this.evaluateFilter(feature, options.filter)) { |
|
if (!options.noFeatureStateReset) { |
|
feature.state = null; |
|
} |
|
features.push(feature); |
|
} |
|
rs.next(); |
|
} |
|
rs.close(); |
|
|
|
var resp = new OpenLayers.Protocol.Response({ |
|
code: OpenLayers.Protocol.Response.SUCCESS, |
|
requestType: "read", |
|
features: features |
|
}); |
|
|
|
if (options && options.callback) { |
|
options.callback.call(options.scope, resp); |
|
} |
|
|
|
return resp; |
|
}, |
|
|
|
/** |
|
* Method: unfreezeFeature |
|
* |
|
* Parameters: |
|
* row - {ResultSet} |
|
* |
|
* Returns: |
|
* {<OpenLayers.Feature.Vector>} |
|
*/ |
|
unfreezeFeature: function(row) { |
|
var feature; |
|
var wkt = row.fieldByName('geometry'); |
|
if (wkt == this.NULL_GEOMETRY) { |
|
feature = new OpenLayers.Feature.Vector(); |
|
} else { |
|
feature = this.wktParser.read(wkt); |
|
} |
|
|
|
feature.attributes = this.jsonParser.read( |
|
row.fieldByName('properties')); |
|
|
|
feature.fid = this.extractFidFromField(row.fieldByName('fid')); |
|
|
|
var state = row.fieldByName('state'); |
|
if (state == this.NULL_FEATURE_STATE) { |
|
state = null; |
|
} |
|
feature.state = state; |
|
|
|
return feature; |
|
}, |
|
|
|
/** |
|
* Method: extractFidFromField |
|
* |
|
* Parameters: |
|
* field - {String} |
|
* |
|
* Returns |
|
* {String} or {Number} The fid. |
|
*/ |
|
extractFidFromField: function(field) { |
|
if (!field.match(this.fidRegExp) && this.typeOfFid == "number") { |
|
field = parseFloat(field); |
|
} |
|
return field; |
|
}, |
|
|
|
/** |
|
* APIMethod: create |
|
* Create new features into the database. |
|
* |
|
* Parameters: |
|
* features - {Array({<OpenLayers.Feature.Vector>})} or |
|
* {<OpenLayers.Feature.Vector>} The features to create in |
|
* the database. |
|
* options - {Object} Optional object for configuring the request. |
|
* |
|
* Returns: |
|
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response> |
|
* object. |
|
*/ |
|
create: function(features, options) { |
|
options = OpenLayers.Util.applyDefaults(options, this.options); |
|
|
|
var resp = this.createOrUpdate(features); |
|
resp.requestType = "create"; |
|
|
|
if (options && options.callback) { |
|
options.callback.call(options.scope, resp); |
|
} |
|
|
|
return resp; |
|
}, |
|
|
|
/** |
|
* APIMethod: update |
|
* Construct a request updating modified feature. |
|
* |
|
* Parameters: |
|
* features - {Array({<OpenLayers.Feature.Vector>})} or |
|
* {<OpenLayers.Feature.Vector>} The features to update in |
|
* the database. |
|
* options - {Object} Optional object for configuring the request. |
|
* |
|
* Returns: |
|
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response> |
|
* object. |
|
*/ |
|
update: function(features, options) { |
|
options = OpenLayers.Util.applyDefaults(options, this.options); |
|
|
|
var resp = this.createOrUpdate(features); |
|
resp.requestType = "update"; |
|
|
|
if (options && options.callback) { |
|
options.callback.call(options.scope, resp); |
|
} |
|
|
|
return resp; |
|
}, |
|
|
|
/** |
|
* Method: createOrUpdate |
|
* Construct a request for updating or creating features in the |
|
* database. |
|
* |
|
* Parameters: |
|
* features - {Array({<OpenLayers.Feature.Vector>})} or |
|
* {<OpenLayers.Feature.Vector>} The feature to create or update |
|
* in the database. |
|
* |
|
* Returns: |
|
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response> |
|
* object. |
|
*/ |
|
createOrUpdate: function(features) { |
|
if (!(features instanceof Array)) { |
|
features = [features]; |
|
} |
|
|
|
var i, len = features.length, feature; |
|
var insertedFeatures = new Array(len); |
|
|
|
for (i = 0; i < len; i++) { |
|
feature = features[i]; |
|
var params = this.freezeFeature(feature); |
|
this.db.execute( |
|
"REPLACE INTO " + this.tableName + |
|
" (fid, geometry, properties, state)" + |
|
" VALUES (?, ?, ?, ?)", |
|
params); |
|
|
|
var clone = feature.clone(); |
|
clone.fid = this.extractFidFromField(params[0]); |
|
insertedFeatures[i] = clone; |
|
} |
|
|
|
return new OpenLayers.Protocol.Response({ |
|
code: OpenLayers.Protocol.Response.SUCCESS, |
|
features: insertedFeatures, |
|
reqFeatures: features |
|
}); |
|
}, |
|
|
|
/** |
|
* Method: freezeFeature |
|
* |
|
* Parameters: |
|
* feature - {<OpenLayers.Feature.Vector>} |
|
* state - {String} The feature state to store in the database. |
|
* |
|
* Returns: |
|
* {Array} |
|
*/ |
|
freezeFeature: function(feature) { |
|
// 2 notes: |
|
// - fid might not be a string |
|
// - getFeatureStateForFreeze needs the feature fid to it's stored |
|
// in the feature here |
|
feature.fid = feature.fid != null ? |
|
"" + feature.fid : OpenLayers.Util.createUniqueID(this.FID_PREFIX); |
|
|
|
var geometry = feature.geometry != null ? |
|
feature.geometry.toString() : this.NULL_GEOMETRY; |
|
|
|
var properties = this.jsonParser.write(feature.attributes); |
|
|
|
var state = this.getFeatureStateForFreeze(feature); |
|
|
|
return [feature.fid, geometry, properties, state]; |
|
}, |
|
|
|
/** |
|
* Method: getFeatureStateForFreeze |
|
* Get the state of the feature to store into the database. |
|
* |
|
* Parameters: |
|
* feature - {<OpenLayers.Feature.Vector>} The feature. |
|
* |
|
* Returns |
|
* {String} The state |
|
*/ |
|
getFeatureStateForFreeze: function(feature) { |
|
var state; |
|
if (!this.saveFeatureState) { |
|
state = this.NULL_FEATURE_STATE; |
|
} else if (this.createdOffline(feature)) { |
|
// if the feature was created in offline mode, its |
|
// state must remain INSERT |
|
state = OpenLayers.State.INSERT; |
|
} else { |
|
state = feature.state; |
|
} |
|
return state; |
|
}, |
|
|
|
/** |
|
* APIMethod: delete |
|
* Delete features from the database. |
|
* |
|
* Parameters: |
|
* features - {Array({<OpenLayers.Feature.Vector>})} or |
|
* {<OpenLayers.Feature.Vector>} |
|
* options - {Object} Optional object for configuring the request. |
|
* This object is modified and should not be reused. |
|
* |
|
* Returns: |
|
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response> |
|
* object. |
|
*/ |
|
"delete": function(features, options) { |
|
if (!(features instanceof Array)) { |
|
features = [features]; |
|
} |
|
|
|
options = OpenLayers.Util.applyDefaults(options, this.options); |
|
|
|
var i, len, feature; |
|
for (i = 0, len = features.length; i < len; i++) { |
|
feature = features[i]; |
|
|
|
// if saveFeatureState is set to true and if the feature wasn't created |
|
// in offline mode we don't delete it in the database but just update |
|
// it state column |
|
if (this.saveFeatureState && !this.createdOffline(feature)) { |
|
var toDelete = feature.clone(); |
|
toDelete.fid = feature.fid; |
|
if (toDelete.geometry) { |
|
toDelete.geometry.destroy(); |
|
toDelete.geometry = null; |
|
} |
|
toDelete.state = feature.state; |
|
this.createOrUpdate(toDelete); |
|
} else { |
|
this.db.execute( |
|
"DELETE FROM " + this.tableName + |
|
" WHERE fid = ?", [feature.fid]); |
|
} |
|
} |
|
|
|
var resp = new OpenLayers.Protocol.Response({ |
|
code: OpenLayers.Protocol.Response.SUCCESS, |
|
requestType: "delete", |
|
reqFeatures: features |
|
}); |
|
|
|
if (options && options.callback) { |
|
options.callback.call(options.scope, resp); |
|
} |
|
|
|
return resp; |
|
}, |
|
|
|
/** |
|
* Method: createdOffline |
|
* Returns true if the feature had a feature id when it was created in |
|
* the Gears database, false otherwise; this is determined by |
|
* checking the form of the feature's fid value. |
|
* |
|
* Parameters: |
|
* feature - {<OpenLayers.Feature.Vector>} |
|
* |
|
* Returns: |
|
* {Boolean} |
|
*/ |
|
createdOffline: function(feature) { |
|
return (typeof feature.fid == "string" && |
|
!!(feature.fid.match(this.fidRegExp))); |
|
}, |
|
|
|
/** |
|
* APIMethod: commit |
|
* Go over the features and for each take action |
|
* based on the feature state. Possible actions are create, |
|
* update and delete. |
|
* |
|
* Parameters: |
|
* features - {Array({<OpenLayers.Feature.Vector>})} |
|
* options - {Object} Object whose possible keys are "create", "update", |
|
* "delete", "callback" and "scope", the values referenced by the |
|
* first three are objects as passed to the "create", "update", and |
|
* "delete" methods, the value referenced by the "callback" key is |
|
* a function which is called when the commit operation is complete |
|
* using the scope referenced by the "scope" key. |
|
* |
|
* Returns: |
|
* {Array({<OpenLayers.Protocol.Response>})} An array of |
|
* <OpenLayers.Protocol.Response> objects, one per request made |
|
* to the database. |
|
*/ |
|
commit: function(features, options) { |
|
var opt, resp = [], nRequests = 0, nResponses = 0; |
|
|
|
function callback(resp) { |
|
if (++nResponses < nRequests) { |
|
resp.last = false; |
|
} |
|
this.callUserCallback(options, resp); |
|
} |
|
|
|
var feature, toCreate = [], toUpdate = [], toDelete = []; |
|
for (var i = features.length - 1; i >= 0; i--) { |
|
feature = features[i]; |
|
switch (feature.state) { |
|
case OpenLayers.State.INSERT: |
|
toCreate.push(feature); |
|
break; |
|
case OpenLayers.State.UPDATE: |
|
toUpdate.push(feature); |
|
break; |
|
case OpenLayers.State.DELETE: |
|
toDelete.push(feature); |
|
break; |
|
} |
|
} |
|
if (toCreate.length > 0) { |
|
nRequests++; |
|
opt = OpenLayers.Util.applyDefaults( |
|
{"callback": callback, "scope": this}, |
|
options.create |
|
); |
|
resp.push(this.create(toCreate, opt)); |
|
} |
|
if (toUpdate.length > 0) { |
|
nRequests++; |
|
opt = OpenLayers.Util.applyDefaults( |
|
{"callback": callback, "scope": this}, |
|
options.update |
|
); |
|
resp.push(this.update(toUpdate, opt)); |
|
} |
|
if (toDelete.length > 0) { |
|
nRequests++; |
|
opt = OpenLayers.Util.applyDefaults( |
|
{"callback": callback, "scope": this}, |
|
options["delete"] |
|
); |
|
resp.push(this["delete"](toDelete, opt)); |
|
} |
|
|
|
return resp; |
|
}, |
|
|
|
/** |
|
* Method: clear |
|
* Removes all rows of the table. |
|
*/ |
|
clear: function() { |
|
this.db.execute("DELETE FROM " + this.tableName); |
|
}, |
|
|
|
/** |
|
* Method: callUserCallback |
|
* This method is called from within commit each time a request is made |
|
* to the database, it is responsible for calling the user-supplied |
|
* callbacks. |
|
* |
|
* Parameters: |
|
* options - {Object} The map of options passed to the commit call. |
|
* resp - {<OpenLayers.Protocol.Response>} |
|
*/ |
|
callUserCallback: function(options, resp) { |
|
var opt = options[resp.requestType]; |
|
if (opt && opt.callback) { |
|
opt.callback.call(opt.scope, resp); |
|
} |
|
if (resp.last && options.callback) { |
|
options.callback.call(options.scope); |
|
} |
|
}, |
|
|
|
CLASS_NAME: "OpenLayers.Protocol.SQL.Gears" |
|
}); |
|
|