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.
Files changed (104) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG +10 -0
  3. data/VERSION.yml +1 -1
  4. data/lib/frameworks/sproutcore/CHANGELOG.md +114 -1
  5. data/lib/frameworks/sproutcore/apps/showcase/views/views_item_view.js +1 -7
  6. data/lib/frameworks/sproutcore/apps/showcase/views/views_list_view.js +9 -9
  7. data/lib/frameworks/sproutcore/frameworks/ajax/system/request.js +167 -5
  8. data/lib/frameworks/sproutcore/frameworks/ajax/system/response.js +24 -8
  9. data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/stack_layout.js +737 -0
  10. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/layout.js +0 -6
  11. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +11 -7
  12. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane_statechart.js +7 -11
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/child_view_layout_protocol.js +8 -3
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/observable_protocol.js +9 -6
  15. data/lib/frameworks/sproutcore/frameworks/{desktop/protocols/responder.js → core_foundation/protocols/responder_protocol.js} +83 -17
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/{sparse_array_delegate.js → sparse_array_delegate_protocol.js} +11 -7
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/protocols/view_transition_protocol.js +11 -6
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/color.js +2 -2
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/page.js +0 -22
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +61 -56
  21. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/main_pane.js +2 -2
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/pane/append_remove.js +3 -3
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/animation.js +63 -39
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/border_frame_test.js +28 -28
  25. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/createLayer.js +10 -4
  26. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layout.js +102 -1
  27. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutDidChange.js +4 -4
  28. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +103 -103
  29. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/replaceAllChildren_test.js +1 -1
  30. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view.js +77 -1
  31. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view_states_test.js +18 -17
  32. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +42 -49
  33. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/animation.js +5 -6
  34. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/enabled.js +16 -5
  35. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout.js +241 -102
  36. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +1 -4
  37. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/manipulation.js +0 -11
  38. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/statechart.js +993 -610
  39. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/theming.js +3 -2
  40. data/lib/frameworks/sproutcore/frameworks/datastore/data_sources/data_source.js +6 -11
  41. data/lib/frameworks/sproutcore/frameworks/datastore/system/nested_store.js +94 -27
  42. data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +133 -53
  43. data/lib/frameworks/sproutcore/frameworks/datastore/system/store.js +30 -35
  44. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/writeAttribute.js +3 -2
  45. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/nested_store/writeDataHash.js +73 -29
  46. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/conflictedStoreKeys_test.js +156 -0
  47. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/dataSourceCallbacks.js +61 -37
  48. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/store/find.js +2 -2
  49. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +68 -39
  50. data/lib/frameworks/sproutcore/frameworks/designer/tests/coders/page.js +1 -2
  51. data/lib/frameworks/sproutcore/frameworks/desktop/panes/panel.js +8 -6
  52. data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +80 -14
  53. data/lib/frameworks/sproutcore/frameworks/desktop/panes/sheet.js +2 -2
  54. data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drag_data_source.js → drag_data_source_protocol.js} +16 -10
  55. data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drag_source.js → drag_source_protocol.js} +28 -26
  56. data/lib/frameworks/sproutcore/frameworks/desktop/protocols/{drop_target.js → drop_target_protocol.js} +73 -75
  57. data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -4
  58. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/panel/ui.js +39 -23
  59. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/collection/mouse.js +120 -97
  60. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/list/rowSizeForContentIndex.js +26 -25
  61. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/scroll/ui.js +3 -3
  62. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/segmented/ui.js +5 -0
  63. data/lib/frameworks/sproutcore/frameworks/desktop/tests/views/split/{dividers.js → dividers_test.js} +38 -38
  64. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +29 -14
  65. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_scroll.js +2 -1
  66. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll_view.js +13 -18
  67. data/lib/frameworks/sproutcore/frameworks/desktop/views/segmented.js +41 -35
  68. data/lib/frameworks/sproutcore/frameworks/desktop/views/slider.js +14 -14
  69. data/lib/frameworks/sproutcore/frameworks/desktop/views/split.js +41 -26
  70. data/lib/frameworks/sproutcore/frameworks/desktop/views/static_content.js +2 -12
  71. data/lib/frameworks/sproutcore/frameworks/foundation/gestures/tap.js +2 -2
  72. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/auto_resize.js +14 -10
  73. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/gesturable.js +104 -63
  74. data/lib/frameworks/sproutcore/frameworks/foundation/protocols/swap_transition_protocol.js +9 -4
  75. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_mixin_tests.js +1 -2
  76. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_resize_test.js +33 -33
  77. data/lib/frameworks/sproutcore/frameworks/foundation/tests/transitions/view_transitions_test.js +5 -5
  78. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/methods.js +0 -4
  79. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/transition_test.js +0 -4
  80. data/lib/frameworks/sproutcore/frameworks/foundation/tests/views/container/ui.js +0 -2
  81. data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +12 -8
  82. data/lib/frameworks/sproutcore/frameworks/media/resources/silence.mp3 +0 -0
  83. data/lib/frameworks/sproutcore/frameworks/media/tests/audio.js +69 -0
  84. data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +1 -0
  85. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
  86. data/lib/frameworks/sproutcore/frameworks/runtime/mixins/observable.js +11 -4
  87. data/lib/frameworks/sproutcore/frameworks/runtime/protocols/mixin_protocol.js +150 -0
  88. data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +447 -137
  89. data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +9 -15
  90. data/lib/frameworks/sproutcore/frameworks/runtime/system/set.js +19 -17
  91. data/lib/frameworks/sproutcore/frameworks/runtime/tests/system/binding.js +188 -16
  92. data/lib/frameworks/sproutcore/frameworks/statechart/system/state.js +1 -1
  93. data/lib/frameworks/sproutcore/frameworks/template_view/panes/template.js +0 -3
  94. data/lib/frameworks/sproutcore/frameworks/template_view/tests/panes/template.js +0 -17
  95. data/lib/frameworks/sproutcore/frameworks/template_view/tests/views/template/collection.js +43 -26
  96. data/lib/frameworks/sproutcore/frameworks/template_view/views/bindable_span.js +9 -2
  97. data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list.css +0 -1
  98. data/lib/frameworks/sproutcore/themes/ace/resources/scroll/scroll.css +3 -0
  99. data/sproutcore.gemspec +3 -3
  100. metadata +19 -17
  101. data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/horizontal_stack_layout.js +0 -465
  102. data/lib/frameworks/sproutcore/frameworks/core_foundation/child_view_layouts/vertical_stack_layout.js +0 -472
  103. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/build.js +0 -87
  104. 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
- if (this._lastTheme === this.get('theme')) { return; }
80
- this._lastTheme = this.get('theme');
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 Local queries, to fetch a remote query you will need to load any data
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#loadQueryResults() to actually set an array of storeKeys that
255
- represent the latest results from the server. This will implicitly also
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
- if (this.get('hasChanges') && this.chainedChanges) {
154
- var chainedChanges = this.chainedChanges,
155
- dataHashes = this.dataHashes,
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.forEach(function(storeKey)
172
- {
173
- if (dataHashes && dataHashes.hasOwnProperty(storeKey))
174
- delete dataHashes[storeKey];
175
- if (revisions && revisions.hasOwnProperty(storeKey))
176
- delete revisions[storeKey];
177
- if (editables) delete editables[storeKey];
178
- if (locks) delete locks[storeKey];
179
- if (statuses && statuses.hasOwnProperty(storeKey))
180
- delete statuses[storeKey];
181
- chainedChanges.remove( storeKey );
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
- for(idx=0;idx<len;idx++) {
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
- changes.add(storeKey);
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, myChanges = this.chainedChanges,len,storeKey;
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, newStatus,
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
- data store. Here is a simple example of a local query:
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 = 'Jonny' AND lastName = 'Cash'"
18
+ conditions: "firstName = 'Johnny' AND lastName = 'Cash'"
19
19
  });
20
20
 
21
- To find all records of your store, that match the query, use find with
22
- the query as an argument:
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 a record array containing all matching records.
27
- To limit the query to a record type of `MyApp.MyModel`,
28
- you can specify the type as a property of the query like this:
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 = 'Jonny' AND lastName = 'Cash'",
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
- It is recommended to limit your query to a record type, since the query will
37
- have to look for matching records in the whole store if no record type
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 should follow, like this:
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 = 'Jonny' AND lastName = 'Cash'",
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
- by writing `'DESC'` behind the property name like in the example above.
50
- If no order is given, or records are equal in respect to a given order,
51
- records will be ordered by their storeKey.
52
-
53
- You can check if a certain record matches the query by calling
54
-
55
- query.contains(record);
56
-
57
- SproutCore Query Language
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: null,
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) == -1; //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) != 1; //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) == 1; //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) != -1; //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 ( grammar[anotherToken].reservedWord
932
- && anotherToken == tokenValue ) {
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 == 'left') return tl.leftType;
1089
- if (side == 'right') return tl.rightType;
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
- || (expectedType('right',p) && !l[p].rightSide);
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 == 'UNKNOWN' ) {
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 == 'OPEN_PAREN' ) openParenthesisStack.push(i);
1172
- if ( l[i].tokenType == 'CLOSE_PAREN' ) removeParenthesesPair(i);
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 == 1) l = l[0];
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] == 'DESC') o[i].descending = true;
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
- var res = storeKeys.sort(function(a, b) {
1388
+ storeKeys.sort(function(a, b) {
1309
1389
  return SC.Query.compareStoreKeys(query, store, a, b);
1310
1390
  });
1311
1391
  }