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