]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Protocol/HTTP.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Protocol / HTTP.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 OpenLayers/Protocol.js
7  * @requires OpenLayers/Feature/Vector.js
8  */
9
10 /**
11  * Class: OpenLayers.Protocol.HTTP
12  * A basic HTTP protocol for vector layers.  Create a new instance with the
13  *     <OpenLayers.Protocol.HTTP> constructor.
14  *
15  * Inherits from:
16  *  - <OpenLayers.Protocol>
17  */
18 OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
19
20     /**
21      * Property: url
22      * {String} Service URL, read-only, set through the options
23      *     passed to constructor.
24      */
25     url: null,
26
27     /**
28      * Property: headers
29      * {Object} HTTP request headers, read-only, set through the options
30      *     passed to the constructor,
31      *     Example: {'Content-Type': 'plain/text'}
32      */
33     headers: null,
34
35     /**
36      * Property: params
37      * {Object} Parameters of GET requests, read-only, set through the options
38      *     passed to the constructor,
39      *     Example: {'bbox': '5,5,5,5'}
40      */
41     params: null,
42     
43     /**
44      * Property: callback
45      * {Object} Function to be called when the <read>, <create>,
46      *     <update>, <delete> or <commit> operation completes, read-only,
47      *     set through the options passed to the constructor.
48      */
49     callback: null,
50
51     /**
52      * Property: scope
53      * {Object} Callback execution scope, read-only, set through the
54      *     options passed to the constructor.
55      */
56     scope: null,
57
58     /**
59      * Property: readWithPOST
60      * {Boolean} true if read operations are done with POST requests
61      *     instead of GET, defaults to false.
62      */
63     readWithPOST: false,
64
65     /**
66      * Constructor: OpenLayers.Protocol.HTTP
67      * A class for giving layers generic HTTP protocol.
68      *
69      * Parameters:
70      * options - {Object} Optional object whose properties will be set on the
71      *     instance.
72      *
73      * Valid options include:
74      * url - {String}
75      * headers - {Object} 
76      * params - {Object}
77      * format - {<OpenLayers.Format>}
78      * callback - {Function}
79      * scope - {Object}
80      */
81     initialize: function(options) {
82         this.params = {};
83         this.headers = {};
84         OpenLayers.Protocol.prototype.initialize.apply(this, arguments);
85     },
86     
87     /**
88      * APIMethod: destroy
89      * Clean up the protocol.
90      */
91     destroy: function() {
92         this.params = null;
93         this.headers = null;
94         OpenLayers.Protocol.prototype.destroy.apply(this);
95     },
96    
97     /**
98      * Method: createCallback
99      * Returns a function that applies the given public method with resp and
100      *     options arguments.
101      *
102      * Parameters:
103      * method - {Function} The method to be applied by the callback.
104      * response - {<OpenLayers.Protocol.Response>} The protocol response object.
105      * options - {Object} Options sent to the protocol method (read, create,
106      *     update, or delete).
107      */
108     createCallback: function(method, response, options) {
109         return OpenLayers.Function.bind(function() {
110             method.apply(this, [response, options]);
111         }, this);
112     },
113
114     /**
115      * APIMethod: read
116      * Construct a request for reading new features.
117      *
118      * Parameters:
119      * options - {Object} Optional object for configuring the request.
120      *     This object is modified and should not be reused.
121      *
122      * Valid options:
123      * url - {String} Url for the request.
124      * params - {Object} Parameters to get serialized as a query string.
125      * headers - {Object} Headers to be set on the request.
126      * filter - {<OpenLayers.Filter.BBOX>} If a bbox filter is sent, it will be
127      *     serialized according to the OpenSearch Geo extension
128      *     (bbox=minx,miny,maxx,maxy).  Note that a BBOX filter as the child
129      *     of a logical filter will not be serialized.
130      * readWithPOST - {Boolean} If the request should be done with POST.
131      *
132      * Returns:
133      * {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
134      *     references the HTTP request, this object is also passed to the
135      *     callback function when the request completes, its "features" property
136      *     is then populated with the the features received from the server.
137      */
138     read: function(options) {
139         options = OpenLayers.Util.applyDefaults(options, this.options);
140         var readWithPOST = (options.readWithPOST !== undefined) ?
141                            options.readWithPOST : this.readWithPOST;
142         var resp = new OpenLayers.Protocol.Response({requestType: "read"});
143
144         if(options.filter && options.filter instanceof OpenLayers.Filter.Spatial) {
145             if(options.filter.type == OpenLayers.Filter.Spatial.BBOX) {
146                 options.params = OpenLayers.Util.extend(options.params, {
147                     bbox: options.filter.value.toArray()
148                 });
149             }
150         }
151
152         if(readWithPOST) {
153             resp.priv = OpenLayers.Request.POST({
154                 url: options.url,
155                 callback: this.createCallback(this.handleRead, resp, options),
156                 data: OpenLayers.Util.getParameterString(options.params),
157                 headers: {
158                     "Content-Type": "application/x-www-form-urlencoded"
159                 }
160             });
161         } else {
162             resp.priv = OpenLayers.Request.GET({
163                 url: options.url,
164                 callback: this.createCallback(this.handleRead, resp, options),
165                 params: options.params,
166                 headers: options.headers
167             });
168         }
169
170         return resp;
171     },
172
173     /**
174      * Method: handleRead
175      * Individual callbacks are created for read, create and update, should
176      *     a subclass need to override each one separately.
177      *
178      * Parameters:
179      * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
180      *     the user callback.
181      * options - {Object} The user options passed to the read call.
182      */
183     handleRead: function(resp, options) {
184         this.handleResponse(resp, options);
185     },
186     
187     /**
188      * APIMethod: create
189      * Construct a request for writing newly created features.
190      *
191      * Parameters:
192      * features - {Array({<OpenLayers.Feature.Vector>})} or
193      *     {<OpenLayers.Feature.Vector>}
194      * options - {Object} Optional object for configuring the request.
195      *     This object is modified and should not be reused.
196      *
197      * Returns:
198      * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
199      *     object, whose "priv" property references the HTTP request, this 
200      *     object is also passed to the callback function when the request
201      *     completes, its "features" property is then populated with the
202      *     the features received from the server.
203      */
204     create: function(features, options) {
205         options = OpenLayers.Util.applyDefaults(options, this.options);
206
207         var resp = new OpenLayers.Protocol.Response({
208             reqFeatures: features,
209             requestType: "create"
210         });
211
212         resp.priv = OpenLayers.Request.POST({
213             url: options.url,
214             callback: this.createCallback(this.handleCreate, resp, options),
215             headers: options.headers,
216             data: this.format.write(features)
217         });
218
219         return resp;
220     },
221
222     /**
223      * Method: handleCreate
224      * Called the the request issued by <create> is complete.  May be overridden
225      *     by subclasses.
226      *
227      * Parameters:
228      * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
229      *     any user callback.
230      * options - {Object} The user options passed to the create call.
231      */
232     handleCreate: function(resp, options) {
233         this.handleResponse(resp, options);
234     },
235
236     /**
237      * APIMethod: update
238      * Construct a request updating modified feature.
239      *
240      * Parameters:
241      * feature - {<OpenLayers.Feature.Vector>}
242      * options - {Object} Optional object for configuring the request.
243      *     This object is modified and should not be reused.
244      *
245      * Returns:
246      * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
247      *     object, whose "priv" property references the HTTP request, this 
248      *     object is also passed to the callback function when the request
249      *     completes, its "features" property is then populated with the
250      *     the feature received from the server.
251      */
252     update: function(feature, options) {
253         var url = options.url || feature.url || this.options.url;
254         options = OpenLayers.Util.applyDefaults(options, this.options);
255
256         var resp = new OpenLayers.Protocol.Response({
257             reqFeatures: feature,
258             requestType: "update"
259         });
260
261         resp.priv = OpenLayers.Request.PUT({
262             url: url,
263             callback: this.createCallback(this.handleUpdate, resp, options),
264             headers: options.headers,
265             data: this.format.write(feature)
266         });
267
268         return resp;
269     },
270
271     /**
272      * Method: handleUpdate
273      * Called the the request issued by <update> is complete.  May be overridden
274      *     by subclasses.
275      *
276      * Parameters:
277      * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
278      *     any user callback.
279      * options - {Object} The user options passed to the update call.
280      */
281     handleUpdate: function(resp, options) {
282         this.handleResponse(resp, options);
283     },
284
285     /**
286      * APIMethod: delete
287      * Construct a request deleting a removed feature.
288      *
289      * Parameters:
290      * feature - {<OpenLayers.Feature.Vector>}
291      * options - {Object} Optional object for configuring the request.
292      *     This object is modified and should not be reused.
293      *
294      * Returns:
295      * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
296      *     object, whose "priv" property references the HTTP request, this 
297      *     object is also passed to the callback function when the request
298      *     completes.
299      */
300     "delete": function(feature, options) {
301         var url = options.url || feature.url || this.options.url;
302         options = OpenLayers.Util.applyDefaults(options, this.options);
303
304         var resp = new OpenLayers.Protocol.Response({
305             reqFeatures: feature,
306             requestType: "delete"
307         });
308
309         resp.priv = OpenLayers.Request.DELETE({
310             url: url,
311             callback: this.createCallback(this.handleDelete, resp, options),
312             headers: options.headers
313         });
314
315         return resp;
316     },
317
318     /**
319      * Method: handleDelete
320      * Called the the request issued by <delete> is complete.  May be overridden
321      *     by subclasses.
322      *
323      * Parameters:
324      * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
325      *     any user callback.
326      * options - {Object} The user options passed to the delete call.
327      */
328     handleDelete: function(resp, options) {
329         this.handleResponse(resp, options);
330     },
331
332     /**
333      * Method: handleResponse
334      * Called by CRUD specific handlers.
335      *
336      * Parameters:
337      * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
338      *     any user callback.
339      * options - {Object} The user options passed to the create, read, update,
340      *     or delete call.
341      */
342     handleResponse: function(resp, options) {
343         var request = resp.priv;
344         if(options.callback) {
345             if(request.status >= 200 && request.status < 300) {
346                 // success
347                 if(resp.requestType != "delete") {
348                     resp.features = this.parseFeatures(request);
349                 }
350                 resp.code = OpenLayers.Protocol.Response.SUCCESS;
351             } else {
352                 // failure
353                 resp.code = OpenLayers.Protocol.Response.FAILURE;
354             }
355             options.callback.call(options.scope, resp);
356         }
357     },
358
359     /**
360      * Method: parseFeatures
361      * Read HTTP response body and return features.
362      *
363      * Parameters:
364      * request - {XMLHttpRequest} The request object
365      *
366      * Returns:
367      * {Array({<OpenLayers.Feature.Vector>})} or
368      *     {<OpenLayers.Feature.Vector>} Array of features or a single feature.
369      */
370     parseFeatures: function(request) {
371         var doc = request.responseXML;
372         if (!doc || !doc.documentElement) {
373             doc = request.responseText;
374         }
375         if (!doc || doc.length <= 0) {
376             return null;
377         }
378         return this.format.read(doc);
379     },
380
381     /**
382      * APIMethod: commit
383      * Iterate over each feature and take action based on the feature state.
384      *     Possible actions are create, update and delete.
385      *
386      * Parameters:
387      * features - {Array({<OpenLayers.Feature.Vector>})}
388      * options - {Object} Optional object for setting up intermediate commit
389      *     callbacks.
390      *
391      * Valid options:
392      * create - {Object} Optional object to be passed to the <create> method.
393      * update - {Object} Optional object to be passed to the <update> method.
394      * delete - {Object} Optional object to be passed to the <delete> method.
395      * callback - {Function} Optional function to be called when the commit
396      *     is complete.
397      * scope - {Object} Optional object to be set as the scope of the callback.
398      *
399      * Returns:
400      * {Array(<OpenLayers.Protocol.Response>)} An array of response objects,
401      *     one per request made to the server, each object's "priv" property
402      *     references the corresponding HTTP request.
403      */
404     commit: function(features, options) {
405         options = OpenLayers.Util.applyDefaults(options, this.options);
406         var resp = [], nResponses = 0;
407         
408         // Divide up features before issuing any requests.  This properly
409         // counts requests in the event that any responses come in before
410         // all requests have been issued.
411         var types = {};
412         types[OpenLayers.State.INSERT] = [];
413         types[OpenLayers.State.UPDATE] = [];
414         types[OpenLayers.State.DELETE] = [];
415         var feature, list, requestFeatures = [];
416         for(var i=0, len=features.length; i<len; ++i) {
417             feature = features[i];
418             list = types[feature.state];
419             if(list) {
420                 list.push(feature);
421                 requestFeatures.push(feature); 
422             }
423         }
424         // tally up number of requests
425         var nRequests = (types[OpenLayers.State.INSERT].length > 0 ? 1 : 0) +
426             types[OpenLayers.State.UPDATE].length +
427             types[OpenLayers.State.DELETE].length;
428         
429         // This response will be sent to the final callback after all the others
430         // have been fired.
431         var success = true;
432         var finalResponse = new OpenLayers.Protocol.Response({
433             reqFeatures: requestFeatures        
434         });
435         
436         function insertCallback(response) {
437             var len = response.features ? response.features.length : 0;
438             var fids = new Array(len);
439             for(var i=0; i<len; ++i) {
440                 fids[i] = response.features[i].fid;
441             }   
442             finalResponse.insertIds = fids;
443             callback.apply(this, [response]);
444         }
445  
446         function callback(response) {
447             this.callUserCallback(response, options);
448             success = success && response.success();
449             nResponses++;
450             if (nResponses >= nRequests) {
451                 if (options.callback) {
452                     finalResponse.code = success ? 
453                         OpenLayers.Protocol.Response.SUCCESS :
454                         OpenLayers.Protocol.Response.FAILURE;
455                     options.callback.apply(options.scope, [finalResponse]);
456                 }    
457             }
458         }
459
460         // start issuing requests
461         var queue = types[OpenLayers.State.INSERT];
462         if(queue.length > 0) {
463             resp.push(this.create(
464                 queue, OpenLayers.Util.applyDefaults(
465                     {callback: insertCallback, scope: this}, options.create
466                 )
467             ));
468         }
469         queue = types[OpenLayers.State.UPDATE];
470         for(var i=queue.length-1; i>=0; --i) {
471             resp.push(this.update(
472                 queue[i], OpenLayers.Util.applyDefaults(
473                     {callback: callback, scope: this}, options.update
474                 ))
475             );
476         }
477         queue = types[OpenLayers.State.DELETE];
478         for(var i=queue.length-1; i>=0; --i) {
479             resp.push(this["delete"](
480                 queue[i], OpenLayers.Util.applyDefaults(
481                     {callback: callback, scope: this}, options["delete"]
482                 ))
483             );
484         }
485         return resp;
486     },
487
488     /**
489      * APIMethod: abort
490      * Abort an ongoing request, the response object passed to
491      * this method must come from this HTTP protocol (as a result
492      * of a create, read, update, delete or commit operation).
493      *
494      * Parameters:
495      * response - {<OpenLayers.Protocol.Response>}
496      */
497     abort: function(response) {
498         if (response) {
499             response.priv.abort();
500         }
501     },
502
503     /**
504      * Method: callUserCallback
505      * This method is used from within the commit method each time an
506      *     an HTTP response is received from the server, it is responsible
507      *     for calling the user-supplied callbacks.
508      *
509      * Parameters:
510      * resp - {<OpenLayers.Protocol.Response>}
511      * options - {Object} The map of options passed to the commit call.
512      */
513     callUserCallback: function(resp, options) {
514         var opt = options[resp.requestType];
515         if(opt && opt.callback) {
516             opt.callback.call(opt.scope, resp);
517         }
518     },
519
520     CLASS_NAME: "OpenLayers.Protocol.HTTP" 
521 });