sproutcore 0.9.14 → 0.9.15

Sign up to get free protection for your applications and to get access to all the features.
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
+ }) ;