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.
- data/History.txt +43 -0
- data/Manifest.txt +12 -3
- data/bin/sc-build +19 -3
- data/bin/sc-install +5 -0
- data/bin/sc-remove +5 -0
- data/bin/sc-update +5 -0
- data/frameworks/prototype/prototype.js +267 -230
- data/frameworks/sproutcore/HISTORY +281 -135
- data/frameworks/sproutcore/controllers/array.js +133 -22
- data/frameworks/sproutcore/controllers/collection.js +4 -5
- data/frameworks/sproutcore/controllers/object.js +8 -2
- data/frameworks/sproutcore/core.js +361 -159
- data/frameworks/sproutcore/{foundation → debug}/unittest.js +3 -3
- data/frameworks/sproutcore/english.lproj/detect-browser +1 -1
- data/frameworks/sproutcore/english.lproj/theme.css +2 -2
- data/frameworks/sproutcore/foundation/application.js +6 -1
- data/frameworks/sproutcore/foundation/benchmark.js +37 -11
- data/frameworks/sproutcore/foundation/date.js +1 -1
- data/frameworks/sproutcore/foundation/enumerator.js +105 -0
- data/frameworks/sproutcore/foundation/object.js +19 -20
- data/frameworks/sproutcore/foundation/responder.js +1 -1
- data/frameworks/sproutcore/foundation/set.js +164 -57
- data/frameworks/sproutcore/foundation/string.js +151 -47
- data/frameworks/sproutcore/foundation/utils.js +84 -3
- data/frameworks/sproutcore/lib/collection_view.rb +1 -0
- data/frameworks/sproutcore/license.js +28 -0
- data/frameworks/sproutcore/mixins/array.js +73 -209
- data/frameworks/sproutcore/mixins/delegate_support.js +1 -1
- data/frameworks/sproutcore/mixins/enumerable.js +1006 -0
- data/frameworks/sproutcore/mixins/observable.js +153 -84
- data/frameworks/sproutcore/mixins/selection_support.js +13 -1
- data/frameworks/sproutcore/models/record.js +74 -27
- data/frameworks/sproutcore/models/store.js +7 -3
- data/frameworks/sproutcore/server/rails_server.js +82 -0
- data/frameworks/sproutcore/server/rest_server.js +178 -0
- data/frameworks/sproutcore/{foundation → server}/server.js +101 -48
- data/frameworks/sproutcore/tests/core/guidFor.rhtml +114 -0
- data/frameworks/sproutcore/tests/foundation/array.rhtml +6 -7
- data/frameworks/sproutcore/tests/foundation/set.rhtml +254 -0
- data/frameworks/sproutcore/tests/mixins/enumerable.rhtml +421 -0
- data/frameworks/sproutcore/tests/mixins/observable.rhtml +127 -0
- data/frameworks/sproutcore/tests/models/model.rhtml +23 -22
- data/frameworks/sproutcore/tests/views/collection/incremental_rendering.rhtml +2 -2
- data/frameworks/sproutcore/tests/views/view/clippingFrame.rhtml +112 -109
- data/frameworks/sproutcore/tests/views/view/frame.rhtml +91 -88
- data/frameworks/sproutcore/validators/date.js +1 -7
- data/frameworks/sproutcore/views/collection/collection.js +7 -2
- data/frameworks/sproutcore/views/list_item.js +141 -3
- data/frameworks/sproutcore/views/split.js +14 -11
- data/frameworks/sproutcore/views/view.js +9 -6
- data/lib/sproutcore/build_tools/html_builder.rb +19 -3
- data/lib/sproutcore/build_tools/resource_builder.rb +9 -3
- data/lib/sproutcore/bundle.rb +21 -0
- data/lib/sproutcore/bundle_manifest.rb +64 -20
- data/lib/sproutcore/helpers/capture_helper.rb +2 -2
- data/lib/sproutcore/library.rb +33 -9
- data/lib/sproutcore/merb/bundle_controller.rb +16 -5
- data/lib/sproutcore/version.rb +1 -1
- data/lib/sproutcore/view_helpers.rb +1 -1
- data/{sc-config.rb → sc-config} +5 -2
- 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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
var rev = this._kvo().revision ;
|
91
|
+
automaticallyNotifiesObserversFor: function(key) {
|
92
|
+
return (key === 'balance') ? NO : sc_super() ;
|
93
|
+
},
|
138
94
|
|
139
|
-
|
140
|
-
var
|
141
|
-
if (
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
102
|
+
return balance ;
|
147
103
|
}
|
148
104
|
|
149
|
-
|
150
|
-
|
151
|
-
|
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 &&
|
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
|
-
|
256
|
-
|
227
|
+
var notify = this.automaticallyNotifiesObserversFor(key) ;
|
228
|
+
|
229
|
+
if (notify) this.propertyWillChange(key) ;
|
230
|
+
|
257
231
|
// set the value.
|
258
|
-
if (func &&
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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 =
|
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 =
|
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
|
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.
|
96
|
-
|
97
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
317
|
-
|
318
|
-
|
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
|
-
|
633
|
-
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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
|
+
}) ;
|