1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('data_sources/data_source');
  9 
 10 /** @class
 11 
 12   A cascading data source will actually forward requests onto an array of
 13   additional data sources, stopping when one of the data sources returns YES,
 14   indicating that it handled the request.
 15 
 16   You can use a cascading data source to tie together multiple data sources,
 17   treating them as a single namespace.
 18 
 19   ## Configuring a Cascade Data Source
 20 
 21   You will usually define your cascading data source in your main method after
 22   all the classes you have are loaded.
 23 
 24       MyApp.dataSource = SC.CascadeDataSource.create({
 25         dataSources: "prefs youtube photos".w(),
 26 
 27         prefs:   MyApp.PrefsDataSource.create({ root: "/prefs" }),
 28         youtube: YouTube.YouTubeDataSource.create({ apiKey: "123456" }),
 29         photos:  MyApp.PhotosDataSource.create({ root: "photos" })
 30 
 31       });
 32 
 33       MyApp.store.set('dataSource', MyApp.dataSource);
 34 
 35   Note that the order you define your dataSources property will determine the
 36   order in which requests will cascade from the store.
 37 
 38   Alternatively, you can use a more jQuery-like API for defining your data
 39   sources:
 40 
 41       MyApp.dataSource = SC.CascadeDataSource.create()
 42         .from(MyApp.PrefsDataSource.create({ root: "/prefs" }))
 43         .from(YouTube.YouTubeDataSource.create({ apiKey: "123456" }))
 44         .from(MyApp.PhotosDataSource.create({ root: "photos" }));
 45 
 46       MyApp.store.set('dataSource', MyApp.dataSource);
 47 
 48   In this case, the order you call from() will determine the order the request
 49   will cascade.
 50 
 51   @extends SC.DataSource
 52   @since SproutCore 1.0
 53 */
 54 SC.CascadeDataSource = SC.DataSource.extend(
 55   /** @scope SC.CascadeDataSource.prototype */ {
 56 
 57   /**
 58     The data sources used by the cascade, in the order that they are to be
 59     followed.  Usually when you define the cascade, you will define this
 60     array.
 61 
 62     @type Array
 63   */
 64   dataSources: null,
 65 
 66   /**
 67     Add a data source to the list of sources to use when cascading.  Used to
 68     build the data source cascade effect.
 69 
 70     @param {SC.DataSource} dataSource a data source instance to add.
 71     @returns {SC.CascadeDataSource} receiver
 72   */
 73   from: function(dataSource) {
 74     var dataSources = this.get('dataSources');
 75     if (!dataSources) this.set('dataSources', dataSources = []);
 76     dataSources.push(dataSource);
 77     return this ;
 78   },
 79 
 80   // ..........................................................
 81   // SC.STORE ENTRY POINTS
 82   //
 83 
 84   /** @private - just cascades */
 85   fetch: function(store, query) {
 86     var sources = this.get('dataSources'),
 87         len     = sources ? sources.length : 0,
 88         ret     = NO,
 89         cur, source, idx;
 90 
 91     for(idx=0; (ret !== YES) && idx<len; idx++) {
 92       source = sources.objectAt(idx);
 93       cur = source.fetch ? source.fetch.apply(source, arguments) : NO;
 94       ret = this._handleResponse(ret, cur);
 95     }
 96 
 97     return ret ;
 98   },
 99 
100 
101   /** @private - just cascades */
102   retrieveRecords: function(store, storeKeys, ids) {
103     var sources = this.get('dataSources'),
104         len     = sources ? sources.length : 0,
105         ret     = NO,
106         cur, source, idx;
107 
108     for(idx=0; (ret !== YES) && idx<len; idx++) {
109       source = sources.objectAt(idx);
110       cur = source.retrieveRecords.apply(source, arguments);
111       ret = this._handleResponse(ret, cur);
112     }
113 
114     return ret ;
115   },
116 
117   /** @private - just cascades */
118   commitRecords: function(store, createStoreKeys, updateStoreKeys, destroyStoreKeys, params) {
119     var sources = this.get('dataSources'),
120         len     = sources ? sources.length : 0,
121         ret     = NO,
122         cur, source, idx;
123 
124     for(idx=0; (ret !== YES) && idx<len; idx++) {
125       source = sources.objectAt(idx);
126       cur = source.commitRecords.apply(source, arguments);
127       ret = this._handleResponse(ret, cur);
128     }
129 
130     return ret ;
131   },
132 
133   /** @private - just cascades */
134   cancel: function(store, storeKeys) {
135     var sources = this.get('dataSources'),
136         len     = sources ? sources.length : 0,
137         ret     = NO,
138         cur, source, idx;
139 
140     for(idx=0; (ret !== YES) && idx<len; idx++) {
141       source = sources.objectAt(idx);
142       cur = source.cancel.apply(source, arguments);
143       ret = this._handleResponse(ret, cur);
144     }
145 
146     return ret ;
147   },
148 
149   // ..........................................................
150   // INTERNAL SUPPORT
151   //
152 
153   /** @private */
154   init: function() {
155     sc_super();
156 
157     // if a dataSources array is defined, look for any strings and lookup
158     // the same on the data source.  Replace.
159     var sources = this.get('dataSources'),
160         idx     = sources ? sources.get('length') : 0,
161         source;
162     while(--idx>=0) {
163       source = sources[idx];
164       if (SC.typeOf(source) === SC.T_STRING) sources[idx] = this.get(source);
165     }
166 
167   },
168 
169   /** @private - Determine the proper return value. */
170   _handleResponse: function(current, response) {
171     if (response === YES) return YES ;
172     else if (current === NO) return (response === NO) ? NO : SC.MIXED_STATE ;
173     else return SC.MIXED_STATE ;
174   }
175 
176 });
177