diff --git a/examples/cross-origin.html b/examples/cross-origin.html new file mode 100644 index 0000000000..142ba479f9 --- /dev/null +++ b/examples/cross-origin.html @@ -0,0 +1,35 @@ + + + + OpenLayers Script Protocol Example + + + + + + + +

Script Protocol

+
+ protocol, script, cross origin, advanced +
+

+ Demonstrates the use of a script protocol for making feature requests + cross origin. +

+
+
+

+ In cases where a service returns serialized features and accepts + a named callback (e.g. http://example.com/features.json?callback=foo), + the script protocol can be used to read features without being + restricted by the same origin policy. +

+

+ View the cross-origin.js + source to see how this is done +

+
+ + + diff --git a/examples/cross-origin.js b/examples/cross-origin.js new file mode 100644 index 0000000000..6cf39ec575 --- /dev/null +++ b/examples/cross-origin.js @@ -0,0 +1,39 @@ +var map = new OpenLayers.Map({ + div: "map", + layers: [ + new OpenLayers.Layer.WMS( + "World Map", + "http://maps.opengeo.org/geowebcache/service/wms", + {layers: "bluemarble"} + ), + new OpenLayers.Layer.Vector("States", { + strategies: [new OpenLayers.Strategy.BBOX()], + protocol: new OpenLayers.Protocol.Script({ + url: "http://suite.opengeo.org/geoserver/wfs", + callbackKey: "format_options", + callbackPrefix: "callback:", + params: { + service: "WFS", + version: "1.1.0", + srsName: "EPSG:4326", + request: "GetFeature", + typeName: "world:cities", + outputFormat: "json" + }, + filterToParams: function(filter, params) { + // example to demonstrate BBOX serialization + if (filter.type === OpenLayers.Filter.Spatial.BBOX) { + params.bbox = filter.value.toArray(); + if (filter.projection) { + params.bbox.push(filter.projection.getCode()); + } + } + return params; + } + }) + }) + ], + center: new OpenLayers.LonLat(0, 0), + zoom: 1 +}); + diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 5afa2b4200..3bc4e611d3 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -252,6 +252,7 @@ "OpenLayers/Protocol/WFS/v1.js", "OpenLayers/Protocol/WFS/v1_0_0.js", "OpenLayers/Protocol/WFS/v1_1_0.js", + "OpenLayers/Protocol/Script.js", "OpenLayers/Protocol/SOS.js", "OpenLayers/Protocol/SOS/v1_0_0.js", "OpenLayers/Layer/PointTrack.js", diff --git a/lib/OpenLayers/Protocol/Script.js b/lib/OpenLayers/Protocol/Script.js new file mode 100644 index 0000000000..9f0955eb4f --- /dev/null +++ b/lib/OpenLayers/Protocol/Script.js @@ -0,0 +1,363 @@ +/* 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 OpenLayers/Protocol.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Format/GeoJSON.js + */ + +/** + * Class: OpenLayers.Protocol.Script + * A basic Script protocol for vector layers. Create a new instance with the + * constructor. A script protocol is used to + * get around the same origin policy. It works with services that return + * JSONP - that is, JSON wrapped in a client-specified callback. The + * protocol handles fetching and parsing of feature data and sends parsed + * features to the configured with the protocol. The protocol + * expects features serialized as GeoJSON by default, but can be configured + * to work with other formats by setting the property. + * + * Inherits from: + * - + */ +OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, { + + /** + * APIProperty: url + * {String} Service URL. The service is expected to return serialized + * features wrapped in a named callback (where the callback name is + * generated by this protocol). + * Read-only, set through the options passed to the constructor. + */ + url: null, + + /** + * APIProperty: params + * {Object} Query string parameters to be appended to the URL. + * Read-only, set through the options passed to the constructor. + * Example: {maxFeatures: 50} + */ + params: null, + + /** + * APIProperty: callback + * {Object} Function to be called when the operation completes. + */ + callback: null, + + /** + * APIProperty: scope + * {Object} Optional ``this`` object for the callback. Read-only, set + * through the options passed to the constructor. + */ + scope: null, + + /** + * APIProperty: format + * {} Format for parsing features. Default is an + * format. If an alternative is provided, + * the format's read method must take an object and return an array + * of features. + */ + format: null, + + /** + * APIProperty: callbackKey + * {String} The name of the query string parameter that the service + * recognizes as the callback identifier. Default is "callback". + * This key is used to generate the URL for the script. For example + * setting to "myCallback" would result in a URL like + * http://example.com/?myCallback=... + */ + callbackKey: "callback", + + /** + * APIProperty: callbackPrefix + * {String} Where a service requires that the callback query string + * parameter value is prefixed by some string, this value may be set. + * For example, setting to "foo:" would result in a + * URL like http://example.com/?callback=foo:... Default is "". + */ + callbackPrefix: "", + + /** + * Property: pendingRequests + * {Object} References all pending requests. Property names are script + * identifiers and property values are script elements. + */ + pendingRequests: null, + + /** + * Constructor: OpenLayers.Protocol.Script + * A class for giving layers generic Script protocol. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + * + * Valid options include: + * url - {String} + * params - {Object} + * callback - {Function} + * scope - {Object} + */ + initialize: function(options) { + options = options || {}; + this.params = {}; + this.pendingRequests = {}; + OpenLayers.Protocol.prototype.initialize.apply(this, arguments); + if (!this.format) { + this.format = new OpenLayers.Format.GeoJSON(); + } + + if (!this.filterToParams && OpenLayers.Protocol.simpleFilterSerializer) { + this.filterToParams = OpenLayers.Function.bind( + OpenLayers.Protocol.simpleFilterSerializer, this + ); + } + }, + + /** + * APIMethod: read + * Construct a request for reading new features. + * + * Parameters: + * options - {Object} Optional object for configuring the request. + * This object is modified and should not be reused. + * + * Valid options: + * url - {String} Url for the request. + * params - {Object} Parameters to get serialized as a query string. + * filter - {} Filter to get serialized as a + * query string. + * + * Returns: + * {} A response object, whose "priv" property + * references the injected script. This object is also passed to the + * callback function when the request completes, its "features" property + * is then populated with the features received from the server. + */ + read: function(options) { + OpenLayers.Protocol.prototype.read.apply(this, arguments); + options = OpenLayers.Util.applyDefaults(options, this.options); + options.params = OpenLayers.Util.applyDefaults( + options.params, this.options.params + ); + if (options.filter && this.filterToParams) { + options.params = this.filterToParams( + options.filter, options.params + ); + } + var response = new OpenLayers.Protocol.Response({requestType: "read"}); + var request = this.createRequest( + options.url, + options.params, + OpenLayers.Function.bind(function(data) { + response.data = data; + this.handleRead(response, options); + }, this) + ); + response.priv = request; + return response; + }, + + /** + * APIMethod: filterToParams + * Optional method to translate an object into an object + * that can be serialized as request query string provided. If a custom + * method is not provided, any filter will not be serialized. + * + * Parameters: + * filter - {} filter to convert. + * params - {Object} The parameters object. + * + * Returns: + * {Object} The resulting parameters object. + */ + + /** + * Method: createRequest + * Issues a request for features by creating injecting a script in the + * document head. + * + * Parameters: + * url - {String} Service URL. + * params - {Object} Query string parameters. + * callback - {Function} Callback to be called with resulting data. + * + * Returns: + * {HTMLScriptElement} The script pending execution. + */ + createRequest: function(url, params, callback) { + var id = OpenLayers.Protocol.Script.register(callback); + var name = "OpenLayers.Protocol.Script.getCallback(" + id + ")"; + params = OpenLayers.Util.extend({}, params); + params[this.callbackKey] = this.callbackPrefix + name; + url = OpenLayers.Util.urlAppend( + url, OpenLayers.Util.getParameterString(params) + ); + var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = url; + script.id = "OpenLayers_Protocol_Script_" + id; + this.pendingRequests[script.id] = script; + var head = document.getElementsByTagName("head")[0]; + head.appendChild(script); + return script; + }, + + /** + * Method: destroyRequest + * Remove a script node associated with a response from the document. Also + * unregisters the callback and removes the script from the + * object. + * + * Parameters: + * script - {HTMLScriptElement} + */ + destroyRequest: function(script) { + OpenLayers.Protocol.Script.unregister(script.id.split("_").pop()); + delete this.pendingRequests[script.id]; + if (script.parentNode) { + script.parentNode.removeChild(script); + } + }, + + /** + * Method: handleRead + * Individual callbacks are created for read, create and update, should + * a subclass need to override each one separately. + * + * Parameters: + * response - {} The response object to pass to + * the user callback. + * options - {Object} The user options passed to the read call. + */ + handleRead: function(response, options) { + this.handleResponse(response, options); + }, + + /** + * Method: handleResponse + * Called by CRUD specific handlers. + * + * Parameters: + * response - {} The response object to pass to + * any user callback. + * options - {Object} The user options passed to the create, read, update, + * or delete call. + */ + handleResponse: function(response, options) { + if (options.callback) { + if (response.data) { + response.features = this.parseFeatures(response.data); + response.code = OpenLayers.Protocol.Response.SUCCESS; + } else { + response.code = OpenLayers.Protocol.Response.FAILURE; + } + this.destroyRequest(response.priv); + options.callback.call(options.scope, response); + } + }, + + /** + * Method: parseFeatures + * Read Script response body and return features. + * + * Parameters: + * data - {Object} The data sent to the callback function by the server. + * + * Returns: + * {Array({})} or + * {} Array of features or a single feature. + */ + parseFeatures: function(data) { + return this.format.read(data); + }, + + /** + * APIMethod: abort + * Abort an ongoing request. If no response is provided, all pending + * requests will be aborted. + * + * Parameters: + * response - {} The response object returned + * from a request. + */ + abort: function(response) { + if (response) { + this.destroyRequest(response.priv); + } else { + for (var key in this.pendingRequests) { + this.destroyRequest(this.pendingRequests[key]); + } + } + }, + + /** + * APIMethod: destroy + * Clean up the protocol. + */ + destroy: function() { + this.abort(); + delete this.params; + delete this.format; + OpenLayers.Protocol.prototype.destroy.apply(this); + }, + + CLASS_NAME: "OpenLayers.Protocol.Script" +}); + +(function() { + var o = OpenLayers.Protocol.Script; + var counter = 0; + var registry = {}; + + /** + * Function: OpenLayers.Protocol.Script.register + * Register a callback for a newly created script. + * + * Parameters: + * callback: {Function} The callback to be executed when the newly added + * script loads. This callback will be called with a single argument + * that is the JSON returned by the service. + * + * Returns: + * {Number} An identifier for retreiving the registered callback. + */ + o.register = function(callback) { + var id = ++counter; + registry[id] = callback; + return id; + }; + + /** + * Function: OpenLayers.Protocol.Script.unregister + * Unregister a callback previously registered with the register function. + * + * Parameters: + * id: {Number} The identifer returned by the register function. + */ + o.unregister = function(id) { + delete registry[id]; + }; + + /** + * Function: OpenLayers.Protocol.Script.getCallback + * Retreive and unregister a callback. A call to this function is the "P" + * in JSONP. For example, a script may be added with a src attribute + * http://example.com/features.json?callback=OpenLayers.Protocol.Script.getCallback(1) + * + * Parameters: + * id: {Number} The identifer returned by the register function. + */ + o.getCallback = function(id) { + var callback = registry[id]; + o.unregister(id); + return callback; + }; +})(); + diff --git a/tests/Protocol/Script.html b/tests/Protocol/Script.html new file mode 100644 index 0000000000..8eaa6e803a --- /dev/null +++ b/tests/Protocol/Script.html @@ -0,0 +1,271 @@ + + + + + + + + diff --git a/tests/list-tests.html b/tests/list-tests.html index 7c61f19736..6aa13ecfa6 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -177,6 +177,7 @@
  • Projection.html
  • Protocol.html
  • Protocol/HTTP.html
  • +
  • Protocol/Script.html
  • Protocol/SimpleFilterSerializer.html
  • Protocol/SQL.html
  • Protocol/SQL/Gears.html