sproutcore 1.11.0.rc2 → 1.11.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/CHANGELOG +10 -0
- data/VERSION.yml +1 -1
- data/lib/frameworks/sproutcore/CHANGELOG.md +114 -1
- data/lib/frameworks/sproutcore/apps/showcase/views/views_item_view.js +1 -7
- data/lib/frameworks/sproutcore/apps/showcase/views/views_list_view.js +9 -9
- data/lib/frameworks/sproutcore/frameworks/ajax/system/request.js +167 -5
- data/lib/frameworks/sproutcore/frameworks/ajax/system/response.js +24 -8
- data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/stack_layout.js +737 -0
- data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/layout.js +0 -6
- data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +11 -7
- data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane_statechart.js +7 -11
- data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/child_view_layout_protocol.js +8 -3
- data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/observable_protocol.js +9 -6
- data/lib/frameworks/sproutcore/frameworks/{desktop/protocols/responder.js → core_foundation/protocols/responder_protocol.js} +83 -17
- data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/{sparse_array_delegate.js → sparse_array_delegate_protocol.js} +11 -7
- data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/view_transition_protocol.js +11 -6
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/color.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/page.js +0 -22
- data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +61 -56
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/main_pane.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/append_remove.js +3 -3
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/animation.js +63 -39
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/border_frame_test.js +28 -28
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/createLayer.js +10 -4
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layout.js +102 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutDidChange.js +4 -4
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +103 -103
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/replaceAllChildren_test.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view.js +77 -1
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view_states_test.js +18 -17
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +42 -49
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/animation.js +5 -6
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/enabled.js +16 -5
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout.js +241 -102
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +1 -4
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/manipulation.js +0 -11
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/statechart.js +993 -610
- data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/theming.js +3 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/data_sources/data_source.js +6 -11
- data/lib/frameworks/sproutcore/frameworks/datastore/system/nested_store.js +94 -27
- data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +133 -53
- data/lib/frameworks/sproutcore/frameworks/datastore/system/store.js +30 -35
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/writeAttribute.js +3 -2
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/writeDataHash.js +73 -29
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/conflictedStoreKeys_test.js +156 -0
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/dataSourceCallbacks.js +61 -37
- data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/find.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +68 -39
- data/lib/frameworks/sproutcore/frameworks/designer/tests/coders/page.js +1 -2
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/panel.js +8 -6
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +80 -14
- data/lib/frameworks/sproutcore/frameworks/desktop/panes/sheet.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drag_data_source.js → drag_data_source_protocol.js} +16 -10
- data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drag_source.js → drag_source_protocol.js} +28 -26
- data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drop_target.js → drop_target_protocol.js} +73 -75
- data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -4
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/panel/ui.js +39 -23
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/mouse.js +120 -97
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/rowSizeForContentIndex.js +26 -25
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/ui.js +3 -3
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/segmented/ui.js +5 -0
- data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/split/{dividers.js → dividers_test.js} +38 -38
- data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +29 -14
- data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroll.js +2 -1
- data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll_view.js +13 -18
- data/lib/frameworks/sproutcore/frameworks/desktop/views/segmented.js +41 -35
- data/lib/frameworks/sproutcore/frameworks/desktop/views/slider.js +14 -14
- data/lib/frameworks/sproutcore/frameworks/desktop/views/split.js +41 -26
- data/lib/frameworks/sproutcore/frameworks/desktop/views/static_content.js +2 -12
- data/lib/frameworks/sproutcore/frameworks/foundation/gestures/tap.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/foundation/mixins/auto_resize.js +14 -10
- data/lib/frameworks/sproutcore/frameworks/foundation/mixins/gesturable.js +104 -63
- data/lib/frameworks/sproutcore/frameworks/foundation/protocols/swap_transition_protocol.js +9 -4
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_mixin_tests.js +1 -2
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_resize_test.js +33 -33
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/transitions/view_transitions_test.js +5 -5
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/methods.js +0 -4
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/transition_test.js +0 -4
- data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/ui.js +0 -2
- data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +12 -8
- data/lib/frameworks/sproutcore/frameworks/media/resources/silence.mp3 +0 -0
- data/lib/frameworks/sproutcore/frameworks/media/tests/audio.js +69 -0
- data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +1 -0
- data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
- data/lib/frameworks/sproutcore/frameworks/runtime/mixins/observable.js +11 -4
- data/lib/frameworks/sproutcore/frameworks/runtime/protocols/mixin_protocol.js +150 -0
- data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +447 -137
- data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +9 -15
- data/lib/frameworks/sproutcore/frameworks/runtime/system/set.js +19 -17
- data/lib/frameworks/sproutcore/frameworks/runtime/tests/system/binding.js +188 -16
- data/lib/frameworks/sproutcore/frameworks/statechart/system/state.js +1 -1
- data/lib/frameworks/sproutcore/frameworks/template_view/panes/template.js +0 -3
- data/lib/frameworks/sproutcore/frameworks/template_view/tests/panes/template.js +0 -17
- data/lib/frameworks/sproutcore/frameworks/template_view/tests/views/template/collection.js +43 -26
- data/lib/frameworks/sproutcore/frameworks/template_view/views/bindable_span.js +9 -2
- data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list.css +0 -1
- data/lib/frameworks/sproutcore/themes/ace/resources/scroll/scroll.css +3 -0
- data/sproutcore.gemspec +3 -3
- metadata +19 -17
- data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/horizontal_stack_layout.js +0 -465
- data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/vertical_stack_layout.js +0 -472
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/build.js +0 -87
- data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/build_children.js +0 -89
@@ -76,8 +76,9 @@ SC.View.reopen(
|
|
76
76
|
Also, because
|
77
77
|
*/
|
78
78
|
_sc_view_themeDidChange: function() {
|
79
|
-
|
80
|
-
this._lastTheme
|
79
|
+
var curTheme = this.get('theme');
|
80
|
+
if (this._lastTheme === curTheme) { return; }
|
81
|
+
this._lastTheme = curTheme;
|
81
82
|
|
82
83
|
// invalidate child view base themes, if present
|
83
84
|
var childViews = this.childViews, len = childViews.length, idx;
|
@@ -194,18 +194,15 @@ SC.MIXED_STATE = '__MIXED__';
|
|
194
194
|
|
195
195
|
* `dataSourceDidFetchQuery(query)` — the data source must call this when
|
196
196
|
it has completed fetching any related data for the query. This returns the
|
197
|
-
query results (i.e. the record array) status into a `READY` state.
|
197
|
+
query results (i.e. the record array) status into a `READY` state. If the query is a 'remote'
|
198
|
+
type, the ordered array of store keys representing the results from the server must be passed
|
199
|
+
as a second argument.
|
198
200
|
* `dataSourceDidErrorQuery(query, error)` — the data source should call
|
199
201
|
this if it encounters an error in executing the query. This puts the query
|
200
202
|
results into an `ERROR` state.
|
201
203
|
* `dataSourceDidCancelQuery(query)` — the data source should call this
|
202
204
|
if loading the results is cancelled.
|
203
205
|
|
204
|
-
In addition to these callbacks, the method `loadQueryResults(query, storeKey)`
|
205
|
-
is used by data sources when handling remote queries. This method is similar to
|
206
|
-
`dataSourceDidFetchQuery()`, except that you also provide an array of storeKeys
|
207
|
-
(or a promise to provide store keys) that comprises the result set.
|
208
|
-
|
209
206
|
@extend SC.Object
|
210
207
|
@since SproutCore 1.0
|
211
208
|
*/
|
@@ -248,13 +245,11 @@ SC.DataSource = SC.Object.extend( /** @scope SC.DataSource.prototype */ {
|
|
248
245
|
server instead of from memory. Usually you will only need to use this
|
249
246
|
type of query when loading large amounts of data from the server.
|
250
247
|
|
251
|
-
Like
|
248
|
+
Like local queries, to fetch a remote query you will need to load any data
|
252
249
|
you need to fetch from the server and add the records to the store. Once
|
253
250
|
you are finished loading this data, however, you must also call
|
254
|
-
SC.Store#
|
255
|
-
represent the latest results from the server.
|
256
|
-
call datasSourceDidFetchQuery() so you don't need to call this method
|
257
|
-
yourself.
|
251
|
+
SC.Store#dataSourceDidFetchQuery() with the array of storeKeys that
|
252
|
+
represent the latest results from the server.
|
258
253
|
|
259
254
|
If you want to support incremental loading from the server for remote
|
260
255
|
queries, you can do so by passing a SC.SparseArray instance instead of
|
@@ -133,14 +133,52 @@ SC.NestedStore = SC.Store.extend(
|
|
133
133
|
commitChanges: function(force) {
|
134
134
|
if (this.get('hasChanges')) {
|
135
135
|
var pstore = this.get('parentStore');
|
136
|
-
pstore.commitChangesFromNestedStore(this, this.chainedChanges, force);
|
136
|
+
pstore.commitChangesFromNestedStore(this, this.get('chainedChanges'), force);
|
137
137
|
}
|
138
138
|
|
139
139
|
// clear out custom changes - even if there is nothing to commit.
|
140
140
|
this.reset();
|
141
|
-
return this
|
141
|
+
return this;
|
142
142
|
},
|
143
143
|
|
144
|
+
/**
|
145
|
+
An array of store keys for all conflicting records with the parent store. If there are no
|
146
|
+
conflicting records, this property will be null.
|
147
|
+
|
148
|
+
@readonly
|
149
|
+
@field
|
150
|
+
@type Array
|
151
|
+
@default null
|
152
|
+
*/
|
153
|
+
conflictedStoreKeys: function () {
|
154
|
+
var ret = null;
|
155
|
+
|
156
|
+
if (this.get('hasChanges')) {
|
157
|
+
var pstore = this.get('parentStore'),
|
158
|
+
locks = this.locks,
|
159
|
+
revisions = pstore.revisions;
|
160
|
+
|
161
|
+
if (locks && revisions) {
|
162
|
+
var changes = this.get('chainedChanges');
|
163
|
+
|
164
|
+
for (var i = 0, len = changes.length; i < len; i++) {
|
165
|
+
var storeKey = changes[i],
|
166
|
+
lock = locks[storeKey] || 1,
|
167
|
+
revision = revisions[storeKey] || 1;
|
168
|
+
|
169
|
+
// If the same revision for the item does not match the current revision, then someone has
|
170
|
+
// changed the data hash in this store and we have a conflict.
|
171
|
+
if (lock < revision) {
|
172
|
+
if (!ret) ret = [];
|
173
|
+
ret.push(storeKey);
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
return ret;
|
180
|
+
}.property('chainedChanges').cacheable(),
|
181
|
+
|
144
182
|
/**
|
145
183
|
Propagate this store's successful changes to its parent (if exists). At the end, it clears the
|
146
184
|
local, private status of the committed records therefore the method can be called several times
|
@@ -150,37 +188,41 @@ SC.NestedStore = SC.Store.extend(
|
|
150
188
|
@returns {SC.Store} receiver
|
151
189
|
*/
|
152
190
|
commitSuccessfulChanges: function(force) {
|
153
|
-
|
154
|
-
|
155
|
-
|
191
|
+
var chainedChanges = this.get('chainedChanges');
|
192
|
+
|
193
|
+
if (this.get('hasChanges') && chainedChanges) {
|
194
|
+
var dataHashes = this.dataHashes,
|
156
195
|
revisions = this.revisions,
|
157
196
|
statuses = this.statuses,
|
158
197
|
editables = this.editables,
|
159
198
|
locks = this.locks;
|
199
|
+
|
160
200
|
var successfulChanges = chainedChanges.filter( function(storeKey) {
|
161
201
|
var state = this.readStatus(storeKey);
|
162
202
|
|
163
|
-
return state===SC.Record.READY_CLEAN || state===SC.Record.DESTROYED_CLEAN;
|
203
|
+
return state === SC.Record.READY_CLEAN || state === SC.Record.DESTROYED_CLEAN;
|
164
204
|
}, this );
|
205
|
+
|
165
206
|
var pstore = this.get('parentStore');
|
166
207
|
|
167
208
|
pstore.commitChangesFromNestedStore(this, successfulChanges, force);
|
168
209
|
|
169
210
|
// remove the local status so these records that have been successfully committed on the server
|
170
211
|
// are no longer retrieved from this nested store but from the parent
|
171
|
-
successfulChanges.
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
if (revisions && revisions.hasOwnProperty(storeKey))
|
176
|
-
|
177
|
-
if (
|
178
|
-
if (
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
}, this );
|
212
|
+
for (var i = 0, len = successfulChanges.get('length'); i < len; i++) {
|
213
|
+
var storeKey = successfulChanges.objectAt(i);
|
214
|
+
|
215
|
+
if (dataHashes && dataHashes.hasOwnProperty(storeKey)) { delete dataHashes[storeKey]; }
|
216
|
+
if (revisions && revisions.hasOwnProperty(storeKey)) { delete revisions[storeKey]; }
|
217
|
+
if (editables) { delete editables[storeKey]; }
|
218
|
+
if (locks) { delete locks[storeKey]; }
|
219
|
+
if (statuses && statuses.hasOwnProperty(storeKey)) { delete statuses[storeKey]; }
|
220
|
+
|
221
|
+
chainedChanges.remove(storeKey);
|
222
|
+
}
|
183
223
|
|
224
|
+
// Indicate that chainedChanges has changed.
|
225
|
+
if (successfulChanges.length > 0) { this.notifyPropertyChange('chainedChanges'); }
|
184
226
|
}
|
185
227
|
|
186
228
|
return this;
|
@@ -234,7 +276,6 @@ SC.NestedStore = SC.Store.extend(
|
|
234
276
|
Resets a store's data hash contents to match its parent.
|
235
277
|
*/
|
236
278
|
reset: function() {
|
237
|
-
var nRecords, nr, sk;
|
238
279
|
// requires a pstore to reset
|
239
280
|
var parentStore = this.get('parentStore');
|
240
281
|
if (!parentStore) throw SC.Store.NO_PARENT_STORE_ERROR;
|
@@ -249,12 +290,11 @@ SC.NestedStore = SC.Store.extend(
|
|
249
290
|
this.parentRecords = parentStore.parentRecords ? SC.beget(parentStore.parentRecords) : {};
|
250
291
|
|
251
292
|
// also, reset private temporary objects
|
293
|
+
this.set('hasChanges', false);
|
252
294
|
this.chainedChanges = this.locks = this.editables = null;
|
253
295
|
this.changelog = null ;
|
254
296
|
|
255
297
|
// TODO: Notify record instances
|
256
|
-
|
257
|
-
this.set('hasChanges', NO);
|
258
298
|
},
|
259
299
|
|
260
300
|
/** @private
|
@@ -435,6 +475,9 @@ SC.NestedStore = SC.Store.extend(
|
|
435
475
|
if (!editables) editables = this.editables = [];
|
436
476
|
editables[storeKey] = 1 ; // use number for dense array support
|
437
477
|
|
478
|
+
// propagate the data to the child records
|
479
|
+
this._updateChildRecordHashes(storeKey, hash, status);
|
480
|
+
|
438
481
|
return this ;
|
439
482
|
},
|
440
483
|
|
@@ -465,18 +508,36 @@ SC.NestedStore = SC.Store.extend(
|
|
465
508
|
storeKey = storeKeys;
|
466
509
|
}
|
467
510
|
|
468
|
-
var changes = this.chainedChanges;
|
511
|
+
var changes = this.get('chainedChanges');
|
469
512
|
if (!changes) changes = this.chainedChanges = SC.Set.create();
|
470
513
|
|
471
|
-
|
514
|
+
var that = this,
|
515
|
+
didAddChainedChanges = false;
|
516
|
+
|
517
|
+
for (idx = 0; idx < len; idx++) {
|
472
518
|
if (isArray) storeKey = storeKeys[idx];
|
519
|
+
|
473
520
|
this._lock(storeKey);
|
474
521
|
this.revisions[storeKey] = rev;
|
475
|
-
|
522
|
+
|
523
|
+
if (!changes.contains(storeKey)) {
|
524
|
+
changes.add(storeKey);
|
525
|
+
didAddChainedChanges = true;
|
526
|
+
}
|
527
|
+
|
476
528
|
this._notifyRecordPropertyChange(storeKey, statusOnly, key);
|
529
|
+
|
530
|
+
// notify also the child records
|
531
|
+
this._propagateToChildren(storeKey, function(storeKey){
|
532
|
+
that.dataHashDidChange(storeKey, null, statusOnly, key);
|
533
|
+
});
|
477
534
|
}
|
478
535
|
|
479
536
|
this.setIfChanged('hasChanges', YES);
|
537
|
+
if (didAddChainedChanges) {
|
538
|
+
this.notifyPropertyChange('chainedChanges');
|
539
|
+
}
|
540
|
+
|
480
541
|
return this ;
|
481
542
|
},
|
482
543
|
|
@@ -492,7 +553,11 @@ SC.NestedStore = SC.Store.extend(
|
|
492
553
|
// save a lock for each store key if it does not have one already
|
493
554
|
// also add each storeKey to my own changes set.
|
494
555
|
var pstore = this.get('parentStore'), psRevisions = pstore.revisions, i;
|
495
|
-
var myLocks = this.locks,
|
556
|
+
var myLocks = this.locks,
|
557
|
+
myChanges = this.get('chainedChanges'),
|
558
|
+
len,
|
559
|
+
storeKey;
|
560
|
+
|
496
561
|
if (!myLocks) myLocks = this.locks = [];
|
497
562
|
if (!myChanges) myChanges = this.chainedChanges = SC.Set.create();
|
498
563
|
|
@@ -504,7 +569,9 @@ SC.NestedStore = SC.Store.extend(
|
|
504
569
|
}
|
505
570
|
|
506
571
|
// Finally, mark store as dirty if we have changes
|
507
|
-
this.setIfChanged('hasChanges', myChanges.get('length')>0);
|
572
|
+
this.setIfChanged('hasChanges', myChanges.get('length') > 0);
|
573
|
+
this.notifyPropertyChange('chainedChanges');
|
574
|
+
|
508
575
|
this.flush();
|
509
576
|
|
510
577
|
return this ;
|
@@ -533,7 +600,7 @@ SC.NestedStore = SC.Store.extend(
|
|
533
600
|
because that can disconnect us from upper and/or lower stores.
|
534
601
|
*/
|
535
602
|
retrieveRecords: function(recordTypes, ids, storeKeys, isRefresh, callbacks) {
|
536
|
-
var pstore = this.get('parentStore'), idx, storeKey,
|
603
|
+
var pstore = this.get('parentStore'), idx, storeKey,
|
537
604
|
len = (!storeKeys) ? ids.length : storeKeys.length,
|
538
605
|
K = SC.Record, status;
|
539
606
|
|
@@ -11,51 +11,114 @@ sc_require('models/record');
|
|
11
11
|
/**
|
12
12
|
@class
|
13
13
|
|
14
|
-
This class permits you to perform queries on your data store or a remote
|
15
|
-
|
14
|
+
This class permits you to perform queries on your data store or a remote data store. Here is a
|
15
|
+
simple example of a *local* (see below) query,
|
16
16
|
|
17
17
|
query = SC.Query.create({
|
18
|
-
conditions: "firstName = '
|
18
|
+
conditions: "firstName = 'Johnny' AND lastName = 'Cash'"
|
19
19
|
});
|
20
20
|
|
21
|
-
|
22
|
-
the query
|
21
|
+
This query, when used with the store, will return all records which have record attributes of
|
22
|
+
`firstName` equal to 'Johnny' and `lastName` equal to 'Cash'. To use the query with the store
|
23
|
+
simple pass it to `find` like so,
|
23
24
|
|
24
25
|
records = MyApp.store.find(query);
|
25
26
|
|
26
|
-
`records` will be
|
27
|
-
|
28
|
-
|
27
|
+
In this example, `records` will be an `SC.RecordArray` array containing all matching records. The
|
28
|
+
amazing feature of record arrays backed by *local* queries is that they update automatically
|
29
|
+
whenever the records in the store change. This means that once we've run the query once, any time
|
30
|
+
data is loaded or unloaded from the store, the results of the query (i.e. `records`) will
|
31
|
+
update automatically. This allows for truly powerful and dynamic uses, such as having a list of
|
32
|
+
filtered results that continually updates instantly as data pages in or out in the background.
|
33
|
+
|
34
|
+
To limit the query to a record type of `MyApp.MyModel`, you can specify the type as a property of
|
35
|
+
the query like this,
|
29
36
|
|
30
37
|
query = SC.Query.create({
|
31
|
-
conditions: "firstName = '
|
38
|
+
conditions: "firstName = 'Johnny' AND lastName = 'Cash'",
|
32
39
|
recordType: MyApp.MyModel
|
33
40
|
});
|
34
41
|
|
35
|
-
Calling `find()` like above will now return only records of type MyApp.MyModel.
|
36
|
-
|
37
|
-
|
38
|
-
is given.
|
42
|
+
Calling `find()` like above will now return only records of type MyApp.MyModel. It is recommended
|
43
|
+
to limit your query to a record type, since the query will have to look for matching records in
|
44
|
+
the whole store if no record type is given.
|
39
45
|
|
40
|
-
You can give an order, which the resulting records
|
46
|
+
You can also give an order for *local* queries, which the resulting records will use,
|
41
47
|
|
42
48
|
query = SC.Query.create({
|
43
|
-
conditions: "firstName = '
|
49
|
+
conditions: "firstName = 'Johnny' AND lastName = 'Cash'",
|
44
50
|
recordType: MyApp.MyModel,
|
45
51
|
orderBy: "lastName, year DESC"
|
46
52
|
});
|
47
53
|
|
48
|
-
The default order direction is ascending. You can change it to descending
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
The default order direction is ascending. You can change it to descending by writing `'DESC'`
|
55
|
+
behind the property name as was done in the example above. If no order is given, or records are
|
56
|
+
equal in respect to a given order, records will be ordered by their storeKeys which increment
|
57
|
+
depending on load order.
|
58
|
+
|
59
|
+
Note, you can check if a certain record matches the query by calling `query.contains(record)`.
|
60
|
+
|
61
|
+
## Local vs. Remote Queries
|
62
|
+
|
63
|
+
The default type for queries is 'local', but there is another type we can use called 'remote'.
|
64
|
+
The distinction between local and remote queries is a common tripping point for new SproutCore
|
65
|
+
developers, but hopefully the following description helps keep it clear. The terms local and
|
66
|
+
remote refer to the *location of the data where the query is performed and by whom*. A local query
|
67
|
+
will be run by the client against the store of data within the client, while a remote query will
|
68
|
+
be run on by the server against some remote store of data. This seems simple enough, but it can
|
69
|
+
lead to a few misconceptions.
|
70
|
+
|
71
|
+
The first misconception is that local queries don't ever result in a call to a server. This is
|
72
|
+
not the case; when a local query is first used with the store it will generally result in a call
|
73
|
+
to the server, but whether or not it does depends on your store's data source. Keep this in mind,
|
74
|
+
local queries *only run against the data loaded in the client's store*. If the client store is
|
75
|
+
empty, the results of the query will be empty even though there may be thousands of matching
|
76
|
+
records on a server somewhere. That's why when a query is first used, the store will look for a
|
77
|
+
data source that implements the `fetch(store, query)` method. Your data source can then decide
|
78
|
+
whether additional data should be loaded into the store first in order to better fulfill the
|
79
|
+
query.
|
80
|
+
|
81
|
+
This is entirely up to your client/server implementation, but a common use case is to have a
|
82
|
+
general query trigger the load of a large set of data and then any more specific queries will only
|
83
|
+
run against what was already loaded. For more details, @see SC.DataSource.prototype.fetch. So to
|
84
|
+
recap, local queries are passed to the data source fetch method the first time they are used so
|
85
|
+
that the data source has a chance to load additional data that the query may need.
|
86
|
+
|
87
|
+
Once we get past the first misconception; local queries are actually pretty easy to understand and
|
88
|
+
to work with. We run the queries, get the resulting record array and watch as the results almost
|
89
|
+
magically update as the data in the client changes. Local queries are the default type, and
|
90
|
+
typically we will use local queries almost exclusively in SproutCore apps. So why do we have a
|
91
|
+
remote type?
|
92
|
+
|
93
|
+
In a previous paragraph, we considered how a local query would be empty if the local store was
|
94
|
+
empty even though there may be thousands of matching records on a server. Well what if there were
|
95
|
+
millions of records on the server? When dealing with extremely large datasets, it's not feasible
|
96
|
+
to load all of the records into the client so that the client can run a query against them. This
|
97
|
+
is the role of the 'remote' type. Remote queries are not actually "run" by the client at all,
|
98
|
+
which is the next misconception; you cannot run a remote query.
|
99
|
+
|
100
|
+
This misconception is another way of saying that you can't set conditions or order on a remote
|
101
|
+
query. The 'remote' SC.Query type is simply a reflection of some database query that was run
|
102
|
+
against a data store somewhere outside of the client. For example, say we want to still find all
|
103
|
+
the records on the server with `firstName` equal to 'Johnny' and `lastName` equal to 'Cash', but
|
104
|
+
now there are millions of records. This type of query is best left to a MySQL or other database on
|
105
|
+
a server and thus the server will have exposed an API endpoint that will return the results of
|
106
|
+
such a search when passed some search terms.
|
107
|
+
|
108
|
+
Again, this is entirely up to your client/server configuration, but the way it is handled by the
|
109
|
+
data source is nearly identical to how local queries will be handled. In both situations, the
|
110
|
+
data source is passed the query the first time it is run and when the data source is done it calls
|
111
|
+
the same method on the store, `dataSourceDidFetchQuery`. In both situations too, any data that the
|
112
|
+
data source receives should be loaded into the client store using `loadRecords`. However, because
|
113
|
+
there may be lots of other data in the client store, the 'remote' query must also be told which
|
114
|
+
records pertain to it and in what order, which is done by passing the store keys of the new data
|
115
|
+
also to `dataSourceDidFetchQuery`.
|
116
|
+
|
117
|
+
So to recap, use 'local' queries to filter the data currently in the client store and use 'remote'
|
118
|
+
queries to represent results filtered by a remote server. Both may be used by a data source to
|
119
|
+
load data from a server.
|
120
|
+
|
121
|
+
## SproutCore Query Language
|
59
122
|
|
60
123
|
Features of the query language:
|
61
124
|
|
@@ -86,7 +149,7 @@ sc_require('models/record');
|
|
86
149
|
|
87
150
|
You cannot use both types of parameters in a single query!
|
88
151
|
|
89
|
-
Operators:
|
152
|
+
### Operators:
|
90
153
|
|
91
154
|
- `=`
|
92
155
|
- `!=`
|
@@ -107,7 +170,7 @@ sc_require('models/record');
|
|
107
170
|
of a Model class on its right side, only records of this
|
108
171
|
type will match)
|
109
172
|
|
110
|
-
Boolean Operators:
|
173
|
+
### Boolean Operators:
|
111
174
|
|
112
175
|
- `AND`
|
113
176
|
- `OR`
|
@@ -118,8 +181,7 @@ sc_require('models/record');
|
|
118
181
|
- `(` and `)`
|
119
182
|
|
120
183
|
|
121
|
-
Adding Your Own Query Handlers
|
122
|
-
---
|
184
|
+
## Adding Your Own Query Handlers
|
123
185
|
|
124
186
|
You can extend the query language with your own operators by calling:
|
125
187
|
|
@@ -135,7 +197,7 @@ sc_require('models/record');
|
|
135
197
|
@extends SC.Freezable
|
136
198
|
@since SproutCore 1.0
|
137
199
|
*/
|
138
|
-
|
200
|
+
// TODO: Rename local vs. remote to avoid confusion.
|
139
201
|
SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
140
202
|
/** @scope SC.Query.prototype */ {
|
141
203
|
|
@@ -184,7 +246,7 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
184
246
|
|
185
247
|
@type String | Function
|
186
248
|
*/
|
187
|
-
orderBy:
|
249
|
+
orderBy: null,
|
188
250
|
|
189
251
|
/**
|
190
252
|
The base record type or types for the query. This must be specified to
|
@@ -681,7 +743,7 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
681
743
|
evaluate: function (r,w) {
|
682
744
|
var left = this.leftSide.evaluate(r,w);
|
683
745
|
var right = this.rightSide.evaluate(r,w);
|
684
|
-
return SC.compare(left, right)
|
746
|
+
return SC.compare(left, right) === -1; //left < right;
|
685
747
|
}
|
686
748
|
},
|
687
749
|
|
@@ -695,7 +757,7 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
695
757
|
evaluate: function (r,w) {
|
696
758
|
var left = this.leftSide.evaluate(r,w);
|
697
759
|
var right = this.rightSide.evaluate(r,w);
|
698
|
-
return SC.compare(left, right)
|
760
|
+
return SC.compare(left, right) !== 1; //left <= right;
|
699
761
|
}
|
700
762
|
},
|
701
763
|
|
@@ -709,7 +771,7 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
709
771
|
evaluate: function (r,w) {
|
710
772
|
var left = this.leftSide.evaluate(r,w);
|
711
773
|
var right = this.rightSide.evaluate(r,w);
|
712
|
-
return SC.compare(left, right)
|
774
|
+
return SC.compare(left, right) === 1; //left > right;
|
713
775
|
}
|
714
776
|
},
|
715
777
|
|
@@ -723,7 +785,7 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
723
785
|
evaluate: function (r,w) {
|
724
786
|
var left = this.leftSide.evaluate(r,w);
|
725
787
|
var right = this.rightSide.evaluate(r,w);
|
726
|
-
return SC.compare(left, right)
|
788
|
+
return SC.compare(left, right) !== -1; //left >= right;
|
727
789
|
}
|
728
790
|
},
|
729
791
|
|
@@ -773,7 +835,7 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
773
835
|
if (allType !== SC.T_ARRAY) all = all.toArray();
|
774
836
|
var found = false;
|
775
837
|
var i = 0;
|
776
|
-
while ( found===false && i<all.length ) {
|
838
|
+
while ( found === false && i < all.length ) {
|
777
839
|
if ( value == all[i] ) found = true;
|
778
840
|
i++;
|
779
841
|
}
|
@@ -901,14 +963,12 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
901
963
|
c = null,
|
902
964
|
t = null,
|
903
965
|
token = null,
|
904
|
-
tokenType = null,
|
905
966
|
currentToken = null,
|
906
967
|
currentTokenType = null,
|
907
968
|
currentTokenValue = null,
|
908
969
|
currentDelimiter = null,
|
909
970
|
endOfString = false,
|
910
971
|
endOfToken = false,
|
911
|
-
belongsToToken = false,
|
912
972
|
skipThisCharacter = false,
|
913
973
|
rememberCount = {};
|
914
974
|
|
@@ -928,8 +988,8 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
928
988
|
// reserved words
|
929
989
|
if ( !t.delimited ) {
|
930
990
|
for ( var anotherToken in grammar ) {
|
931
|
-
if (
|
932
|
-
|
991
|
+
if (grammar[anotherToken].reservedWord &&
|
992
|
+
anotherToken == tokenValue ) {
|
933
993
|
tokenType = anotherToken;
|
934
994
|
}
|
935
995
|
}
|
@@ -1085,8 +1145,8 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
1085
1145
|
var p = position;
|
1086
1146
|
var tl = tokenLogic(p);
|
1087
1147
|
if ( !tl ) return false;
|
1088
|
-
if (side
|
1089
|
-
if (side
|
1148
|
+
if (side === 'left') return tl.leftType;
|
1149
|
+
if (side === 'right') return tl.rightType;
|
1090
1150
|
}
|
1091
1151
|
|
1092
1152
|
function evalType (position) {
|
@@ -1110,8 +1170,8 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
1110
1170
|
function tokenIsMissingChilds (position) {
|
1111
1171
|
var p = position;
|
1112
1172
|
if ( p < 0 ) return true;
|
1113
|
-
return (expectedType('left',p) && !l[p].leftSide)
|
1114
|
-
|
1173
|
+
return (expectedType('left',p) && !l[p].leftSide) ||
|
1174
|
+
(expectedType('right',p) && !l[p].rightSide);
|
1115
1175
|
}
|
1116
1176
|
|
1117
1177
|
function typesAreMatching (parent, child) {
|
@@ -1161,15 +1221,15 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
1161
1221
|
|
1162
1222
|
// step through the tokenList
|
1163
1223
|
|
1164
|
-
for (i=0; i < l.length; i++) {
|
1224
|
+
for (i = 0; i < l.length; i++) {
|
1165
1225
|
shouldCheckAgain = false;
|
1166
1226
|
|
1167
|
-
if ( l[i].tokenType
|
1227
|
+
if ( l[i].tokenType === 'UNKNOWN' ) {
|
1168
1228
|
error.push('found unknown token: '+l[i].tokenValue);
|
1169
1229
|
}
|
1170
1230
|
|
1171
|
-
if ( l[i].tokenType
|
1172
|
-
if ( l[i].tokenType
|
1231
|
+
if ( l[i].tokenType === 'OPEN_PAREN' ) openParenthesisStack.push(i);
|
1232
|
+
if ( l[i].tokenType === 'CLOSE_PAREN' ) removeParenthesesPair(i);
|
1173
1233
|
|
1174
1234
|
if ( preceedingTokenCanBeMadeChild(i) ) makeChild(i);
|
1175
1235
|
|
@@ -1183,7 +1243,7 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
1183
1243
|
}
|
1184
1244
|
|
1185
1245
|
// error if tokenList l is not a single token now
|
1186
|
-
if (l.length
|
1246
|
+
if (l.length === 1) l = l[0];
|
1187
1247
|
else error.push('string did not resolve to a single tree');
|
1188
1248
|
|
1189
1249
|
// If we have errors, return an error object.
|
@@ -1193,7 +1253,7 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
1193
1253
|
tree: l,
|
1194
1254
|
// Conform to SC.Error.
|
1195
1255
|
isError: YES,
|
1196
|
-
errorVal: function() { return this.error }
|
1256
|
+
errorVal: function() { return this.error; }
|
1197
1257
|
};
|
1198
1258
|
}
|
1199
1259
|
// Otherwise the token list is now a tree and can be returned.
|
@@ -1224,6 +1284,26 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
1224
1284
|
return orderOp;
|
1225
1285
|
}
|
1226
1286
|
else {
|
1287
|
+
// @if(debug)
|
1288
|
+
// debug mode syntax checks.
|
1289
|
+
// first check is position of ASC or DESC
|
1290
|
+
var ASCpos = orderOp.indexOf("ASC");
|
1291
|
+
var DESCpos = orderOp.indexOf("DESC");
|
1292
|
+
if (ASCpos > -1 || DESCpos > -1) { // if they exist
|
1293
|
+
if (ASCpos > -1 && (ASCpos + 3) !== orderOp.length) {
|
1294
|
+
SC.warn("Developer Warning: You have an orderBy syntax error in a Query, %@: ASC should be in the last position.".fmt(orderOp));
|
1295
|
+
}
|
1296
|
+
if (DESCpos > -1 && (DESCpos + 4) !== orderOp.length) {
|
1297
|
+
SC.warn("Developer Warning: You have an orderBy syntax error in a Query, %@: DESC should be in the last position.".fmt(orderOp));
|
1298
|
+
}
|
1299
|
+
}
|
1300
|
+
|
1301
|
+
// check for improper separation chars
|
1302
|
+
if (orderOp.indexOf(":") > -1 || orderOp.indexOf(":") > -1) {
|
1303
|
+
SC.warn("Developer Warning: You have an orderBy syntax error in a Query, %@: Colons or semicolons should not be used as a separation character.".fmt(orderOp));
|
1304
|
+
}
|
1305
|
+
// @endif
|
1306
|
+
|
1227
1307
|
var o = orderOp.split(',');
|
1228
1308
|
for (var i=0; i < o.length; i++) {
|
1229
1309
|
var p = o[i];
|
@@ -1231,7 +1311,7 @@ SC.Query = SC.Object.extend(SC.Copyable, SC.Freezable,
|
|
1231
1311
|
p = p.replace(/\s+/,',');
|
1232
1312
|
p = p.split(',');
|
1233
1313
|
o[i] = {propertyName: p[0]};
|
1234
|
-
if (p[1] && p[1]
|
1314
|
+
if (p[1] && p[1] === 'DESC') o[i].descending = true;
|
1235
1315
|
}
|
1236
1316
|
|
1237
1317
|
return o;
|
@@ -1305,7 +1385,7 @@ SC.Query.mixin( /** @scope SC.Query */ {
|
|
1305
1385
|
orderStoreKeys: function(storeKeys, query, store) {
|
1306
1386
|
// apply the sort if there is one
|
1307
1387
|
if (storeKeys) {
|
1308
|
-
|
1388
|
+
storeKeys.sort(function(a, b) {
|
1309
1389
|
return SC.Query.compareStoreKeys(query, store, a, b);
|
1310
1390
|
});
|
1311
1391
|
}
|