sproutcore 0.9.14 → 0.9.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/History.txt +43 -0
  2. data/Manifest.txt +12 -3
  3. data/bin/sc-build +19 -3
  4. data/bin/sc-install +5 -0
  5. data/bin/sc-remove +5 -0
  6. data/bin/sc-update +5 -0
  7. data/frameworks/prototype/prototype.js +267 -230
  8. data/frameworks/sproutcore/HISTORY +281 -135
  9. data/frameworks/sproutcore/controllers/array.js +133 -22
  10. data/frameworks/sproutcore/controllers/collection.js +4 -5
  11. data/frameworks/sproutcore/controllers/object.js +8 -2
  12. data/frameworks/sproutcore/core.js +361 -159
  13. data/frameworks/sproutcore/{foundation → debug}/unittest.js +3 -3
  14. data/frameworks/sproutcore/english.lproj/detect-browser +1 -1
  15. data/frameworks/sproutcore/english.lproj/theme.css +2 -2
  16. data/frameworks/sproutcore/foundation/application.js +6 -1
  17. data/frameworks/sproutcore/foundation/benchmark.js +37 -11
  18. data/frameworks/sproutcore/foundation/date.js +1 -1
  19. data/frameworks/sproutcore/foundation/enumerator.js +105 -0
  20. data/frameworks/sproutcore/foundation/object.js +19 -20
  21. data/frameworks/sproutcore/foundation/responder.js +1 -1
  22. data/frameworks/sproutcore/foundation/set.js +164 -57
  23. data/frameworks/sproutcore/foundation/string.js +151 -47
  24. data/frameworks/sproutcore/foundation/utils.js +84 -3
  25. data/frameworks/sproutcore/lib/collection_view.rb +1 -0
  26. data/frameworks/sproutcore/license.js +28 -0
  27. data/frameworks/sproutcore/mixins/array.js +73 -209
  28. data/frameworks/sproutcore/mixins/delegate_support.js +1 -1
  29. data/frameworks/sproutcore/mixins/enumerable.js +1006 -0
  30. data/frameworks/sproutcore/mixins/observable.js +153 -84
  31. data/frameworks/sproutcore/mixins/selection_support.js +13 -1
  32. data/frameworks/sproutcore/models/record.js +74 -27
  33. data/frameworks/sproutcore/models/store.js +7 -3
  34. data/frameworks/sproutcore/server/rails_server.js +82 -0
  35. data/frameworks/sproutcore/server/rest_server.js +178 -0
  36. data/frameworks/sproutcore/{foundation → server}/server.js +101 -48
  37. data/frameworks/sproutcore/tests/core/guidFor.rhtml +114 -0
  38. data/frameworks/sproutcore/tests/foundation/array.rhtml +6 -7
  39. data/frameworks/sproutcore/tests/foundation/set.rhtml +254 -0
  40. data/frameworks/sproutcore/tests/mixins/enumerable.rhtml +421 -0
  41. data/frameworks/sproutcore/tests/mixins/observable.rhtml +127 -0
  42. data/frameworks/sproutcore/tests/models/model.rhtml +23 -22
  43. data/frameworks/sproutcore/tests/views/collection/incremental_rendering.rhtml +2 -2
  44. data/frameworks/sproutcore/tests/views/view/clippingFrame.rhtml +112 -109
  45. data/frameworks/sproutcore/tests/views/view/frame.rhtml +91 -88
  46. data/frameworks/sproutcore/validators/date.js +1 -7
  47. data/frameworks/sproutcore/views/collection/collection.js +7 -2
  48. data/frameworks/sproutcore/views/list_item.js +141 -3
  49. data/frameworks/sproutcore/views/split.js +14 -11
  50. data/frameworks/sproutcore/views/view.js +9 -6
  51. data/lib/sproutcore/build_tools/html_builder.rb +19 -3
  52. data/lib/sproutcore/build_tools/resource_builder.rb +9 -3
  53. data/lib/sproutcore/bundle.rb +21 -0
  54. data/lib/sproutcore/bundle_manifest.rb +64 -20
  55. data/lib/sproutcore/helpers/capture_helper.rb +2 -2
  56. data/lib/sproutcore/library.rb +33 -9
  57. data/lib/sproutcore/merb/bundle_controller.rb +16 -5
  58. data/lib/sproutcore/version.rb +1 -1
  59. data/lib/sproutcore/view_helpers.rb +1 -1
  60. data/{sc-config.rb → sc-config} +5 -2
  61. metadata +24 -5
@@ -73,82 +73,55 @@
73
73
  This will call the 'targetAction' method on the targetObject to be called
74
74
  whenever the value of the propertyKey changes.
75
75
 
76
-
77
- */
78
- SC.Observable = {
79
-
80
- /**
81
- Manually add a new binding to an object. This is the same as doing
82
- the more familiar propertyBinding: 'property.path' approach.
83
- */
84
- bind: function(toKey, fromPropertyPath) {
85
-
86
- var r = SC.idt.active ;
87
-
88
- var binding ;
89
- var props = { to: [this, toKey] } ;
90
-
91
- // for strings try to do default relay
92
- var pathType = $type(fromPropertyPath) ;
93
- if (pathType == T_STRING || pathType == T_ARRAY) {
94
- binding = this[toKey + 'BindingDefault'] || SC.Binding.From;
95
- binding = binding(fromPropertyPath) ;
96
- } else binding = fromPropertyPath ;
97
-
98
- // check the 'from' value of the relay. if it starts w/
99
- // '.' || '*' then convert to a local tuple.
100
- var relayFrom = binding.prototype.from ;
101
- if ($type(relayFrom) == T_STRING) switch(relayFrom.slice(0,1)) {
102
- case '*':
103
- case '.':
104
- relayFrom = [this,relayFrom.slice(1,relayFrom.length)];
105
- }
106
-
107
- if(r) bt = new Date().getTime();
108
-
109
- binding = binding.create(props, { from: relayFrom }) ;
110
- this.bindings.push(binding) ;
111
-
112
- if (r) SC.idt.b1_t += (new Date().getTime()) - bt ;
113
-
114
- return binding ;
115
- },
116
76
 
117
- /**
118
- didChangeFor makes it easy for you to verify that you haven't seen any
119
- changed values. You need to use this if your method observes multiple
120
- properties. To use this, call it like this:
77
+ h2. Implementing Manual Change Notifications
121
78
 
122
- if (this.didChangeFor('render','height','width')) {
123
- // DO SOMETHING HERE IF CHANGED.
124
- }
125
- */
126
- didChangeFor: function(context) {
127
- var keys = $A(arguments) ;
128
- context = keys.shift() ;
129
-
130
- var ret = false ;
131
- if (!this._didChangeCache) this._didChangeCache = {} ;
132
- if (!this._didChangeRevisionCache) this._didChangeRevisionCache = {};
79
+ Sometimes you may want to control the rate at which notifications for
80
+ a property are delivered, for example by checking first to make sure
81
+ that the value has changed.
82
+
83
+ To do this, you need to implement a computed property for the property
84
+ you want to change and override automaticallyNotifiesObserversFor().
85
+
86
+ The example below will only notify if the "balance" property value actually
87
+ changes:
88
+
89
+ {{{
133
90
 
134
- var seen = this._didChangeCache[context] || {} ;
135
- var seenRevisions = this._didChangeRevisionCache[context] || {} ;
136
- var loc = keys.length ;
137
- var rev = this._kvo().revision ;
91
+ automaticallyNotifiesObserversFor: function(key) {
92
+ return (key === 'balance') ? NO : sc_super() ;
93
+ },
138
94
 
139
- while(--loc >= 0) {
140
- var key = keys[loc] ;
141
- if (seenRevisions[key] != rev) {
142
- var val = this.get(key) ;
143
- if (seen[key] !== val) ret = true ;
144
- seen[key] = val ;
95
+ balance: function(key, value) {
96
+ var balance = this._balance ;
97
+ if ((value !== undefined) && (balance !== value)) {
98
+ this.propertyWillChange(key) ;
99
+ balance = this._balance = value ;
100
+ this.propertyDidChange(key) ;
145
101
  }
146
- seenRevisions[key] = rev ;
102
+ return balance ;
147
103
  }
148
104
 
149
- this._didChangeCache[context] = seen ;
150
- this._didChangeRevisionCache[context] = seenRevisions ;
151
- return ret ;
105
+ }}}
106
+
107
+ */
108
+ SC.Observable = {
109
+
110
+ /**
111
+ Determines whether observers should be automatically notified of changes
112
+ to a key.
113
+
114
+ If you are manually implementing change notifications for a property, you
115
+ can override this method to return NO for properties you do not want the
116
+ observing system to automatically notify for.
117
+
118
+ The default implementation always returns YES.
119
+
120
+ @param key {String} the key that is changing
121
+ @returns {Boolean} YES if automatic notification should occur.
122
+ */
123
+ automaticallyNotifiesObserversFor: function(key) {
124
+ return YES;
152
125
  },
153
126
 
154
127
  // ..........................................
@@ -196,7 +169,7 @@ SC.Observable = {
196
169
  var ret = this[key] ;
197
170
  if (ret === undefined) {
198
171
  return this.unknownProperty(key) ;
199
- } else if (ret && (ret instanceof Function) && ret.isProperty) {
172
+ } else if (ret && ret.isProperty) {
200
173
  return ret.call(this,key) ;
201
174
  } else return ret ;
202
175
  },
@@ -251,20 +224,107 @@ SC.Observable = {
251
224
  set: function(key, value) {
252
225
  var func = this[key] ;
253
226
  var ret = value ;
254
-
255
- this.propertyWillChange(key) ;
256
-
227
+ var notify = this.automaticallyNotifiesObserversFor(key) ;
228
+
229
+ if (notify) this.propertyWillChange(key) ;
230
+
257
231
  // set the value.
258
- if (func && (func instanceof Function) && (func.isProperty)) {
232
+ if (func && func.isProperty) {
259
233
  ret = func.call(this,key,value) ;
260
234
  } else if (func === undefined) {
261
235
  ret = this.unknownProperty(key,value) ;
262
236
  } else ret = this[key] = value ;
263
-
237
+
264
238
  // post out notifications.
265
- this.propertyDidChange(key, ret) ;
239
+ if (notify) this.propertyDidChange(key, ret) ;
266
240
  return this ;
267
- },
241
+ },
242
+
243
+ // ..........................................
244
+ // OBSERVERS
245
+ //
246
+
247
+
248
+
249
+ // ..........................................
250
+ // BINDINGS
251
+ //
252
+
253
+ /**
254
+ Manually add a new binding to an object. This is the same as doing
255
+ the more familiar propertyBinding: 'property.path' approach.
256
+ */
257
+ bind: function(toKey, fromPropertyPath) {
258
+
259
+ var r = SC.idt.active ;
260
+
261
+ var binding ;
262
+ var props = { to: [this, toKey] } ;
263
+
264
+ // for strings try to do default relay
265
+ var pathType = $type(fromPropertyPath) ;
266
+ if (pathType == T_STRING || pathType == T_ARRAY) {
267
+ binding = this[toKey + 'BindingDefault'] || SC.Binding.From;
268
+ binding = binding(fromPropertyPath) ;
269
+ } else binding = fromPropertyPath ;
270
+
271
+ // check the 'from' value of the relay. if it starts w/
272
+ // '.' || '*' then convert to a local tuple.
273
+ var relayFrom = binding.prototype.from ;
274
+ if ($type(relayFrom) == T_STRING) switch(relayFrom.slice(0,1)) {
275
+ case '*':
276
+ case '.':
277
+ relayFrom = [this,relayFrom.slice(1,relayFrom.length)];
278
+ }
279
+
280
+ if(r) bt = new Date().getTime();
281
+
282
+ binding = binding.create(props, { from: relayFrom }) ;
283
+ this.bindings.push(binding) ;
284
+
285
+ if (r) SC.idt.b1_t += (new Date().getTime()) - bt ;
286
+
287
+ return binding ;
288
+ },
289
+
290
+ /**
291
+ didChangeFor makes it easy for you to verify that you haven't seen any
292
+ changed values. You need to use this if your method observes multiple
293
+ properties. To use this, call it like this:
294
+
295
+ if (this.didChangeFor('render','height','width')) {
296
+ // DO SOMETHING HERE IF CHANGED.
297
+ }
298
+ */
299
+ didChangeFor: function(context) {
300
+ var keys = SC.$A(arguments) ;
301
+ context = keys.shift() ;
302
+
303
+ var ret = false ;
304
+ if (!this._didChangeCache) this._didChangeCache = {} ;
305
+ if (!this._didChangeRevisionCache) this._didChangeRevisionCache = {};
306
+
307
+ var seen = this._didChangeCache[context] || {} ;
308
+ var seenRevisions = this._didChangeRevisionCache[context] || {} ;
309
+ var loc = keys.length ;
310
+ var rev = this._kvo().revision ;
311
+
312
+ while(--loc >= 0) {
313
+ var key = keys[loc] ;
314
+ if (seenRevisions[key] != rev) {
315
+ var val = this.get(key) ;
316
+ if (seen[key] !== val) ret = true ;
317
+ seen[key] = val ;
318
+ }
319
+ seenRevisions[key] = rev ;
320
+ }
321
+
322
+ this._didChangeCache[context] = seen ;
323
+ this._didChangeRevisionCache[context] = seenRevisions ;
324
+ return ret ;
325
+ },
326
+
327
+
268
328
 
269
329
  /**
270
330
  Sets the property only if the passed value is different from the
@@ -314,7 +374,7 @@ SC.Observable = {
314
374
  @returns {Array} Values of property keys.
315
375
  */
316
376
  getEach: function() {
317
- var keys = $A(arguments).flatten() ;
377
+ var keys = SC.$A(arguments).flatten() ;
318
378
  var ret = [];
319
379
  for(var idx=0; idx<keys.length;idx++) {
320
380
  ret[ret.length] = this.getPath(keys[idx]);
@@ -543,6 +603,13 @@ SC.Observable = {
543
603
 
544
604
  // otherwise, bind as a normal property
545
605
  } else {
606
+
607
+ // if you add an observer beginning with '@', then we might need to
608
+ // create or register the property...
609
+ if (this.reducedProperty && (key.charAt(0) === '@')) {
610
+ this.reducedProperty(key, undefined) ; // create if needed...
611
+ }
612
+
546
613
  var observers = kvo.observers[key] = (kvo.observers[key] || []) ;
547
614
  var found = false; var loc = observers.length;
548
615
  while(!found && --loc >= 0) found = (observers[loc] == func) ;
@@ -586,7 +653,7 @@ SC.Observable = {
586
653
  @param propertyNames one or more property names
587
654
  */
588
655
  logProperty: function() {
589
- var props = $A(arguments) ;
656
+ var props = SC.$A(arguments) ;
590
657
  for(var idx=0;idx<props.length; idx++) {
591
658
  var prop = props[idx] ;
592
659
  console.log('%@:%@: '.fmt(this._guid, prop), this.get(prop)) ;
@@ -626,7 +693,7 @@ SC.Observable = {
626
693
  change.
627
694
  */
628
695
  registerDependentKey: function(key) {
629
- var keys = $A(arguments) ;
696
+ var keys = SC.$A(arguments) ;
630
697
  var dependent = keys.shift() ;
631
698
  var kvo = this._kvo() ;
632
699
  for(var loc=0;loc<keys.length;loc++) {
@@ -743,11 +810,13 @@ SC.Observable = {
743
810
 
744
811
  } ;
745
812
 
813
+ SC.mixin(Array.prototype, SC.Observable) ;
814
+
746
815
  // ........................................................................
747
816
  // FUNCTION ENHANCEMENTS
748
817
  //
749
818
  // Enhance function.
750
- Object.extend(Function.prototype,
819
+ SC.mixin(Function.prototype,
751
820
  /** @scope Function.prototype */ {
752
821
 
753
822
  /**
@@ -858,14 +927,14 @@ Object.extend(Function.prototype,
858
927
  @returns {Function} the declared function instance
859
928
  */
860
929
  property: function() {
861
- this.dependentKeys = $A(arguments) ;
930
+ this.dependentKeys = SC.$A(arguments) ;
862
931
  this.isProperty = true; return this;
863
932
  },
864
933
 
865
934
  // Declare that a function should observe an object at the named path. Note
866
935
  // that the path is used only to construct the observation one time.
867
936
  observes: function(propertyPaths) {
868
- this.propertyPaths = $A(arguments);
937
+ this.propertyPaths = SC.$A(arguments);
869
938
  return this;
870
939
  },
871
940
 
@@ -893,7 +962,7 @@ Object.extend(Function.prototype,
893
962
  if (interval === undefined) interval = 1 ;
894
963
  var f = this;
895
964
  if (arguments.length > 2) {
896
- var args =$A(arguments).slice(2,arguments.length);
965
+ var args =SC.$A(arguments).slice(2,arguments.length);
897
966
  args.unshift(target);
898
967
  f = f.bind.apply(f, args) ;
899
968
  }
@@ -143,6 +143,18 @@ SC.SelectionSupport = {
143
143
 
144
144
  @type bool
145
145
  */
146
- allowsEmptySelection: true
146
+ allowsEmptySelection: true,
147
+
148
+ /**
149
+ YES if the receiver currently has a non-zero selection.
150
+
151
+ @property
152
+ @type {Boolean}
153
+ */
154
+ hasSelection: function() {
155
+ var sel = this.get('selection') ;
156
+ return sel && (sel.get('length') > 0) ;
157
+ }.property('selection')
158
+
147
159
  };
148
160
 
@@ -92,9 +92,10 @@ SC.Record = SC.Object.extend(
92
92
  //
93
93
 
94
94
  /**
95
- Set this URL to point to the type of resource this record is. Put a
96
- '%@' where you expect the primaryKey to be inserted to identify the
97
- record.
95
+ Set this URL to point to the type of resource this record is.
96
+
97
+ If you are using SC.Server, then put a '%@' where you expect the
98
+ primaryKey to be inserted to identify the record.
98
99
 
99
100
  @field
100
101
  @type {String}
@@ -111,6 +112,33 @@ SC.Record = SC.Object.extend(
111
112
  */
112
113
  dataSource: SC.Store,
113
114
 
115
+ /**
116
+ The URL where this record can be refreshed. Usually you would send the value
117
+ for this URL from the server in response to requests from Sproutcore.
118
+
119
+ @field
120
+ @type {String}
121
+ */
122
+ refreshURL: null,
123
+
124
+ /**
125
+ The URL where this record can be updated. Usually you would send the value
126
+ for this URL from the server in response to requests from Sproutcore.
127
+
128
+ @field
129
+ @type {String}
130
+ */
131
+ updateURL: null,
132
+
133
+ /**
134
+ The URL where this record can be destroyed. Usually you would send the value
135
+ for this URL from the server in response to requests from Sproutcore.
136
+
137
+ @field
138
+ @type {String}
139
+ */
140
+ destroyURL: null,
141
+
114
142
 
115
143
  init: function()
116
144
  {
@@ -146,7 +174,7 @@ SC.Record = SC.Object.extend(
146
174
  to support server changes. Note that this is used to support both the
147
175
  create and update components of CRUD.
148
176
  */
149
- commit: function() {
177
+ commit: function() {
150
178
  // no longer a new record once changes have been committed.
151
179
  if (this.get('newRecord')) {
152
180
  this.dataSource.createRecords([this]) ;
@@ -183,18 +211,18 @@ SC.Record = SC.Object.extend(
183
211
  @returns {value} the value of the key, or null if it doesn't exist
184
212
  **/
185
213
  readAttribute: function(key) {
186
- if (!this._cachedAttributes) this._cachedAttributes = {} ;
187
- var ret = this._cachedAttributes[key] ;
188
- if (ret === undefined) {
189
- var attr = this._attributes ;
190
- ret = (attr) ? attr[key] : undefined ;
191
- if (ret !== undefined) {
192
- var recordType = this._getRecordType(key+'Type') ;
193
- ret = this._propertyFromAttribute(ret, recordType) ;
194
- }
195
- this._cachedAttributes[key] = ret ;
196
- }
197
- return (ret === undefined) ? null : ret;
214
+ if (!this._cachedAttributes) this._cachedAttributes = {} ;
215
+ var ret = this._cachedAttributes[key] ;
216
+ if (ret === undefined) {
217
+ var attr = this._attributes ;
218
+ ret = (attr) ? attr[key] : undefined ;
219
+ if (ret !== undefined) {
220
+ var recordType = this._getRecordType(key+'Type') ;
221
+ ret = this._propertyFromAttribute(ret, recordType) ;
222
+ }
223
+ this._cachedAttributes[key] = ret ;
224
+ }
225
+ return (ret === undefined) ? null : ret;
198
226
  },
199
227
 
200
228
  /**
@@ -210,9 +238,17 @@ SC.Record = SC.Object.extend(
210
238
  if (!this._attributes) this._attributes = {} ;
211
239
  this._attributes[key] = ret ;
212
240
  if (this._cachedAttributes) delete this._cachedAttributes[key]; // clear cache.
241
+ this.recordDidChange() ;
242
+ return value ;
243
+ },
244
+
245
+ /**
246
+ You can invoke this method anytime you need to make the record as dirty
247
+ and needing a commit to the server.
248
+ */
249
+ recordDidChange: function() {
213
250
  this.incrementProperty('changeCount') ;
214
251
  if (SC.Store) SC.Store.recordDidChange(this) ;
215
- return value ;
216
252
  },
217
253
 
218
254
  /**
@@ -313,10 +349,15 @@ SC.Record = SC.Object.extend(
313
349
 
314
350
  _propertyFromAttribute: function(value,recordType) {
315
351
  if (value && value instanceof Array) {
316
- var that = this;
317
- return value.map(function(v) {
318
- return that._propertyFromAttribute(v,recordType);
319
- }) ;
352
+ var max = value.get('length') ;
353
+ var ret = new Array(max) ;
354
+ for(var idx=0;idx<max;idx++) {
355
+ var v = value.objectAt(idx) ;
356
+ ret[idx] = this._propertyFromAttribute(v, recordType) ;
357
+ }
358
+ ret.ownerRecord = this ;
359
+ return ret ;
360
+
320
361
  } else {
321
362
  var typeConverter = this._pickTypeConverter(recordType) ;
322
363
  if (typeConverter) return typeConverter(value,'in') ;
@@ -462,6 +503,7 @@ SC.Record = SC.Object.extend(
462
503
  if (recValue && recValue.primaryKey) recValue = recValue.get(recValue.primaryKey) ;
463
504
  var stringify = (value instanceof RegExp);
464
505
  if (stringify) {
506
+ if (recValue == null) return false ;
465
507
  return recValue.toString().match(value) ;
466
508
  } else {
467
509
  return recValue==value ;
@@ -624,17 +666,20 @@ SC.Record = SC.Object.extend(
624
666
  }) ;
625
667
 
626
668
  // Class Methods
627
- SC.Record.mixin({
669
+ SC.Record.mixin(
670
+ /** @static SC.Record */ {
628
671
 
629
672
  // Constants for sorting
630
673
  SORT_BEFORE: -1, SORT_AFTER: 1, SORT_SAME: 0,
631
674
 
632
- // Used to find the first object matching the specified conditions. You can pass
633
- // in either a simple guid or one or more hashes of conditions.
675
+ /**
676
+ Used to find the first object matching the specified conditions. You can
677
+ pass in either a simple guid or one or more hashes of conditions.
678
+ */
634
679
  find: function(guid) {
635
680
  var args ;
636
681
  if (typeof(guid) == 'object') {
637
- args = $A(arguments) ;
682
+ args = SC.$A(arguments) ;
638
683
  args.push(this) ;
639
684
  var ret = SC.Store.findRecords.apply(SC.Store,args) ;
640
685
  return (ret && ret.length > 0) ? ret[0] : null ;
@@ -654,7 +699,7 @@ SC.Record.mixin({
654
699
  // Same as find except returns all records matching the passed conditions.
655
700
  findAll: function(filter) {
656
701
  if (!filter) filter = {} ;
657
- args = $A(arguments) ; args.push(this) ; // add type
702
+ args = SC.$A(arguments) ; args.push(this) ; // add type
658
703
  return SC.Store.findRecords.apply(SC.Store,args) ;
659
704
  },
660
705
 
@@ -723,9 +768,11 @@ SC.Record.mixin({
723
768
  SC.Store.addRecord(rec) ;
724
769
  return rec;
725
770
  }
726
-
771
+
727
772
  }) ;
728
773
 
774
+ SC.Record.newObject = SC.Record.newRecord; // clone method
775
+
729
776
  // Built in Type Converters. You can also use an SC.Record.
730
777
  SC.Record.Date = function(value,direction) {
731
778
  if (direction == 'out') {
@@ -61,6 +61,9 @@ SC.Store = SC.Object.create(
61
61
  var pkValue = data[rt.primaryKey()] ;
62
62
  var rec = store.getRecordFor(pkValue,rt,true) ;
63
63
  rec.dataSource = dataSource ;
64
+ if (data.refreshURL) rec.refreshURL = data.refreshURL;
65
+ if (data.updateURL) rec.updateURL = data.updateURL;
66
+ if (data.destroyURL) rec.destroyURL = data.destroyURL;
64
67
  rec.updateAttributes(data, isLoaded, isLoaded) ;
65
68
  if (rec.needsAddToStore) store.addRecord(rec) ;
66
69
  ret.push(rec) ;
@@ -177,7 +180,7 @@ SC.Store = SC.Object.create(
177
180
  recordType. It will AND the results of each condition hash.
178
181
  */
179
182
  findRecords: function() {
180
- var allConditions = $A(arguments) ;
183
+ var allConditions = SC.$A(arguments) ;
181
184
  var recordType = allConditions.pop() ;
182
185
  var guid = recordType._storeKey() ;
183
186
 
@@ -195,8 +198,9 @@ SC.Store = SC.Object.create(
195
198
  }
196
199
  records = ret ;
197
200
  }
198
-
199
- return records ;
201
+
202
+ // clone records...
203
+ return SC.$A(records) ;
200
204
  },
201
205
 
202
206
  // private method used by Record and Store. Returns null if the record does not exist.
@@ -0,0 +1,82 @@
1
+ // ========================================================================
2
+ // SproutCore
3
+ // copyright 2006-2008 Sprout Systems, Inc.
4
+ // ========================================================================
5
+
6
+ require('core') ;
7
+
8
+ /**
9
+ @class
10
+
11
+ This server extends SC.RestServer and leverages Rails feature to protect
12
+ your controller actions from CSRF attacks by using the authenticity token
13
+ from rails in requests from SproutCore. For more information about this
14
+ feature from Rails see the documentation in rails for module
15
+ ActionController::RequestForgeryProtection.
16
+
17
+ To use this server create it like for example:
18
+
19
+ {{{
20
+ Contacts = SC.Object.create({
21
+ server: SC.RailsServer.create({ prefix: ['Contacts'] })
22
+ }) ;
23
+ }}}
24
+
25
+ In order for SproutCore to send the authenticity token in the body of
26
+ requests there is one prerequisite: you must initialize the SproutCore app
27
+ by setting the following two variables:
28
+
29
+ | SC.RAILS_AUTH_TOKEN_NAME | Should be set to the name of the authenticity token |
30
+ | SC.AUTH_TOKEN | Should be set to the value of the authenticity token |
31
+
32
+ Following is a description of how this can be achieved. Stick the following
33
+ code in one of your controllers:
34
+
35
+ {{{
36
+ # Passes the authenticity token for use in javascript
37
+ def auth_token
38
+ respond_to do |wants|
39
+ wants.js do
40
+ if protect_against_forgery?
41
+ render :text => "var SC = SC || {};
42
+ SC.RAILS_AUTH_TOKEN_NAME = '#{request_forgery_protection_token}';
43
+ SC.RAILS_AUTH_TOKEN = '#{form_authenticity_token}';"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ }}}
49
+
50
+ And make it accessible via your rails routes.rb file, for example:
51
+
52
+ {{{
53
+ map.connect 'auth-token.js', :controller => 'application', :action => 'auth_token'
54
+ }}}
55
+
56
+ Copy-and-paste the default index template of SproutCore into your own
57
+ client app. Refer to your new default index template by modifying the
58
+ :layout option in your sc-config file. Lastly, within this default
59
+ index template add a line to call auth-token.js on your rails server.
60
+ For example:
61
+
62
+ {{{
63
+ <script src="/sc/auth-token.js" type="text/javascript"></script>
64
+ }}}
65
+
66
+
67
+ @extends SC.RestServer
68
+ @author Lawrence Pit
69
+ @copyright 2006-2008, Sprout Systems, Inc. and contributors.
70
+ @since SproutCore 1.0
71
+ */
72
+ SC.RailsServer = SC.RestServer.extend({
73
+
74
+ urlFor: function(resource, action, ids, params, method) {
75
+ if (method != 'get' && SC.RAILS_AUTH_TOKEN_NAME) {
76
+ params[SC.RAILS_AUTH_TOKEN_NAME] = SC.RAILS_AUTH_TOKEN;
77
+ }
78
+
79
+ return sc_super();
80
+ }
81
+
82
+ }) ;