1 // ==========================================================================
  2 // Project:   The M-Project - Mobile HTML5 Application Framework
  3 // Copyright: (c) 2011 panacoda GmbH. All rights reserved.
  4 // Creator:   Dominik
  5 // Date:      26.07.2011
  6 // License:   Dual licensed under the MIT or GPL Version 2 licenses.
  7 //            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
  8 //            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
  9 // ==========================================================================
 10 
 11 m_require('core/utility/logger.js');
 12 
 13 /**
 14  * @class
 15  *
 16  * A data consumer can be called a read-only data provider. It's only job is it to retrieve some data form
 17  * remote services, e.g. a webservice, and to push them into the store.
 18  *
 19  * Note: So far we only support data in JSON format!
 20  *
 21  * @extends M.Object
 22  */
 23 M.DataConsumer = M.Object.extend(
 24 /** @scope M.DataConsumer.prototype */ {
 25 
 26     /**
 27      * The type of this object.
 28      *
 29      * @type String
 30      */
 31     type: 'M.DataConsumer',
 32 
 33     /**
 34      * This property can be used to specify the path to the desired data within
 35      * the response. Simply name the path by concatenating the path parts with
 36      * a '.', e.g.: 'path.to.my.desired.response'.
 37      *
 38      * @type String
 39      */
 40     responsePath: null,
 41 
 42     /**
 43      * This property specifies the used http method for the request. By default
 44      * GET is used.
 45      *
 46      * @type String
 47      */
 48     httpMethod: 'GET',
 49 
 50     /**
 51      * This property can be used to specify whether or not to append any fetched
 52      * data sets to the existing records. If set to NO, the model's records are
 53      * removed whenever the find() method is called.
 54      *
 55      * @type Boolean
 56      */
 57     appendRecords: YES,
 58 
 59     /**
 60      * The urlParams property will be pushed to the url() method of your data
 61      * consumer. This should look like:
 62      *
 63      *   url: function(query, rpp) {
 64      *     return 'http://www.myserver.com/request?query=' + query + '&rpp=' + rpp
 65      *   }
 66      *
 67      * @type String
 68      */
 69     urlParams: null,
 70 
 71     /**
 72      * Use this method within your model to configure the data consumer. Set
 73      * resp. override all the default object's properties, e.g.:
 74      *
 75      *   {
 76      *     urlParams: {
 77      *       query: 'html5',
 78      *       rpp: 10
 79      *     },
 80      *     appendRecords: YES,
 81      *     callbacks: {
 82      *       success: {
 83      *         target: MyApp.MyController,
 84      *         action: 'itWorked'
 85      *       },
 86      *       error: {
 87      *         action: function(e) {
 88      *           console.log(e);
 89      *         }
 90      *       }
 91      *     },
 92      *     map: function(obj) {
 93      *       return {
 94      *         userName: obj.from_user,
 95      *         userImage: obj.profile_image_url,
 96      *         createdAt: obj.created_at,
 97      *         tweet: obj.text
 98      *       };
 99      *     }
100      *   }
101      *
102      * @param {Object} obj The configuration parameters for the data consumer.
103      */
104     configure: function(obj) {
105         return this.extend(obj);
106     },
107 
108     /**
109      * This method is automatically called by the model, if you call the model's
110      * find(). To execute the data consuming processs imply pass along an object
111      * specifying the call's parameters as follows:
112      *
113      * {
114      *   urlParams: {
115      *     query: 'html5',
116      *     rpp: 10
117      *   }
118      * }
119      *
120      * These parameters will automatically be added to the url, using the
121      * url() method of your data consumer.
122      *
123      * Depending on the success/failure of the call, the specified success
124      * resp. error callback will be called.
125      *
126      * @param {Object} obj The options for the call.
127      */
128     find: function(obj) {
129         this.include(obj);
130 
131         var that = this;
132         M.Request.init({
133             url: this.bindToCaller(this, this.url, _.toArray(this.urlParams))(),
134             isJSON: YES,
135             callbacks: {
136                 success: {
137                     target: this,
138                     action: function(data, message, request){
139                         /* if no data was returned, skip this */
140                         if(data) {
141                             /* apply response path */
142                             if(this.responsePath) {
143                                 var responsePath = this.responsePath.split('.');
144                                 _.each(responsePath, function(subPath) {
145                                     data = data[subPath];
146                                 });
147                             }
148 
149                             /* if no data was found inside responsePath, skip */
150                             if(data && !_.isArray(data) || _.isArray(data) && data.length > 0) {
151                                 /* make sure we've got an array */
152                                 if(!_.isArray(data)) {
153                                     data = [data];
154                                 }
155 
156                                 /* apply map function and create a record for all data sets */
157                                 var records = [];
158                                 _.each(data, function(d) {
159                                     var record = obj.model.createRecord(that.map(d));
160                                     records.push(record);
161                                 });
162 
163                                 /* call callback */
164                                 if(this.callbacks && M.EventDispatcher.checkHandler(this.callbacks['success'])) {
165                                     M.EventDispatcher.callHandler(this.callbacks['success'], null, NO, [records]);
166                                 }
167                             } else {
168                                 /* log message, that there were no data sets found in given response path */
169                                 M.Logger.log('There were no data sets found in response path \'' + this.responsePath + '\'.', M.INFO);
170 
171                                 /* call callback */
172                                 if(this.callbacks && M.EventDispatcher.checkHandler(this.callbacks['success'])) {
173                                     M.EventDispatcher.callHandler(this.callbacks['success'], null, NO, [[]]);
174                                 }
175                             }
176                         } else {
177                             /* log message, this there were no data sets returned */
178                             M.Logger.log('There was no data returned for url \'' + this.bindToCaller(this, this.url, _.toArray(this.urlParams))() + '\'.', M.INFO);
179 
180                             /* call callback */
181                             if(this.callbacks && M.EventDispatcher.checkHandler(this.callbacks['success'])) {
182                                 M.EventDispatcher.callHandler(this.callbacks['success'], null, NO, [[]]);
183                             }
184                         }
185                     }
186                 },
187                 error: {
188                     target: this,
189                     action: function(request, message){
190                         /* call callback */
191                         if(this.callbacks && M.EventDispatcher.checkHandler(this.callbacks['error'])) {
192                             M.EventDispatcher.callHandler(this.callbacks['error'], null, NO, message);
193                         }
194                     }
195                 }
196             }
197         }).send();
198     },
199 
200     /**
201      * Override this method within the data consumer's configuration, to map
202      * the response object to your model's properties as follows:
203      *
204      *   map: function(obj) {
205      *       return {
206      *           userName: obj.from_user,
207      *           userImage: obj.profile_image_url,
208      *           createdAt: obj.created_at,
209      *           tweet: obj.text
210      *       };
211      *   }
212      *
213      * @param {Object} obj The response object.
214      * @interface
215      */
216     map: function(obj) {
217         // needs to be implemented by concrete data consumer object
218     },
219 
220     /**
221      * Override this method within the data consumer's configuration, to tell
222      * the component which url to connect to and with which parameters as
223      * follows:
224      *
225      *   url: function(query, rpp) {
226      *     return 'http://www.myserver.com/request?query=' + query + '&rpp=' + rpp
227      *   }
228      *
229      * The parameters passed to this method are defined by the configuration
230      * of your data consumer. See the urlParams property for further information
231      * about that.
232      *
233      * @interface
234      */
235     url: function() {
236         // needs to be implemented by concrete data consumer object
237     }
238 
239 });