embient 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3790 +0,0 @@
1
-
2
- (function(exports) {
3
- // ==========================================================================
4
- // Project: SproutCore Statechart
5
- // Copyright: ©2006-2011 Strobe Inc. and contributors.
6
- // Portions ©2008-2011 Apple Inc. All rights reserved.
7
- // License: Licensed under MIT license (see license.js)
8
- // ==========================================================================
9
-
10
- SC.handleActions = function(func) {
11
- var args = Array.prototype.slice.call(arguments);
12
- // remove func
13
- args.shift();
14
-
15
- func.isActionHandler = YES;
16
- func.actions = args;
17
- return func;
18
- };
19
-
20
- SC.stateObserves = function(func) {
21
- var args = Array.prototype.slice.call(arguments);
22
- // remove func
23
- args.shift();
24
-
25
- func.isStateObserveHandler = YES;
26
- func.args = args;
27
- return func;
28
- };
29
-
30
- })({});
31
-
32
-
33
- (function(exports) {
34
- // ==========================================================================
35
- // Project: SC.Statechart - A Statechart Framework for SproutCore
36
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
37
- // Portions @2011 Apple Inc. All rights reserved.
38
- // License: Licensed under MIT license (see license.js)
39
- // ==========================================================================
40
-
41
- /*globals SC */
42
-
43
- SC.StatechartSequenceMatcher = SC.Object.extend({
44
-
45
- statechartMonitor: null,
46
-
47
- match: null,
48
-
49
- MISMATCH: {},
50
-
51
- begin: function() {
52
- this._stack = [];
53
- this.beginSequence();
54
- this._start = this._stack[0];
55
- return this;
56
- },
57
-
58
- end: function() {
59
- this.endSequence();
60
-
61
- if (this._stack.length > 0) {
62
- throw "can not match sequence. sequence matcher has been left in an invalid state";
63
- }
64
-
65
- var monitor = this.statechartMonitor,
66
- result = this._matchSequence(this._start, 0) === monitor.sequence.length;
67
-
68
- this.set('match', result);
69
-
70
- return result;
71
- },
72
-
73
- entered: function() {
74
- this._addStatesToCurrentGroup('entered', arguments);
75
- return this;
76
- },
77
-
78
- exited: function() {
79
- this._addStatesToCurrentGroup('exited', arguments);
80
- return this;
81
- },
82
-
83
- beginConcurrent: function() {
84
- var group = {
85
- type: 'concurrent',
86
- values: []
87
- };
88
- if (this._peek()) this._peek().values.push(group);
89
- this._stack.push(group);
90
- return this;
91
- },
92
-
93
- endConcurrent: function() {
94
- this._stack.pop();
95
- return this;
96
- },
97
-
98
- beginSequence: function() {
99
- var group = {
100
- type: 'sequence',
101
- values: []
102
- };
103
- if (this._peek()) this._peek().values.push(group);
104
- this._stack.push(group);
105
- return this;
106
- },
107
-
108
- endSequence: function() {
109
- this._stack.pop();
110
- return this;
111
- },
112
-
113
- _peek: function() {
114
- var len = this._stack.length;
115
- return len === 0 ? null : this._stack[len - 1];
116
- },
117
-
118
- _addStatesToCurrentGroup: function(action, states) {
119
- var group = this._peek(), len = states.length, i = 0;
120
- for (; i < len; i += 1) {
121
- group.values.push({ action: action, state: states[i] });
122
- }
123
- },
124
-
125
- _matchSequence: function(sequence, marker) {
126
- var values = sequence.values,
127
- len = values.length,
128
- i = 0, val,
129
- monitor = this.statechartMonitor;
130
-
131
- if (len === 0) return marker;
132
- if (marker > monitor.sequence.length) return this.MISMATCH;
133
-
134
- for (; i < len; i += 1) {
135
- val = values[i];
136
-
137
- if (val.type === 'sequence') {
138
- marker = this._matchSequence(val, marker);
139
- } else if (val.type === 'concurrent') {
140
- marker = this._matchConcurrent(val, marker);
141
- } else if (!this._matchItems(val, monitor.sequence[marker])){
142
- return this.MISMATCH;
143
- } else {
144
- marker += 1;
145
- }
146
-
147
- if (marker === this.MISMATCH) return this.MISMATCH;
148
- }
149
-
150
- return marker;
151
- },
152
-
153
- // A
154
- // B (concurrent [X, Y])
155
- // X
156
- // M
157
- // N
158
- // Y
159
- // O
160
- // P
161
- // C
162
- //
163
- // 0 1 2 3 4 5 6 7 8
164
- // ^ ^
165
- // A B (X M N) (Y O P) C
166
- // ^ ^
167
- // A B (Y O P) (X M N) C
168
-
169
- _matchConcurrent: function(concurrent, marker) {
170
- var values = concurrent.values.slice(0),
171
- len = values.length,
172
- i = 0, val, tempMarker = marker, match = false,
173
- monitor = this.statechartMonitor;
174
-
175
- if (len === 0) return marker;
176
- if (marker > monitor.sequence.length) return this.MISMATCH;
177
-
178
- while (values.length > 0) {
179
- for (i = 0; i < len; i += 1) {
180
- val = values[i];
181
-
182
- if (val.type === 'sequence') {
183
- tempMarker = this._matchSequence(val, marker);
184
- } else if (val.type === 'concurrent') {
185
- tempMarker = this._matchConcurrent(val, marker);
186
- } else if (!this._matchItems(val, monitor.sequence[marker])){
187
- tempMarker = this.MISMATCH;
188
- } else {
189
- tempMarker = marker + 1;
190
- }
191
-
192
- if (tempMarker !== this.MISMATCH) break;
193
- }
194
-
195
- if (tempMarker === this.MISMATCH) return this.MISMATCH;
196
- values.removeAt(i);
197
- len = values.length;
198
- marker = tempMarker;
199
- }
200
-
201
- return marker;
202
- },
203
-
204
- _matchItems: function(matcherItem, monitorItem) {
205
- if (!matcherItem || !monitorItem) return false;
206
-
207
- if (matcherItem.action !== monitorItem.action) {
208
- return false;
209
- }
210
-
211
- if (SC.typeOf(matcherItem.state) === "instance" && matcherItem.state === monitorItem.state) {
212
- return true;
213
- }
214
-
215
- if (matcherItem.state === monitorItem.state.get('stateName')) {
216
- return true;
217
- }
218
-
219
- return false;
220
- }
221
-
222
- });
223
-
224
- })({});
225
-
226
-
227
- (function(exports) {
228
- // ==========================================================================
229
- // Project: SC.Statechart - A Statechart Framework for SproutCore
230
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
231
- // Portions @2011 Apple Inc. All rights reserved.
232
- // License: Licensed under MIT license (see license.js)
233
- // ==========================================================================
234
- /*globals SC */
235
- SC.StatechartMonitor = SC.Object.extend({
236
-
237
- statechart: null,
238
-
239
- sequence: null,
240
-
241
- init: function() {
242
- this.reset();
243
- },
244
-
245
- reset: function() {
246
- this.propertyWillChange('length');
247
- this.sequence = [];
248
- this.propertyDidChange('length');
249
- },
250
-
251
- length: function() {
252
- return this.sequence.length;
253
- }.property(),
254
-
255
- pushEnteredState: function(state) {
256
- this.propertyWillChange('length');
257
- this.sequence.push({ action: 'entered', state: state });
258
- this.propertyDidChange('length');
259
- },
260
-
261
- pushExitedState: function(state) {
262
- this.propertyWillChange('length');
263
- this.sequence.push({ action: 'exited', state: state });
264
- this.propertyDidChange('length');
265
- },
266
-
267
- matchSequence: function() {
268
- return SC.StatechartSequenceMatcher.create({
269
- statechartMonitor: this
270
- });
271
- },
272
-
273
- matchEnteredStates: function() {
274
- var expected = Array.prototype.slice.call(arguments.length === 1 ? arguments[0] : arguments),
275
- actual = this.getPath('statechart.enteredStates'),
276
- matched = 0,
277
- statechart = this.get('statechart');
278
-
279
- if (expected.length !== actual.length) return NO;
280
-
281
- expected.forEach(function(item) {
282
- if (SC.typeOf(item) === "string") item = statechart.getState(item);
283
- if (!item) return;
284
- if (statechart.stateIsEntered(item) && item.get('isEnteredState')) matched += 1;
285
- });
286
-
287
- return matched === actual.length;
288
- },
289
-
290
- toString: function() {
291
- var seq = "",
292
- i = 0,
293
- len = 0,
294
- item = null;
295
-
296
- seq += "[";
297
-
298
- len = this.sequence.length;
299
- for (i = 0; i < len; i += 1) {
300
- item = this.sequence[i];
301
- seq += "%@ %@".fmt(item.action, item.state.get('fullPath'));
302
- if (i < len - 1) seq += ", ";
303
- }
304
-
305
- seq += "]";
306
-
307
- return seq;
308
- }
309
-
310
- });
311
-
312
- })({});
313
-
314
-
315
- (function(exports) {
316
- // ==========================================================================
317
- // Project: SproutCore Statechart
318
- // Copyright: ©2006-2011 Strobe Inc. and contributors.
319
- // Portions ©2008-2011 Apple Inc. All rights reserved.
320
- // License: Licensed under MIT license (see license.js)
321
- // ==========================================================================
322
- })({});
323
-
324
-
325
- (function(exports) {
326
- // ==========================================================================
327
- // Project: SC.Statechart - A Statechart Framework for SproutCore
328
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
329
- // Portions @2011 Apple Inc. All rights reserved.
330
- // License: Licensed under MIT license (see license.js)
331
- // ==========================================================================
332
-
333
- /*globals SC */
334
-
335
- if (SC.EXTEND_PROTOTYPES) {
336
-
337
- /**
338
- Extends the JS Function object with the handleActions method that
339
- will provide more advanced action handling capabilities when constructing
340
- your statechart's states.
341
-
342
- By default, when you add a method to a state, the state will react to
343
- actions that matches a method's name, like so:
344
-
345
- {{{
346
-
347
- state = SC.State.extend({
348
-
349
- // Will be invoked when a action named "foo" is sent to this state
350
- foo: function(action, sender, context) { ... }
351
-
352
- })
353
-
354
- }}}
355
-
356
- In some situations, it may be advantageous to use one method that can react to
357
- multiple actions instead of having multiple methods that essentially all do the
358
- same thing. In order to set a method to handle more than one action you use
359
- the handleActions method which can be supplied a list of string and/or regular
360
- expressions. The following example demonstrates the use of handleActions:
361
-
362
- {{{
363
-
364
- state = SC.State.extend({
365
-
366
- actionHandlerA: function(action, sender, context) {
367
-
368
- }.handleActions('foo', 'bar'),
369
-
370
- actionHandlerB: function(action, sender, context) {
371
-
372
- }.handleActions(/num\d/, 'decimal')
373
-
374
- })
375
-
376
- }}}
377
-
378
- Whenever actions 'foo' and 'bar' are sent to the state, the method actionHandlerA
379
- will be invoked. When there is an action that matches the regular expression
380
- /num\d/ or the action is 'decimal' then actionHandlerB is invoked. In both
381
- cases, the name of the action will be supplied to the action handler.
382
-
383
- It should be noted that the use of regular expressions may impact performance
384
- since that statechart will not be able to fully optimize the action handling logic based
385
- on its use. Therefore the use of regular expression should be used sparingly.
386
-
387
- @param {(String|RegExp)...} args
388
- */
389
- Function.prototype.handleActions = function() {
390
- var args = Array.prototype.slice.call(arguments);
391
- args.unshift(this);
392
- return SC.handleActions.apply(SC, args);
393
- };
394
-
395
- /**
396
- Extends the JS Function object with the stateObserves method that will
397
- create a state observe handler on a given state object.
398
-
399
- Use a stateObserves() instead of the common observes() method when you want a
400
- state to observer changes to some property on the state itself or some other
401
- object.
402
-
403
- Any method on the state that has stateObserves is considered a state observe
404
- handler and behaves just like when you use observes() on a method, but with an
405
- important difference. When you apply stateObserves to a method on a state, those
406
- methods will be active *only* when the state is entered, otherwise those methods
407
- will be inactive. This removes the need for you having to explicitly call
408
- addObserver and removeObserver. As an example:
409
-
410
- {{{
411
-
412
- state = SC.State.extend({
413
-
414
- foo: null,
415
-
416
- user: null,
417
-
418
- observeHandlerA: function(target, key) {
419
-
420
- }.stateObserves('MyApp.someController.status'),
421
-
422
- observeHandlerB: function(target, key) {
423
-
424
- }.stateObserves('foo'),
425
-
426
- observeHandlerC: function(target, key) {
427
-
428
- }.stateObserves('.user.name', '.user.salary')
429
-
430
- })
431
-
432
- }}}
433
-
434
- Above, state has three state observe handlers: observeHandlerA, observeHandlerB, and
435
- observeHandlerC. When state is entered, the state will automatically add itself as
436
- an observer for all of its registered state observe handlers. Therefore when
437
- foo changes, observeHandlerB will be invoked, and when MyApp.someController's status
438
- changes then observeHandlerA will be invoked. The moment that state is exited then
439
- the state will automatically remove itself as an observer for all of its registered
440
- state observe handlers. Therefore none of the state observe handlers will be
441
- invoked until the next time the state is entered.
442
-
443
- @param {String...} args
444
- */
445
- Function.prototype.stateObserves = function() {
446
- var args = Array.prototype.slice.call(arguments);
447
- args.unshift(this);
448
- return SC.stateObserves.apply(SC, args);
449
- };
450
- }
451
-
452
- })({});
453
-
454
-
455
- (function(exports) {
456
- // ==========================================================================
457
- // Project: SproutCore Statechart
458
- // Copyright: ©2006-2011 Strobe Inc. and contributors.
459
- // Portions ©2008-2011 Apple Inc. All rights reserved.
460
- // License: Licensed under MIT license (see license.js)
461
- // ==========================================================================
462
- })({});
463
-
464
-
465
- (function(exports) {
466
- // ==========================================================================
467
- // Project: SC.Statechart - A Statechart Framework for SproutCore
468
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
469
- // Portions @2011 Apple Inc. All rights reserved.
470
- // License: Licensed under MIT license (see license.js)
471
- // ==========================================================================
472
- /*globals SC */
473
- /**
474
- @class
475
-
476
- Represents a call that is intended to be asynchronous. This is
477
- used during a state transition process when either entering or
478
- exiting a state.
479
-
480
- @extends SC.Object
481
- @author Michael Cohen
482
- */
483
- SC.Async = SC.Object.extend(
484
- /** @scope SC.Async.prototype */{
485
-
486
- func: null,
487
-
488
- arg1: null,
489
-
490
- arg2: null,
491
-
492
- /** @private
493
- Called by the statechart
494
- */
495
- tryToPerform: function(state) {
496
- var func = this.get('func'),
497
- arg1 = this.get('arg1'),
498
- arg2 = this.get('arg2'),
499
- funcType = SC.typeOf(func);
500
-
501
- if (funcType === "string") {
502
- SC.tryToPerform(state, func, arg1, arg2);
503
- } else if (funcType === "function") {
504
- func.apply(state, [arg1, arg2]);
505
- }
506
- }
507
-
508
- });
509
-
510
- /**
511
- Singleton
512
- */
513
- SC.Async.reopenClass(/** @scope SC.Async */{
514
-
515
- /**
516
- Call in either a state's enterState or exitState method when you
517
- want a state to perform an asynchronous action, such as an animation.
518
-
519
- Examples:
520
-
521
- {{
522
-
523
- SC.State.extend({
524
-
525
- enterState: function() {
526
- return SC.Async.perform('foo');
527
- },
528
-
529
- exitState: function() {
530
- return SC.Async.perform('bar', 100);
531
- }
532
-
533
- foo: function() { ... },
534
-
535
- bar: function(arg) { ... }
536
-
537
- });
538
-
539
- }}
540
-
541
- @param func {String|Function} the functio to be invoked on a state
542
- @param arg1 Optional. An argument to pass to the given function
543
- @param arg2 Optional. An argument to pass to the given function
544
- @return {SC.Async} a new instance of a SC.Async
545
- */
546
- perform: function(func, arg1, arg2) {
547
- return SC.Async.create({ func: func, arg1: arg1, arg2: arg2 });
548
- }
549
-
550
- });
551
-
552
- })({});
553
-
554
-
555
- (function(exports) {
556
- // ==========================================================================
557
- // Project: SC.Statechart - A Statechart Framework for SproutCore
558
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
559
- // Portions @2011 Apple Inc. All rights reserved.
560
- // License: Licensed under MIT license (see license.js)
561
- // ==========================================================================
562
- /*globals SC */
563
-
564
- var get = SC.get, set = SC.set, getPath = SC.getPath, slice = Array.prototype.slice;
565
-
566
- /**
567
- @class
568
-
569
- Represents a state within a statechart.
570
-
571
- The statechart actively manages all states belonging to it. When a state is created,
572
- it immediately registers itself with it parent states.
573
-
574
- You do not create an instance of a state itself. The statechart manager will go through its
575
- state heirarchy and create the states itself.
576
-
577
- For more information on using statecharts, see SC.StatechartManager.
578
-
579
- @author Michael Cohen
580
- @extends SC.Object
581
- */
582
- SC.State = SC.Object.extend(
583
- /** @lends SC.State.prototype */ {
584
-
585
- // walk like a duck!
586
- isState: true,
587
-
588
- /**
589
- The name of the state
590
-
591
- @property {String}
592
- */
593
- stateName: null,
594
-
595
- /**
596
- This state's parent state. Managed by the statechart
597
-
598
- @property {State}
599
- */
600
- parentState: null,
601
-
602
- /**
603
- This state's history state. Can be null. Managed by the statechart.
604
-
605
- @property {State}
606
- */
607
- historyState: null,
608
-
609
- /**
610
- Used to indicate the initial substate of this state to enter into.
611
-
612
- You assign the value with the name of the state. Upon creation of
613
- the state, the statechart will automatically change the property
614
- to be a corresponding state object
615
-
616
- The substate is only to be this state's immediate substates. If
617
- no initial substate is assigned then this states initial substate
618
- will be an instance of an empty state (SC.EmptyState).
619
-
620
- Note that a statechart's root state must always have an explicity
621
- initial substate value assigned else an error will be thrown.
622
-
623
- @property {String|State}
624
- */
625
- initialSubstate: null,
626
-
627
- /**
628
- Used to indicates if this state's immediate substates are to be
629
- concurrent (orthogonal) to each other.
630
-
631
- @property {Boolean}
632
- */
633
- substatesAreConcurrent: false,
634
-
635
- /**
636
- The immediate substates of this state. Managed by the statechart.
637
-
638
- @property {Array}
639
- */
640
- substates: null,
641
-
642
- /**
643
- The statechart that this state belongs to. Assigned by the owning
644
- statechart.
645
-
646
- @property {Statechart}
647
- */
648
- statechart: null,
649
-
650
- /**
651
- Indicates if this state has been initialized by the statechart
652
-
653
- @propety {Boolean}
654
- */
655
- stateIsInitialized: false,
656
-
657
- /**
658
- An array of this state's current substates. Managed by the statechart
659
-
660
- @propety {Array}
661
- */
662
- currentSubstates: null,
663
-
664
- /**
665
- An array of this state's substates that are currently entered. Managed by
666
- the statechart.
667
-
668
- @property {Array}
669
- */
670
- enteredSubstates: null,
671
-
672
- /**
673
- Can optionally assign what route this state is to represent.
674
-
675
- If assigned then this state will be notified to handle the route when triggered
676
- any time the app's location changes and matches this state's assigned route.
677
- The handler invoked is this state's {@link #routeTriggered} method.
678
-
679
- The value assigned to this property is dependent on the underlying routing
680
- mechanism used by the application. The default routing mechanism is to use
681
- SC.routes.
682
-
683
- @property {String|Hash}
684
-
685
- @see #routeTriggered
686
- @see #location
687
- @see SC.StatechartDelegate
688
- */
689
- representRoute: null,
690
-
691
- /**
692
- Indicates if this state should trace actions. Useful for debugging
693
- purposes. Managed by the statechart.
694
-
695
- @see SC.StatechartManager#trace
696
-
697
- @property {Boolean}
698
- */
699
- trace: function() {
700
- var key = getPath(this, 'statechart.statechartTraceKey');
701
- return getPath(this, 'statechart.%@'.fmt(key));
702
- }.property().cacheable(),
703
-
704
- /**
705
- Indicates who the owner is of this state. If not set on the statechart
706
- then the owner is the statechart, otherwise it is the assigned
707
- object. Managed by the statechart.
708
-
709
- @see SC.StatechartManager#owner
710
-
711
- @property {SC.Object}
712
- */
713
- owner: function() {
714
- var sc = get(this, 'statechart'),
715
- key = sc ? get(sc, 'statechartOwnerKey') : null,
716
- owner = sc ? get(sc, key) : null;
717
- return owner ? owner : sc;
718
- }.property().cacheable(),
719
-
720
- /**
721
- Returns the statechart's assigned delegate. A statechart delegate is one
722
- that adheres to the {@link SC.StatechartDelegate} mixin.
723
-
724
- @property {SC.Object}
725
-
726
- @see SC.StatechartDelegate
727
- */
728
- statechartDelegate: function() {
729
- return this.getPath('statechart.statechartDelegate');
730
- }.property().cacheable(),
731
-
732
- /**
733
- A volatile property used to get and set the app's current location.
734
-
735
- This computed property defers to the the statechart's delegate to
736
- actually update and acquire the app's location.
737
-
738
- Note: Binding for this pariticular case is discouraged since in most
739
- cases we need the location value immediately. If we were to use
740
- bindings then the location value wouldn't be updated until at least
741
- the end of one run loop. It is also advised that the delegate not
742
- have its `statechartUpdateLocationForState` and
743
- `statechartAcquireLocationForState` methods implemented where bindings
744
- are used since they will inadvertenly stall the location value from
745
- propogating immediately.
746
-
747
- @property {String}
748
-
749
- @see SC.StatechartDelegate#statechartUpdateLocationForState
750
- @see SC.StatechartDelegate#statechartAcquireLocationForState
751
- */
752
- location: function(key, value) {
753
- var sc = this.get('statechart'),
754
- del = this.get('statechartDelegate');
755
-
756
- if (value !== undefined) {
757
- del.statechartUpdateLocationForState(sc, value, this);
758
- }
759
-
760
- return del.statechartAcquireLocationForState(sc, this);
761
- }.property(),
762
-
763
- init: function() {
764
- this._registeredActionHandlers = {};
765
- this._registeredStringActionHandlers = {};
766
- this._registeredRegExpActionHandlers = [];
767
- this._registeredStateObserveHandlers = {};
768
- this._isEnteringState = NO;
769
- this._isExitingState = NO;
770
-
771
- // Setting up observes this way is faster then using .observes,
772
- // which adds a noticable increase in initialization time.
773
- var statechart = get(this, 'statechart'),
774
- ownerKey = statechart ? get(statechart, 'statechartOwnerKey') : null,
775
- traceKey = statechart ? get(statechart, 'statechartTraceKey') : null;
776
-
777
- if (statechart) {
778
- statechart.addObserver(ownerKey, this, '_statechartOwnerDidChange');
779
- statechart.addObserver(traceKey, this, '_statechartTraceDidChange');
780
- }
781
- },
782
-
783
- destroy: function() {
784
- var sc = get(this, 'statechart'),
785
- ownerKey = sc ? get(sc, 'statechartOwnerKey') : null,
786
- traceKey = sc ? get(sc, 'statechartTraceKey') : null;
787
-
788
- if (sc) {
789
- SC.removeObserver(sc, ownerKey, this, '_statechartOwnerDidChange');
790
- SC.removeObserver(sc, traceKey, this, '_statechartTraceDidChange');
791
- }
792
-
793
- var substates = get(this, 'substates');
794
- if (substates) {
795
- substates.forEach(function(state) {
796
- state.destroy();
797
- });
798
- }
799
-
800
- this._teardownAllStateObserveHandlers();
801
-
802
- set(this, 'substates', null);
803
- set(this, 'currentSubstates', null);
804
- set(this, 'enteredSubstates', null);
805
- set(this, 'parentState', null);
806
- set(this, 'historyState', null);
807
- set(this, 'initialSubstate', null);
808
- set(this, 'statechart', null);
809
-
810
- this.notifyPropertyChange('trace');
811
- this.notifyPropertyChange('owner');
812
-
813
- this._registeredActionHandlers = null;
814
- this._registeredStringActionHandlers = null;
815
- this._registeredRegExpActionHandlers = null;
816
- this._registeredStateObserveHandlers = null;
817
-
818
- this._super();
819
- },
820
-
821
- /**
822
- Used to initialize this state. To only be called by the owning statechart.
823
- */
824
- initState: function() {
825
- if (get(this, 'stateIsInitialized')) return;
826
-
827
- this._registerWithParentStates();
828
- this._setupRouteHandling();
829
-
830
- var key = null,
831
- value = null,
832
- state = null,
833
- substates = [],
834
- matchedInitialSubstate = NO,
835
- initialSubstate = get(this, 'initialSubstate'),
836
- substatesAreConcurrent = get(this, 'substatesAreConcurrent'),
837
- statechart = get(this, 'statechart'),
838
- i = 0,
839
- len = 0,
840
- valueIsFunc = NO,
841
- historyState = null;
842
-
843
- if (SC.HistoryState.detect(initialSubstate) && initialSubstate.isClass) {
844
- historyState = this.createHistoryState(initialSubstate, { parentState: this, statechart: statechart });
845
- set(this, 'initialSubstate', historyState);
846
-
847
- if (SC.none(get(historyState, 'defaultState'))) {
848
- this.stateLogError("Initial substate is invalid. History state requires the name of a default state to be set");
849
- set(this, 'initialSubstate', null);
850
- historyState = null;
851
- }
852
- }
853
-
854
- // Iterate through all this state's substates, if any, create them, and then initialize
855
- // them. This causes a recursive process.
856
- for (key in this) {
857
- value = this[key];
858
- valueIsFunc = SC.typeOf(value) === "function";
859
-
860
- if (valueIsFunc && value.isActionHandler) {
861
- this._registerActionHandler(key, value);
862
- continue;
863
- }
864
-
865
- if (valueIsFunc && value.isStateObserveHandler) {
866
- this._registerStateObserveHandler(key, value);
867
- continue;
868
- }
869
-
870
- if (valueIsFunc && value.statePlugin) {
871
- value = value.apply(this);
872
- }
873
-
874
- if (SC.State.detect(value) && value.isClass && this[key] !== this.constructor) {
875
- state = this.createSubstate(value, { stateName: key, parentState: this, statechart: statechart });
876
- substates.push(state);
877
- this[key] = state;
878
- state.initState();
879
- if (key === initialSubstate) {
880
- set(this, 'initialSubstate', state);
881
- matchedInitialSubstate = YES;
882
- } else if (historyState && get(historyState, 'defaultState') === key) {
883
- set(historyState, 'defaultState', state);
884
- matchedInitialSubstate = YES;
885
- }
886
- }
887
- }
888
-
889
- if (!SC.none(initialSubstate) && !matchedInitialSubstate) {
890
- this.stateLogError("Unable to set initial substate %@ since it did not match any of state's %@ substates".fmt(initialSubstate, this));
891
- }
892
-
893
- if (substates.length === 0) {
894
- if (!SC.none(initialSubstate)) {
895
- this.stateLogWarning("Unable to make %@ an initial substate since state %@ has no substates".fmt(initialSubstate, this));
896
- }
897
- }
898
- else if (substates.length > 0) {
899
- if (SC.none(initialSubstate) && !substatesAreConcurrent) {
900
- state = this.createEmptyState({ parentState: this, statechart: statechart });
901
- set(this, 'initialSubstate', state);
902
- substates.push(state);
903
- this[get(state, 'stateName')] = state;
904
- state.initState();
905
- this.stateLogWarning("state %@ has no initial substate defined. Will default to using an empty state as initial substate".fmt(this));
906
- }
907
- else if (!SC.none(initialSubstate) && substatesAreConcurrent) {
908
- set(this, 'initialSubstate', null);
909
- this.stateLogWarning("Can not use %@ as initial substate since substates are all concurrent for state %@".fmt(initialSubstate, this));
910
- }
911
- }
912
-
913
- set(this, 'substates', substates);
914
- set(this, 'currentSubstates', []);
915
- set(this, 'enteredSubstates', []);
916
- set(this, 'stateIsInitialized', YES);
917
- },
918
-
919
- /** @private
920
-
921
- Used to bind this state with a route this state is to represent if a route has been assigned.
922
-
923
- When invoked, the method will delegate the actual binding strategy to the statechart delegate
924
- via the delegate's {@link SC.StatechartDelegate#statechartBindStateToRoute} method.
925
-
926
- Note that a state cannot be bound to a route if this state is a concurrent state.
927
-
928
- @see #representRoute
929
- @see SC.StatechartDelegate#statechartBindStateToRoute
930
- */
931
- _setupRouteHandling: function() {
932
- var route = this.get('representRoute'),
933
- sc = this.get('statechart'),
934
- del = this.get('statechartDelegate');
935
-
936
- if (!route) return;
937
-
938
- if (this.get('isConcurrentState')) {
939
- this.stateLogError("State %@ cannot handle route '%@' since state is concurrent".fmt(this, route));
940
- return;
941
- }
942
-
943
- del.statechartBindStateToRoute(sc, this, route, this.routeTriggered);
944
- },
945
-
946
- /**
947
- Main handler that gets triggered whenever the app's location matches this state's assigned
948
- route.
949
-
950
- When invoked the handler will first refer to the statechart delegate to determine if it
951
- should actually handle the route via the delegate's
952
- {@see SC.StatechartDelegate#statechartShouldStateHandleTriggeredRoute} method. If the
953
- delegate allows the handling of the route then the state will continue on with handling
954
- the triggered route by calling the state's {@link #handleTriggeredRoute} method, otherwise
955
- the state will cancel the handling and inform the delegate through the delegate's
956
- {@see SC.StatechartDelegate#statechartStateCancelledHandlingRoute} method.
957
-
958
- The handler will create a state route context ({@link SC.StateRouteContext}) object
959
- that packages information about what is being currently handled. This context object gets
960
- passed along to the delegate's invoked methods as well as the state transition process.
961
-
962
- Note that this method is not intended to be directly called or overridden.
963
-
964
- @see #representRoute
965
- @see SC.StatechartDelegate#statechartShouldStateHandleRoute
966
- @see SC.StatechartDelegate#statechartStateCancelledHandlingRoute
967
- @see #createStateRouteHandlerContext
968
- @see #handleTriggeredRoute
969
- */
970
- routeTriggered: function(params) {
971
- if (this._isEnteringState) return;
972
-
973
- var sc = this.get('statechart'),
974
- del = this.get('statechartDelegate'),
975
- loc = this.get('location');
976
-
977
- var attr = {
978
- state: this,
979
- location: loc,
980
- params: params,
981
- handler: this.routeTriggered
982
- };
983
-
984
- var context = this.createStateRouteHandlerContext(attr);
985
-
986
- if (del.statechartShouldStateHandleTriggeredRoute(sc, this, context)) {
987
- if (this.get('trace') && loc) {
988
- this.stateLogTrace("will handle route '%@'".fmt(loc));
989
- }
990
- this.handleTriggeredRoute(context);
991
- } else {
992
- del.statechartStateCancelledHandlingTriggeredRoute(sc, this, context);
993
- }
994
- },
995
-
996
- /**
997
- Constructs a new instance of a state routing context object.
998
-
999
- @param {Hash} attr attributes to apply to the constructed object
1000
- @return {SC.StateRouteContext}
1001
-
1002
- @see #handleRoute
1003
- */
1004
- createStateRouteHandlerContext: function(attr) {
1005
- return SC.StateRouteHandlerContext.create(attr);
1006
- },
1007
-
1008
- /**
1009
- Invoked by this state's {@link #routeTriggered} method if the state is
1010
- actually allowed to handle the triggered route.
1011
-
1012
- By default the method invokes a state transition to this state.
1013
- */
1014
- handleTriggeredRoute: function(context) {
1015
- this.gotoState(this, context);
1016
- },
1017
-
1018
- /**
1019
- creates a substate for this state
1020
- */
1021
- createSubstate: function(state, attrs) {
1022
- return state.create(attrs);
1023
- },
1024
-
1025
- /**
1026
- Create a history state for this state
1027
- */
1028
- createHistoryState: function(state, attrs) {
1029
- return state.create(attrs);
1030
- },
1031
-
1032
- /**
1033
- Create an empty state for this state's initial substate
1034
- */
1035
- createEmptyState: function(attrs) {
1036
- return SC.EmptyState.create(attrs);
1037
- },
1038
-
1039
- /** @private
1040
-
1041
- Registers action handlers with this state. Action handlers are special
1042
- functions on the state that are intended to handle more than one action. This
1043
- compared to basic functions that only respond to a single action that reflects
1044
- the name of the method.
1045
- */
1046
- _registerActionHandler: function(name, handler) {
1047
- var actions = handler.actions,
1048
- action = null,
1049
- len = actions.length,
1050
- i = 0;
1051
-
1052
- this._registeredActionHandlers[name] = handler;
1053
-
1054
- for (; i < len; i += 1) {
1055
- action = actions[i];
1056
-
1057
- if (SC.typeOf(action) === "string") {
1058
- this._registeredStringActionHandlers[action] = {
1059
- name: name,
1060
- handler: handler
1061
- };
1062
- continue;
1063
- }
1064
-
1065
- if (action instanceof RegExp) {
1066
- this._registeredRegExpActionHandlers.push({
1067
- name: name,
1068
- handler: handler,
1069
- regexp: action
1070
- });
1071
- continue;
1072
- }
1073
-
1074
- this.stateLogError("Invalid action %@ for action handler %@ in state %@".fmt(action, name, this));
1075
- }
1076
- },
1077
-
1078
- /** @private
1079
-
1080
- Registers state observe handlers with this state. State observe handlers behave just like
1081
- when you apply observes() on a method but will only be active when the state is currently
1082
- entered, otherwise the handlers are inactive until the next time the state is entered
1083
- */
1084
- _registerStateObserveHandler: function(name, handler) {
1085
- var i = 0,
1086
- args = handler.args,
1087
- len = args.length,
1088
- arg, validHandlers = YES;
1089
-
1090
- for (; i < len; i += 1) {
1091
- arg = args[i];
1092
- if (SC.typeOf(arg) !== "string" || SC.empty(arg)) {
1093
- this.stateLogError("Invalid argument %@ for state observe handler %@ in state %@".fmt(arg, name, this));
1094
- validHandlers = NO;
1095
- }
1096
- }
1097
-
1098
- if (!validHandlers) return;
1099
-
1100
- this._registeredStateObserveHandlers[name] = handler.args;
1101
- },
1102
-
1103
- /** @private
1104
- Will traverse up through this state's parent states to register
1105
- this state with them.
1106
- */
1107
- _registerWithParentStates: function() {
1108
- this._registerSubstate(this);
1109
- var parent = get(this, 'parentState');
1110
- while (!SC.none(parent)) {
1111
- parent._registerSubstate(this);
1112
- parent = get(parent, 'parentState');
1113
- }
1114
- },
1115
-
1116
- /** @private
1117
- Will register a given state as a substate of this state
1118
- */
1119
- _registerSubstate: function(state) {
1120
- var path = state.pathRelativeTo(this);
1121
- if (SC.none(path)) return;
1122
-
1123
- // Create special private member variables to help
1124
- // keep track of substates and access them.
1125
- if (SC.none(this._registeredSubstatePaths)) {
1126
- this._registeredSubstatePaths = {};
1127
- this._registeredSubstates = [];
1128
- }
1129
-
1130
- this._registeredSubstates.push(state);
1131
-
1132
- // Keep track of states based on their relative path
1133
- // to this state.
1134
- var regPaths = this._registeredSubstatePaths;
1135
- if (regPaths[get(state, 'stateName')] === undefined) {
1136
- regPaths[get(state, 'stateName')] = { __ki_paths__: [] };
1137
- }
1138
-
1139
- var paths = regPaths[get(state, 'stateName')];
1140
- paths[path] = state;
1141
- paths.__ki_paths__.push(path);
1142
- },
1143
-
1144
- /**
1145
- Will generate path for a given state that is relative to this state. It is
1146
- required that the given state is a substate of this state.
1147
-
1148
- If the heirarchy of the given state to this state is the following:
1149
- A > B > C, where A is this state and C is the given state, then the
1150
- relative path generated will be "B.C"
1151
- */
1152
- pathRelativeTo: function(state) {
1153
- var path = get(this, 'stateName'),
1154
- parent = get(this, 'parentState');
1155
-
1156
- while (!SC.none(parent) && parent !== state) {
1157
- path = "%@.%@".fmt(get(parent, 'stateName'), path);
1158
- parent = get(parent, 'parentState');
1159
- }
1160
-
1161
- if (parent !== state && state !== this) {
1162
- this.stateLogError('Can not generate relative path from %@ since it not a parent state of %@'.fmt(state, this));
1163
- return null;
1164
- }
1165
-
1166
- return path;
1167
- },
1168
-
1169
- /**
1170
- Used to get a substate of this state that matches a given value.
1171
-
1172
- If the value is a state object, then the value will be returned if it is indeed
1173
- a substate of this state, otherwise null is returned.
1174
-
1175
- If the given value is a string, then the string is assumed to be a path to a substate.
1176
- The value is then parsed to find the closes match. If there is no match then null
1177
- is returned. If there is more than one match then null is return and an error
1178
- is generated indicating ambiguity of the given value.
1179
-
1180
- Note that when the value is a string, it is assumed to be a path relative to this
1181
- state; not the root state of the statechart.
1182
- */
1183
- getSubstate: function(value) {
1184
- var valueType = SC.typeOf(value);
1185
-
1186
- // If the value is an object then just check if the value is
1187
- // a registered substate of this state, and if so return it.
1188
- if (value instanceof SC.State) {
1189
- return this._registeredSubstates.indexOf(value) > -1 ? value : null;
1190
- }
1191
-
1192
- if (valueType !== "string") {
1193
- this.stateLogError("Can not find matching subtype. value must be an object or string: %@".fmt(value));
1194
- return null;
1195
- }
1196
-
1197
- // The value is a string. Therefore treat the value as a relative path to
1198
- // a substate of this state.
1199
-
1200
- // Extract that last part of the string. Ex. 'foo' => 'foo', 'foo.bar' => 'bar'
1201
- var matches = value.match(/(^|\.)(\w+)$/);
1202
- if (!matches) return null;
1203
-
1204
- // Get all the paths related to the matched value. If no paths then return null.
1205
- var paths = this._registeredSubstatePaths[matches[2]];
1206
- if (SC.none(paths)) return null;
1207
-
1208
- // Do a quick check to see if there is a path that exactly matches the given
1209
- // value, and if so return the corresponding state
1210
- var state = paths[value];
1211
- if (!SC.none(state)) return state;
1212
-
1213
- // No exact match found. If the value given is a basic string with no ".", then check
1214
- // if there is only one path containing that string. If so, return it. If there is
1215
- // more than one path then it is ambiguous as to what state is trying to be reached.
1216
- if (matches[1] === "") {
1217
- if (paths.__ki_paths__.length === 1) {
1218
- state = paths[paths.__ki_paths__[0]];
1219
- } else if (paths.__ki_paths__.length > 1) {
1220
- var msg = 'Can not find substate matching %@ in state %@. Ambiguous with the following: %@';
1221
- this.stateLogError(msg.fmt(value, this, paths.__ki_paths__));
1222
- }
1223
- }
1224
-
1225
- return state;
1226
- },
1227
-
1228
- /**
1229
- Used to go to a state in the statechart either directly from this state if it is a current state,
1230
- or from one of the current states within the same tree of states. To find the current state in the
1231
- same tree, we walk up the hierarchy and look on each parentState for current substates.
1232
- When the first current substate is found, it is chosen to transition from. This approach is much
1233
- better than just choosing the first current state of the statechart, since this can lead to problems
1234
- with transitions between concurrent states.
1235
-
1236
- Note that if the value given is a string, it will be assumed to be a path to a state. The path
1237
- will be relative to the statechart's root state; not relative to this state.
1238
-
1239
- Method can be called in the following ways:
1240
-
1241
- // With one argument
1242
- gotoState(<state>)
1243
-
1244
- // With two arguments
1245
- gotoState(<state>, <hash>)
1246
-
1247
- Where <state> is either a string or a SC.State object and <hash> is a regular JS hash object.
1248
-
1249
- @param state {SC.State|String} the state to go to
1250
- @param context {Hash} Optional. context object that will be supplied to all states that are
1251
- exited and entered during the state transition process
1252
- */
1253
- gotoState: function(state, context) {
1254
- var fromState = null;
1255
-
1256
- if (get(this, 'isCurrentState')) {
1257
- fromState = this;
1258
- } else {
1259
- nextParentState = this;
1260
-
1261
- while(true) {
1262
- if(nextParentState.get('hasCurrentSubstates')) {
1263
- break;
1264
- }
1265
- nextParentState = nextParentState.get('parentState');
1266
- }
1267
-
1268
- fromState = nextParentState.get('currentSubstates')[0];
1269
- }
1270
-
1271
- get(this, 'statechart').gotoState(state, fromState, context);
1272
- },
1273
-
1274
- /**
1275
- Used to go to a given state's history state in the statechart either directly from this state if it
1276
- is a current state or from one of this state's current substates.
1277
-
1278
- Note that if the value given is a string, it will be assumed to be a path to a state. The path
1279
- will be relative to the statechart's root state; not relative to this state.
1280
-
1281
- Method can be called in the following ways:
1282
-
1283
- // With one argument
1284
- gotoHistoryState(<state>)
1285
-
1286
- // With two arguments
1287
- gotoHistoryState(<state>, <boolean | hash>)
1288
-
1289
- // With three arguments
1290
- gotoHistoryState(<state>, <boolean>, <hash>)
1291
-
1292
- Where <state> is either a string or a SC.State object and <hash> is a regular JS hash object.
1293
-
1294
- @param state {SC.State|String} the state whose history state to go to
1295
- @param recusive {Boolean} Optional. Indicates whether to follow history states recusively starting
1296
- from the given state
1297
- @param context {Hash} Optional. context object that will be supplied to all states that are exited
1298
- entered during the state transition process
1299
- */
1300
- gotoHistoryState: function(state, recursive, context) {
1301
- var fromState = null;
1302
-
1303
- if (get(this, 'isCurrentState')) {
1304
- fromState = this;
1305
- } else if (get(this, 'hasCurrentSubstates')) {
1306
- fromState = get(this, 'currentSubstates')[0];
1307
- }
1308
-
1309
- get(this, 'statechart').gotoHistoryState(state, fromState, recursive, context);
1310
- },
1311
-
1312
- /**
1313
- Resumes an active goto state transition process that has been suspended.
1314
- */
1315
- resumeGotoState: function() {
1316
- get(this, 'statechart').resumeGotoState();
1317
- },
1318
-
1319
- /**
1320
- Used to check if a given state is a current substate of this state. Mainly used in cases
1321
- when this state is a concurrent state.
1322
-
1323
- @param state {State|String} either a state object or the name of a state
1324
- @returns {Boolean} true is the given state is a current substate, otherwise false is returned
1325
- */
1326
- stateIsCurrentSubstate: function(state) {
1327
- if (SC.typeOf(state) === "string") state = get(this, 'statechart').getState(state);
1328
- var current = get(this, 'currentSubstates');
1329
- return !!current && current.indexOf(state) >= 0;
1330
- },
1331
-
1332
- /**
1333
- Used to check if a given state is a substate of this state that is currently entered.
1334
-
1335
- @param state {State|String} either a state object of the name of a state
1336
- @returns {Boolean} true if the given state is a entered substate, otherwise false is returned
1337
- */
1338
- stateIsEnteredSubstate: function(state) {
1339
- if (SC.typeOf(state) === "string") state = get(this, 'statechart').getState(state);
1340
- var entered = get(this, 'enteredSubstates');
1341
- return !!entered && entered.indexOf(state) >= 0;
1342
- },
1343
-
1344
- /**
1345
- Indicates if this state is the root state of the statechart.
1346
-
1347
- @property {Boolean}
1348
- */
1349
- isRootState: function() {
1350
- return getPath(this, 'statechart.rootState') === this;
1351
- }.property(),
1352
-
1353
- /**
1354
- Indicates if this state is a current state of the statechart.
1355
-
1356
- @property {Boolean}
1357
- */
1358
- isCurrentState: function() {
1359
- return this.stateIsCurrentSubstate(this);
1360
- }.property('currentSubstates').cacheable(),
1361
-
1362
- /**
1363
- Indicates if this state is a concurrent state
1364
-
1365
- @property {Boolean}
1366
- */
1367
- isConcurrentState: function() {
1368
- return getPath(this, 'parentState.substatesAreConcurrent');
1369
- }.property(),
1370
-
1371
- /**
1372
- Indicates if this state is a currently entered state.
1373
-
1374
- A state is currently entered if during a state transition process the
1375
- state's enterState method was invoked, but only after its exitState method
1376
- was called, if at all.
1377
- */
1378
- isEnteredState: function() {
1379
- return this.stateIsEnteredSubstate(this);
1380
- }.property('enteredSubstates').cacheable(),
1381
-
1382
- /**
1383
- Indicate if this state has any substates
1384
-
1385
- @propety {Boolean}
1386
- */
1387
- hasSubstates: function() {
1388
- return getPath(this, 'substates.length') > 0;
1389
- }.property('substates'),
1390
-
1391
- /**
1392
- Indicates if this state has any current substates
1393
- */
1394
- hasCurrentSubstates: function() {
1395
- var current = get(this, 'currentSubstates');
1396
- return !!current && get(current, 'length') > 0;
1397
- }.property('currentSubstates').cacheable(),
1398
-
1399
- /**
1400
- Indicates if this state has any currently entered substates
1401
- */
1402
- hasEnteredSubstates: function() {
1403
- var entered = get(this, 'enteredSubstates');
1404
- return !!entered && get(entered, 'length') > 0;
1405
- }.property('enteredSubstates').cacheable(),
1406
-
1407
- /**
1408
- Used to re-enter this state. Call this only when the state a current state of
1409
- the statechart.
1410
- */
1411
- reenter: function() {
1412
- var statechart = get(this, 'statechart');
1413
- if (get(this, 'isCurrentState')) {
1414
- statechart.gotoState(this);
1415
- } else {
1416
- SC.Logger.error('Can not re-enter state %@ since it is not a current state in the statechart'.fmt(this));
1417
- }
1418
- },
1419
-
1420
- /**
1421
- Called by the statechart to allow a state to try and handle the given action. If the
1422
- action is handled by the state then YES is returned, otherwise NO.
1423
-
1424
- There is a particular order in how an action is handled by a state:
1425
-
1426
- 1. Basic function whose name matches the action
1427
- 2. Registered action handler that is associated with an action represented as a string
1428
- 3. Registered action handler that is associated with actions matching a regular expression
1429
- 4. The unknownAction function
1430
-
1431
- Use of action handlers that are associated with actions matching a regular expression may
1432
- incur a performance hit, so they should be used sparingly.
1433
-
1434
- The unknownAction function is only invoked if the state has it, otherwise it is skipped. Note that
1435
- you should be careful when using unknownAction since it can be either abused or cause unexpected
1436
- behavior.
1437
-
1438
- Example of a state using all four action handling techniques:
1439
-
1440
- SC.State.extend({
1441
-
1442
- // Basic function handling action 'foo'
1443
- foo: function(arg1, arg2) { ... },
1444
-
1445
- // action handler that handles 'frozen' and 'canuck'
1446
- actionHandlerA: function(action, arg1, arg2) {
1447
- ...
1448
- }.handleActions('frozen', 'canuck'),
1449
-
1450
- // action handler that handles actions matching the regular expression /num\d/
1451
- // ex. num1, num2
1452
- actionHandlerB: function(action, arg1, arg2) {
1453
- ...
1454
- }.handleActions(/num\d/),
1455
-
1456
- // Handle any action that was not handled by some other
1457
- // method on the state
1458
- unknownAction: function(action, arg1, arg2) {
1459
-
1460
- }
1461
-
1462
- });
1463
- */
1464
- tryToHandleAction: function(action, arg1, arg2) {
1465
-
1466
- var trace = get(this, 'trace');
1467
-
1468
- // First check if the name of the action is the same as a registered action handler. If so,
1469
- // then do not handle the action.
1470
- if (this._registeredActionHandlers[action]) {
1471
- this.stateLogWarning("state %@ can not handle action %@ since it is a registered action handler".fmt(this, action));
1472
- return NO;
1473
- }
1474
-
1475
- if (this._registeredStateObserveHandlers[action]) {
1476
- this.stateLogWarning("state %@ can not handle action %@ since it is a registered state observe handler".fmt(this, action));
1477
- return NO;
1478
- }
1479
-
1480
- // Now begin by trying a basic method on the state to respond to the action
1481
- if (SC.typeOf(this[action]) === "function") {
1482
- if (trace) this.stateLogTrace("will handle action %@".fmt(action));
1483
- return (this[action](arg1, arg2) !== NO);
1484
- }
1485
-
1486
- // Try an action handler that is associated with an action represented as a string
1487
- var handler = this._registeredStringActionHandlers[action];
1488
- if (handler) {
1489
- if (trace) this.stateLogTrace("%@ will handle action %@".fmt(handler.name, action));
1490
- return (handler.handler.call(this, action, arg1, arg2) !== NO);
1491
- }
1492
-
1493
- // Try an action handler that is associated with actions matching a regular expression
1494
-
1495
- var len = this._registeredRegExpActionHandlers.length,
1496
- i = 0;
1497
-
1498
- for (; i < len; i += 1) {
1499
- handler = this._registeredRegExpActionHandlers[i];
1500
- if (action.match(handler.regexp)) {
1501
- if (trace) this.stateLogTrace("%@ will handle action %@".fmt(handler.name, action));
1502
- return (handler.handler.call(this, action, arg1, arg2) !== NO);
1503
- }
1504
- }
1505
-
1506
- // Final attempt. If the state has an unknownAction function then invoke it to
1507
- // handle the action
1508
- if (SC.typeOf(this['unknownAction']) === "function") {
1509
- if (trace) this.stateLogTrace("unknownAction will handle action %@".fmt(action));
1510
- return (this.unknownAction(action, arg1, arg2) !== NO);
1511
- }
1512
-
1513
- // Nothing was able to handle the given action for this state
1514
- return NO;
1515
- },
1516
-
1517
- /**
1518
- Called whenever this state is to be entered during a state transition process. This
1519
- is useful when you want the state to perform some initial set up procedures.
1520
-
1521
- If when entering the state you want to perform some kind of asynchronous action, such
1522
- as an animation or fetching remote data, then you need to return an asynchronous
1523
- action, which is done like so:
1524
-
1525
- enterState: function() {
1526
- return this.performAsync('foo');
1527
- }
1528
-
1529
- After returning an action to be performed asynchronously, the statechart will suspend
1530
- the active state transition process. In order to resume the process, you must call
1531
- this state's resumeGotoState method or the statechart's resumeGotoState. If no asynchronous
1532
- action is to be perform, then nothing needs to be returned.
1533
-
1534
- When the enterState method is called, an optional context value may be supplied if
1535
- one was provided to the gotoState method.
1536
-
1537
- @param context {Hash} Optional value if one was supplied to gotoState when invoked
1538
- */
1539
- enterState: function(context) { },
1540
-
1541
- /**
1542
- Notification called just before enterState is invoked.
1543
-
1544
- Note: This is intended to be used by the owning statechart but it can be overridden if
1545
- you need to do something special.
1546
-
1547
- @see #enterState
1548
- */
1549
- stateWillBecomeEntered: function(context) {
1550
- this._isEnteringState = YES;
1551
- },
1552
-
1553
- /**
1554
- Notification called just after enterState is invoked.
1555
-
1556
- Note: This is intended to be used by the owning statechart but it can be overridden if
1557
- you need to do something special.
1558
-
1559
- @see #enterState
1560
- */
1561
- stateDidBecomeEntered: function(context) {
1562
- this._setupAllStateObserveHandlers();
1563
- this._isEnteringState = NO;
1564
- },
1565
-
1566
- /**
1567
- Called whenever this state is to be exited during a state transition process. This is
1568
- useful when you want the state to peform some clean up procedures.
1569
-
1570
- If when exiting the state you want to perform some kind of asynchronous action, such
1571
- as an animation or fetching remote data, then you need to return an asynchronous
1572
- action, which is done like so:
1573
-
1574
- exitState: function() {
1575
- return this.performAsync('foo');
1576
- }
1577
-
1578
- After returning an action to be performed asynchronously, the statechart will suspend
1579
- the active state transition process. In order to resume the process, you must call
1580
- this state's resumeGotoState method or the statechart's resumeGotoState. If no asynchronous
1581
- action is to be perform, then nothing needs to be returned.
1582
-
1583
- When the exitState method is called, an optional context value may be supplied if
1584
- one was provided to the gotoState method.
1585
-
1586
- @param context {Hash} Optional value if one was supplied to gotoState when invoked
1587
- */
1588
- exitState: function(context) { },
1589
-
1590
- /**
1591
- Notification called just before exitState is invoked.
1592
-
1593
- Note: This is intended to be used by the owning statechart but it can be overridden
1594
- if you need to do something special.
1595
-
1596
- @see #exitState
1597
- */
1598
- stateWillBecomeExited: function(context) {
1599
- this._isExitingState = YES;
1600
- this._teardownAllStateObserveHandlers();
1601
- },
1602
-
1603
- /**
1604
- Notification called just after exitState is invoked.
1605
-
1606
- Note: This is intended to be used by the owning statechart but it can be overridden
1607
- if you need to do something special.
1608
-
1609
- @see #exitState
1610
- */
1611
- stateDidBecomeExited: function(context) {
1612
- this._isExitingState = NO;
1613
- },
1614
-
1615
- /** @private
1616
-
1617
- Used to setup all the state observer handlers. Should be done when
1618
- the state has been entered.
1619
- */
1620
- _setupAllStateObserveHandlers: function() {
1621
- this._configureAllStateObserveHandlers('addObserver');
1622
- },
1623
-
1624
- /** @private
1625
-
1626
- Used to teardown all the state observer handlers. Should be done when
1627
- the state is being exited.
1628
- */
1629
- _teardownAllStateObserveHandlers: function() {
1630
- this._configureAllStateObserveHandlers('removeObserver');
1631
- },
1632
-
1633
- /** @private
1634
-
1635
- Primary method used to either add or remove this state as an observer
1636
- based on all the state observe handlers that have been registered with
1637
- this state.
1638
-
1639
- Note: The code to add and remove the state as an observer has been
1640
- taken from the observerable mixin and made slightly more generic. However,
1641
- having this code in two different places is not ideal, but for now this
1642
- will have to do. In the future the code should be refactored so that
1643
- there is one common function that both the observerable mixin and the
1644
- statechart framework use.
1645
- */
1646
- _configureAllStateObserveHandlers: function(action) {
1647
- var key, values, path, observer, i, tuple;
1648
-
1649
- for (key in this._registeredStateObserveHandlers) {
1650
- values = this._registeredStateObserveHandlers[key];
1651
- for (i = 0; i < values.length; i += 1) {
1652
- path = values[i]; observer = key;
1653
- tuple = SC.normalizeTuple(this, path);
1654
- SC[action](tuple[0], tuple[1], this, observer);
1655
- }
1656
- }
1657
- },
1658
-
1659
- /**
1660
- Call when an asynchronous action need to be performed when either entering or exiting
1661
- a state.
1662
-
1663
- @see enterState
1664
- @see exitState
1665
- */
1666
- performAsync: function(func, arg1, arg2) {
1667
- return SC.Async.perform(func, arg1, arg2);
1668
- },
1669
-
1670
- /** @override
1671
-
1672
- Returns YES if this state can respond to the given action, otherwise
1673
- NO is returned
1674
-
1675
- @param action {String} the value to check
1676
- @returns {Boolean}
1677
- */
1678
- respondsToAction: function(action) {
1679
- if (this._registeredActionHandlers[action]) return false;
1680
- if (SC.typeOf(this[action]) === "function") return true;
1681
- if (this._registeredStringActionHandlers[action]) return true;
1682
- if (this._registeredStateObserveHandlers[action]) return false;
1683
-
1684
- var len = this._registeredRegExpActionHandlers.length,
1685
- i = 0,
1686
- handler;
1687
-
1688
- for (; i < len; i += 1) {
1689
- handler = this._registeredRegExpActionHandlers[i];
1690
- if (action.match(handler.regexp)) return true;
1691
- }
1692
-
1693
- return SC.typeOf(this['unknownAction']) === "function";
1694
- },
1695
-
1696
- /**
1697
- Returns the path for this state relative to the statechart's
1698
- root state.
1699
-
1700
- The path is a dot-notation string representing the path from
1701
- this state to the statechart's root state, but without including
1702
- the root state in the path. For instance, if the name of this
1703
- state if "foo" and the parent state's name is "bar" where bar's
1704
- parent state is the root state, then the full path is "bar.foo"
1705
-
1706
- @property {String}
1707
- */
1708
- fullPath: function() {
1709
- var root = getPath(this, 'statechart.rootState');
1710
- if (!root) return get(this, 'stateName');
1711
- return this.pathRelativeTo(root);
1712
- }.property('stateName', 'parentState').cacheable(),
1713
-
1714
- // toString: function() {
1715
- // var className = this._super();
1716
- // return "%@<%@, %@>".fmt(className, get(this, 'fullPath'), SC.guidFor(this));
1717
- // },
1718
-
1719
- /** @private */
1720
- _statechartTraceDidChange: function() {
1721
- this.notifyPropertyChange('trace');
1722
- },
1723
-
1724
- /** @private */
1725
- _statechartOwnerDidChange: function() {
1726
- this.notifyPropertyChange('owner');
1727
- },
1728
-
1729
- /**
1730
- Used to log a state trace message
1731
- */
1732
- stateLogTrace: function(msg) {
1733
- var sc = get(this, 'statechart');
1734
- sc.statechartLogTrace("%@: %@".fmt(this, msg));
1735
- },
1736
-
1737
- /**
1738
- Used to log a state warning message
1739
- */
1740
- stateLogWarning: function(msg) {
1741
- var sc = get(this, 'statechart');
1742
- sc.statechartLogWarning(msg);
1743
- },
1744
-
1745
- /**
1746
- Used to log a state error message
1747
- */
1748
- stateLogError: function(msg) {
1749
- var sc = get(this, 'statechart');
1750
- sc.statechartLogError(msg);
1751
- }
1752
-
1753
- });
1754
-
1755
- /**
1756
- Use this when you want to plug-in a state into a statechart. This is beneficial
1757
- in cases where you split your statechart's states up into multiple files and
1758
- don't want to fuss with the sc_require construct.
1759
-
1760
- Example:
1761
-
1762
- MyApp.statechart = SC.Statechart.create({
1763
- rootState: SC.State.extend({
1764
- initialSubstate: 'a',
1765
- a: SC.State.plugin('path.to.a.state.class'),
1766
- b: SC.State.plugin('path.to.another.state.class')
1767
- })
1768
- });
1769
-
1770
- You can also supply hashes the plugin feature in order to enhance a state or
1771
- implement required functionality:
1772
-
1773
- SomeMixin = { ... };
1774
-
1775
- stateA: SC.State.plugin('path.to.state', SomeMixin, { ... })
1776
-
1777
- @param value {String} property path to a state class
1778
- @param args {Hash,...} Optional. Hash objects to be added to the created state
1779
- */
1780
- SC.State.plugin = function(value) {
1781
- var args = slice.call(arguments); args.shift();
1782
- var func = function() {
1783
- var klass = SC.getPath(window, value);
1784
- if (!klass) {
1785
- console.error('SC.State.plugin: Unable to determine path %@'.fmt(value));
1786
- return undefined;
1787
- }
1788
- if (!klass.isClass || (klass.isInstance && !(klass instanceof SC.State))) {
1789
- console.error('SC.State.plugin: Unable to extend. %@ must be a class extending from SC.State'.fmt(value));
1790
- return undefined;
1791
- }
1792
- return klass.extend.apply(klass, args);
1793
- };
1794
- func.statePlugin = YES;
1795
- return func;
1796
- };
1797
-
1798
- })({});
1799
-
1800
-
1801
- (function(exports) {
1802
- // ==========================================================================
1803
- // Project: SC.Statechart - A Statechart Framework for SproutCore
1804
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
1805
- // Portions @2011 Apple Inc. All rights reserved.
1806
- // License: Licensed under MIT license (see license.js)
1807
- // ==========================================================================
1808
- /*globals SC */
1809
- /**
1810
- The default name given to an empty state
1811
- */
1812
- SC.EMPTY_STATE_NAME = "__EMPTY_STATE__";
1813
-
1814
- /**
1815
- @class
1816
-
1817
- Represents an empty state that gets assigned as a state's initial substate
1818
- if the state does not have an initial substate defined.
1819
-
1820
- @extends SC.State
1821
- */
1822
- SC.EmptyState = SC.State.extend(/** @scope SC.EmptyState.prototype */{
1823
-
1824
- name: SC.EMPTY_STATE_NAME,
1825
-
1826
- enterState: function() {
1827
- var msg = "No initial substate was defined for state %@. Entering default empty state";
1828
- this.stateLogWarning(msg.fmt(this.get('parentState')));
1829
- }
1830
-
1831
- });
1832
-
1833
- })({});
1834
-
1835
-
1836
- (function(exports) {
1837
- // ==========================================================================
1838
- // Project: SC.Statechart - A Statechart Framework for SproutCore
1839
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
1840
- // Portions @2011 Apple Inc. All rights reserved.
1841
- // License: Licensed under MIT license (see license.js)
1842
- // ==========================================================================
1843
-
1844
- /*globals SC */
1845
- /**
1846
- @class
1847
-
1848
- Represents a history state that can be assigned to a SC.State object's
1849
- initialSubstate property.
1850
-
1851
- If a SC.HistoryState object is assigned to a state's initial substate,
1852
- then after a state is entered the statechart will refer to the history
1853
- state object to determine the next course of action. If the state has
1854
- its historyState property assigned then the that state will be entered,
1855
- otherwise the default state assigned to history state object will be entered.
1856
-
1857
- An example of how to use:
1858
-
1859
- stateA: SC.State.extend({
1860
-
1861
- initialSubstate: SC.HistoryState({
1862
- defaultState: 'stateB'
1863
- }),
1864
-
1865
- stateB: SC.State.extend({ ... }),
1866
-
1867
- stateC: SC.State.extend({ ... })
1868
-
1869
- })
1870
-
1871
- @author Michael Cohen
1872
- @extends SC.Object
1873
- */
1874
- SC.HistoryState = SC.Object.extend(
1875
- /** @scope SC.HistoryState.prototype */{
1876
-
1877
- /**
1878
- Used to indicate if the statechart should recurse the
1879
- history states after entering the this object's parent state
1880
-
1881
- @property {Boolean}
1882
- */
1883
- isRecursive: NO,
1884
-
1885
- /**
1886
- The default state to enter if the parent state does not
1887
- yet have its historyState property assigned to something
1888
- other than null.
1889
-
1890
- The value assigned to this property must be the name of an
1891
- immediate substate that belongs to the parent state. The
1892
- statechart will manage the property upon initialization.
1893
-
1894
- @property {String}
1895
- */
1896
- defaultState: null,
1897
-
1898
- /** @private
1899
- Managed by the statechart
1900
-
1901
- The statechart that owns this object.
1902
- */
1903
- statechart: null,
1904
-
1905
- /** @private
1906
- Managed by the statechart
1907
-
1908
- The state that owns this object
1909
- */
1910
- parentState: null,
1911
-
1912
- /**
1913
- Used by the statechart during a state transition process.
1914
-
1915
- Returns a state to enter based on whether the parent state has
1916
- its historyState property assigned. If not then this object's
1917
- assigned default state is returned.
1918
- */
1919
- state: function() {
1920
- var defaultState = this.get('defaultState'),
1921
- historyState = this.getPath('parentState.historyState');
1922
- return !!historyState ? historyState : defaultState;
1923
- }.property().cacheable(),
1924
-
1925
- /** @private */
1926
- parentHistoryStateDidChange: function() {
1927
- this.notifyPropertyChange('state');
1928
- }.observes('*parentState.historyState')
1929
-
1930
- });
1931
-
1932
- })({});
1933
-
1934
-
1935
- (function(exports) {
1936
- // ==========================================================================
1937
- // Project: SproutCore - JavaScript Application Framework
1938
- // Copyright: ©2006-2011 Strobe Inc. and contributors.
1939
- // Portions ©2008-2011 Apple Inc. All rights reserved.
1940
- // License: Licensed under MIT license (see license.js)
1941
- // ==========================================================================
1942
-
1943
- /**
1944
- @namespace
1945
-
1946
- Support methods for the Delegate design pattern.
1947
-
1948
- The Delegate design pattern makes it easy to delegate a portion of your
1949
- application logic to another object. This is most often used in views to
1950
- delegate various application-logic decisions to controllers in order to
1951
- avoid having to bake application-logic directly into the view itself.
1952
-
1953
- The methods provided by this mixin make it easier to implement this pattern
1954
- but they are not required to support delegates.
1955
-
1956
- ## The Pattern
1957
-
1958
- The delegate design pattern typically means that you provide a property,
1959
- usually ending in "delegate", that can be set to another object in the
1960
- system.
1961
-
1962
- When events occur or logic decisions need to be made that you would prefer
1963
- to delegate, you can call methods on the delegate if it is set. If the
1964
- delegate is not set, you should provide some default functionality instead.
1965
-
1966
- Note that typically delegates are not observable, hence it is not necessary
1967
- to use get() to retrieve the value of the delegate.
1968
-
1969
- @since SproutCore 1.0
1970
-
1971
- */
1972
- SC.DelegateSupport = {
1973
-
1974
- /**
1975
- Selects the delegate that implements the specified method name. Pass one
1976
- or more delegates. The receiver is automatically included as a default.
1977
-
1978
- This can be more efficient than using invokeDelegateMethod() which has
1979
- to marshall arguments into a delegate call.
1980
-
1981
- @param {String} methodName
1982
- @param {Object...} delegate one or more delegate arguments
1983
- @returns {Object} delegate or null
1984
- */
1985
- delegateFor: function(methodName) {
1986
- var idx = 1,
1987
- len = arguments.length,
1988
- ret ;
1989
-
1990
- while(idx<len) {
1991
- ret = arguments[idx];
1992
- if (ret && ret[methodName] !== undefined) return ret ;
1993
- idx++;
1994
- }
1995
-
1996
- return (this[methodName] !== undefined) ? this : null;
1997
- },
1998
-
1999
- /**
2000
- Invokes the named method on the delegate that you pass. If no delegate
2001
- is defined or if the delegate does not implement the method, then a
2002
- method of the same name on the receiver will be called instead.
2003
-
2004
- You can pass any arguments you want to pass onto the delegate after the
2005
- delegate and methodName.
2006
-
2007
- @param {Object} delegate a delegate object. May be null.
2008
- @param {String} methodName a method name
2009
- @param {Object...} args (OPTIONAL) any additional arguments
2010
-
2011
- @returns {Object} value returned by delegate
2012
- */
2013
- invokeDelegateMethod: function(delegate, methodName, args) {
2014
- args = SC.A(arguments); args = args.slice(2, args.length) ;
2015
- if (!delegate || !delegate[methodName]) delegate = this ;
2016
-
2017
- var method = delegate[methodName];
2018
- return method ? method.apply(delegate, args) : null;
2019
- },
2020
-
2021
- /**
2022
- Search the named delegates for the passed property. If one is found,
2023
- gets the property value and returns it. If none of the passed delegates
2024
- implement the property, search the receiver for the property as well.
2025
-
2026
- @param {String} key the property to get.
2027
- @param {Object} delegate one or more delegate
2028
- @returns {Object} property value or undefined
2029
- */
2030
- getDelegateProperty: function(key, delegate) {
2031
- var idx = 1,
2032
- len = arguments.length,
2033
- ret ;
2034
-
2035
- while(idx<len) {
2036
- ret = arguments[idx++];
2037
- if (ret && ret[key] != undefined) {
2038
- return ret.get ? ret.get(key) : ret[key] ;
2039
- }
2040
- }
2041
-
2042
- return (this[key] != undefined) ? this.get(key) : undefined ;
2043
- }
2044
-
2045
- };
2046
-
2047
- })({});
2048
-
2049
-
2050
- (function(exports) {
2051
- // ==========================================================================
2052
- // Project: SC.Statechart - A Statechart Framework for SproutCore
2053
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
2054
- // Portions @2011 Apple Inc. All rights reserved.
2055
- // License: Licensed under MIT license (see license.js)
2056
- // ==========================================================================
2057
-
2058
- /*globals SC */
2059
-
2060
- /**
2061
- @class
2062
-
2063
- Apply to objects that are to represent a delegate for a SC.Statechart object.
2064
- When assigned to a statechart, the statechart and its associated states will
2065
- use the delegate in order to make various decisions.
2066
-
2067
- @see SC.Statechart#delegate
2068
-
2069
- @author Michael Cohen
2070
- */
2071
-
2072
- SC.StatechartDelegate = /** @scope SC.StatechartDelegate.prototype */ {
2073
-
2074
- // Walk like a duck
2075
- isStatechartDelegate: YES,
2076
-
2077
- // Route Handling Management
2078
-
2079
- /**
2080
- Called to update the application's current location.
2081
-
2082
- The location provided is dependent upon the application's underlying
2083
- routing mechanism.
2084
-
2085
- @param {SC.StatechartManager} statechart the statechart
2086
- @param {String|Hash} location the new location
2087
- @param {SC.State} state the state requesting the location update
2088
- */
2089
- statechartUpdateLocationForState: function(statechart, location, state) {
2090
- SC.routes.set('location', location);
2091
- },
2092
-
2093
- /**
2094
- Called to acquire the application's current location.
2095
-
2096
- @param {SC.StatechartManager} statechart the statechart
2097
- @param {SC.State} state the state requesting the location
2098
- @returns {String} the location
2099
- */
2100
- statechartAcquireLocationForState: function(statechart, state) {
2101
- return SC.routes.get('location');
2102
- },
2103
-
2104
- /**
2105
- Used to bind a state's handler to a route. When the application's location
2106
- matches the given route, the state's handler is to be invoked.
2107
-
2108
- The statechart and states remain completely independent of how the underlying
2109
- routing mechanism works thereby providing a looser coupling and more flexibility
2110
- in how routing is to work. Given this flexiblity, it is important that a route
2111
- assigned (using the {@link SC.State#representRoute} property) to a state strictly
2112
- conforms to the underlying routing mechanism's criteria in order for the given
2113
- handler to be properly invoked.
2114
-
2115
- By default the {@link SC.routes} mechanism is used to bind the state's handler with
2116
- the given route.
2117
-
2118
- @param {SC.StatechartManager} statechart the statechart
2119
- @param {SC.State} state the state to bind the route to
2120
- @param {String|Hash} route the route that is to be bound to the state
2121
- @param {Function|String} handler the method on the state to be invoked when the route
2122
- gets triggered.
2123
-
2124
- @see SC.State#representRoute
2125
- */
2126
- statechartBindStateToRoute: function(statechart, state, route, handler) {
2127
- SC.routes.add(route, state, handler);
2128
- },
2129
-
2130
- /**
2131
- Invoked by a state that has been notified to handle a triggered route. The state
2132
- asks if it should go ahead an actually handle the triggered route. If no then
2133
- the state's handler will no longer continue and finish by calling this delegate's
2134
- `statechartStateCancelledHandlingTriggeredRoute` method. If yes then the state will
2135
- continue with handling the triggered route.
2136
-
2137
- By default `YES` is returned.
2138
-
2139
- @param {SC.StatechartManager} statechart the statechart
2140
- @param {SC.State} state the state making the request
2141
- @param {SC.StateRouteHandlerContext} routeContext contextual information about the handling
2142
- of a route
2143
-
2144
- @see #statechartStateCancelledHandlingTriggeredRoute
2145
- */
2146
- statechartShouldStateHandleTriggeredRoute: function(statechart, state, context) {
2147
- return YES;
2148
- },
2149
-
2150
- /**
2151
- Invoked by a state that has been informed by the delegate to not handle a triggered route.
2152
- Used this for any additional clean up or processing that you may wish to perform.
2153
-
2154
- @param {SC.StatechartManager} statechart the statechart
2155
- @param {SC.State} state the state making the request
2156
- @param {SC.StateRouteHandlerContext} routeContext contextual information about the handling
2157
- of a route
2158
-
2159
- @see #statechartShouldStateHandleTriggeredRoute
2160
- */
2161
- statechartStateCancelledHandlingTriggeredRoute: function(statechart, state, context) { }
2162
-
2163
- };
2164
-
2165
- })({});
2166
-
2167
-
2168
- (function(exports) {
2169
- // ==========================================================================
2170
- // Project: SC.Statechart - A Statechart Framework for SproutCore
2171
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
2172
- // Portions @2011 Apple Inc. All rights reserved.
2173
- // License: Licensed under MIT license (see license.js)
2174
- // ==========================================================================
2175
-
2176
- /*globals SC */
2177
-
2178
- /**
2179
- @class
2180
-
2181
- Represents contextual information for whenever a state handles a triggered
2182
- route. In additional to retaining contextual information, you can also
2183
- use the object to retry trigging the state's route handler. Useful in cases
2184
- where you need to defer the handling of the route for a later time.
2185
-
2186
- @see SC.State
2187
-
2188
- @extends SC.Object
2189
- @author Michael Cohen
2190
- */
2191
- SC.StateRouteHandlerContext = SC.Object.extend(
2192
- /** @scope SC.StateRouteContext.prototype */{
2193
-
2194
- // walk like a duck
2195
- isStateRouteHandlerContext: true,
2196
-
2197
- /**
2198
- The state that constructed this context object.
2199
-
2200
- @property {SC.State}
2201
- */
2202
- state: null,
2203
-
2204
- /**
2205
- The location that caused the state's route to be
2206
- triggered.
2207
-
2208
- @property {String}
2209
- */
2210
- location: null,
2211
-
2212
- /**
2213
- The parameters that were supplied to the state's
2214
- handler when the state's route was triggered.
2215
-
2216
- @property {Hash}
2217
- */
2218
- params: null,
2219
-
2220
- /**
2221
- The handler that got invoked when the state's
2222
- route was triggered. This can either be a reference
2223
- to the actual method or a name of the method.
2224
-
2225
- @property {Function|String}
2226
- */
2227
- handler: null,
2228
-
2229
- /**
2230
- Used to retry invoking the state's handler for when
2231
- the state's route gets triggered. When called this will
2232
- essentially perform the same call as when the handler
2233
- was originally triggered on state.
2234
- */
2235
- retry: function() {
2236
- var state = this.get('state'),
2237
- params = this.get('params'),
2238
- handler = this.get('handler');
2239
-
2240
- if (SC.typeOf(handler) === 'string') {
2241
- handler = state[handler];
2242
- }
2243
-
2244
- if (SC.typeOf(handler) === 'function') {
2245
- handler.apply(state, [params]);
2246
- }
2247
- }
2248
-
2249
- });
2250
-
2251
- })({});
2252
-
2253
-
2254
- (function(exports) {
2255
- // ==========================================================================
2256
- // Project: SC.Statechart - A Statechart Framework for SproutCore
2257
- // Copyright: ©2010, 2011 Michael Cohen, and contributors.
2258
- // Portions @2011 Apple Inc. All rights reserved.
2259
- // License: Licensed under MIT license (see license.js)
2260
- // ==========================================================================
2261
- /*globals SC */
2262
- var get = SC.get, set = SC.set, getPath = SC.getPath;
2263
-
2264
- /**
2265
- @class
2266
-
2267
- The startchart manager mixin allows an object to be a statechart. By becoming a statechart, the
2268
- object can then be manage a set of its own states.
2269
-
2270
- This implemention of the statechart manager closely follows the concepts stated in D. Harel's
2271
- original paper "Statecharts: A Visual Formalism For Complex Systems"
2272
- (www.wisdom.weizmann.ac.il/~harel/papers/Statecharts.pdf).
2273
-
2274
- The statechart allows for complex state heircharies by nesting states within states, and
2275
- allows for state orthogonality based on the use of concurrent states.
2276
-
2277
- At minimum, a statechart must have one state: The root state. All other states in the statechart
2278
- are a decendents (substates) of the root state.
2279
-
2280
- The following example shows how states are nested within a statechart:
2281
-
2282
- MyApp.Statechart = SC.Object.extend(SC.StatechartManager, {
2283
- rootState: SC.State.extend({
2284
- initialSubstate: 'stateA',
2285
-
2286
- stateA: SC.State.extend({
2287
- // ... can continue to nest further states
2288
- }),
2289
-
2290
- stateB: SC.State.extend({
2291
- // ... can continue to nest further states
2292
- })
2293
- })
2294
- });
2295
-
2296
- Note how in the example above, the root state as an explicit initial substate to enter into. If no
2297
- initial substate is provided, then the statechart will default to the the state's first substate.
2298
-
2299
- You can also defined states without explicitly defining the root state. To do so, simply create properties
2300
- on your object that represents states. Upon initialization, a root state will be constructed automatically
2301
- by the mixin and make the states on the object substates of the root state. As an example:
2302
-
2303
- MyApp.Statechart = SC.Object.extend(SC.StatechartManager, {
2304
- initialState: 'stateA',
2305
-
2306
- stateA: SC.State.extend({
2307
- // ... can continue to nest further states
2308
- }),
2309
-
2310
- stateB: SC.State.extend({
2311
- // ... can continue to nest further states
2312
- })
2313
- });
2314
-
2315
- If you liked to specify a class that should be used as the root state but using the above method to defined
2316
- states, you can set the rootStateExample property with a class that extends from SC.State. If the
2317
- rootStateExample property is not explicitly assigned the then default class used will be SC.State.
2318
-
2319
- To provide your statechart with orthogonality, you use concurrent states. If you use concurrent states,
2320
- then your statechart will have multiple current states. That is because each concurrent state represents an
2321
- independent state structure from other concurrent states. The following example shows how to provide your
2322
- statechart with concurrent states:
2323
-
2324
- MyApp.Statechart = SC.Object.extend(SC.StatechartManager, {
2325
- rootState: SC.State.extend({
2326
- substatesAreConcurrent: true,
2327
-
2328
- stateA: SC.State.extend({
2329
- // ... can continue to nest further states
2330
- }),
2331
-
2332
- stateB: SC.State.extend({
2333
- // ... can continue to nest further states
2334
- })
2335
- })
2336
- });
2337
-
2338
- Above, to indicate that a state's substates are concurrent, you just have to set the substatesAreConcurrent to
2339
- true. Once done, then stateA and stateB will be independent of each other and each will manage their
2340
- own current substates. The root state will then have more then one current substate.
2341
-
2342
- To define concurrent states directly on the object without explicitly defining a root, you can do the
2343
- following:
2344
-
2345
- MyApp.Statechart = SC.Object.extend(SC.StatechartManager, {
2346
- statesAreConcurrent: true,
2347
-
2348
- stateA: SC.State.extend({
2349
- // ... can continue to nest further states
2350
- }),
2351
-
2352
- stateB: SC.State.extend({
2353
- // ... can continue to nest further states
2354
- })
2355
- });
2356
-
2357
- Remember that a startchart can have a mixture of nested and concurrent states in order for you to
2358
- create as complex of statecharts that suite your needs. Here is an example of a mixed state structure:
2359
-
2360
- MyApp.Statechart = SC.Object.extend(SC.StatechartManager, {
2361
- rootState: SC.State.extend({
2362
- initialSubstate: 'stateA',
2363
-
2364
- stateA: SC.State.extend({
2365
- substatesAreConcurrent: true,
2366
-
2367
- stateM: SC.State.extend({ ... })
2368
- stateN: SC.State.extend({ ... })
2369
- stateO: SC.State.extend({ ... })
2370
- }),
2371
-
2372
- stateB: SC.State.extend({
2373
- initialSubstate: 'stateX',
2374
-
2375
- stateX: SC.State.extend({ ... })
2376
- stateY: SC.State.desgin({ ... })
2377
- })
2378
- })
2379
- });
2380
-
2381
- Depending on your needs, a statechart can have lots of states, which can become hard to manage all within
2382
- one file. To modularize your states and make them easier to manage and maintain, you can plug-in states
2383
- into other states. Let's say we are using the statechart in the last example above, and all the code is
2384
- within one file. We could update the code and split the logic across two or more files like so:
2385
-
2386
- // state_a.js
2387
-
2388
- MyApp.StateA = SC.State.extend({
2389
- substatesAreConcurrent: true,
2390
-
2391
- stateM: SC.State.extend({ ... })
2392
- stateN: SC.State.extend({ ... })
2393
- stateO: SC.State.extend({ ... })
2394
- });
2395
-
2396
- // state_b.js
2397
-
2398
- MyApp.StateB = SC.State.extend({
2399
- substatesAreConcurrent: true,
2400
-
2401
- stateM: SC.State.extend({ ... })
2402
- stateN: SC.State.extend({ ... })
2403
- stateO: SC.State.extend({ ... })
2404
- });
2405
-
2406
- // statechart.js
2407
-
2408
- MyApp.Statechart = SC.Object.extend(SC.StatechartManager, {
2409
- rootState: SC.State.extend({
2410
- initialSubstate: 'stateA',
2411
- stateA: SC.State.plugin('MyApp.StateA'),
2412
- stateB: SC.State.plugin('MyApp.StateB')
2413
- })
2414
- });
2415
-
2416
- Using state plug-in functionality is optional. If you use the plug-in feature you can break up your statechart
2417
- into as many files as you see fit.
2418
-
2419
- @author Michael Cohen
2420
- */
2421
-
2422
- SC.StatechartManager = /** @scope SC.StatechartManager.prototype */{
2423
-
2424
- // Walk like a duck
2425
- isStatechart: true,
2426
-
2427
- /**
2428
- Indicates if this statechart has been initialized
2429
-
2430
- @property {Boolean}
2431
- */
2432
- statechartIsInitialized: false,
2433
-
2434
- /**
2435
- The root state of this statechart. All statecharts must have a root state.
2436
-
2437
- If this property is left unassigned then when the statechart is initialized
2438
- it will used the rootStateExample, initialState, and statesAreConcurrent
2439
- properties to construct a root state.
2440
-
2441
- @see #rootStateExample
2442
- @see #initialState
2443
- @see #statesAreConcurrent
2444
-
2445
- @property {SC.State}
2446
- */
2447
- rootState: null,
2448
-
2449
- /**
2450
- Represents the class used to construct a class that will be the root state for
2451
- this statechart. The class assigned must derive from SC.State.
2452
-
2453
- This property will only be used if the rootState property is not assigned.
2454
-
2455
- @see #rootState
2456
-
2457
- @property {SC.State}
2458
- */
2459
- rootStateExample: SC.State,
2460
-
2461
- /**
2462
- Indicates what state should be the intiail state of this statechart. The value
2463
- assigned must be the name of a property on this object that represents a state.
2464
- As well, the statesAreConcurrent must be set to false.
2465
-
2466
- This property will only be used if the rootState property is not assigned.
2467
-
2468
- @see #rootState
2469
-
2470
- @property {String}
2471
- */
2472
- initialState: null,
2473
-
2474
- /**
2475
- Indicates if properties on this object representing states are concurrent to each other.
2476
- If true then they are concurrent, otherwise they are not. If the true, then the
2477
- initialState property must not be assigned.
2478
-
2479
- This property will only be used if the rootState property is not assigned.
2480
-
2481
- @see #rootState
2482
-
2483
- @property {Boolean}
2484
- */
2485
- statesAreConcurrent: false,
2486
-
2487
- /**
2488
- Indicates whether to use a monitor to monitor that statechart's activities. If true then
2489
- the monitor will be active, otherwise the monitor will not be used. Useful for debugging
2490
- purposes.
2491
-
2492
- @property {Boolean}
2493
- */
2494
- monitorIsActive: false,
2495
-
2496
- /**
2497
- A statechart monitor that can be used to monitor this statechart. Useful for debugging purposes.
2498
- A monitor will only be used if monitorIsActive is true.
2499
-
2500
- @property {SC.StatechartMonitor}
2501
- */
2502
- monitor: null,
2503
-
2504
- /**
2505
- Used to specify what property (key) on the statechart should be used as the trace property. By
2506
- default the property is 'trace'.
2507
-
2508
- @property {String}
2509
- */
2510
- statechartTraceKey: 'trace',
2511
-
2512
- /**
2513
- Indicates whether to trace the statecharts activities. If true then the statechart will output
2514
- its activites to the browser's JS console. Useful for debugging purposes.
2515
-
2516
- @see #statechartTraceKey
2517
-
2518
- @property {Boolean}
2519
- */
2520
- trace: false,
2521
-
2522
- /**
2523
- Used to specify what property (key) on the statechart should be used as the owner property. By
2524
- default the property is 'owner'.
2525
-
2526
- @property {String}
2527
- */
2528
- statechartOwnerKey: 'owner',
2529
-
2530
- /**
2531
- Sets who the owner is of this statechart. If null then the owner is this object otherwise
2532
- the owner is the assigned object.
2533
-
2534
- @see #statechartOwnerKey
2535
-
2536
- @property {SC.Object}
2537
- */
2538
- owner: null,
2539
-
2540
- /**
2541
- Indicates if the statechart should be automatically initialized by this
2542
- object after it has been created. If true then initStatechart will be
2543
- called automatically, otherwise it will not.
2544
-
2545
- @property {Boolean}
2546
- */
2547
- autoInitStatechart: true,
2548
-
2549
- /**
2550
- If yes, any warning messages produced by the statechart or any of its states will
2551
- not be logged, otherwise all warning messages will be logged.
2552
-
2553
- While designing and debugging your statechart, it's best to keep this value false.
2554
- In production you can then suppress the warning messages.
2555
-
2556
- @property {Boolean}
2557
- */
2558
- suppressStatechartWarnings: false,
2559
-
2560
- /**
2561
- A statechart delegate used by the statechart and the states that the statechart
2562
- manages. The value assigned must adhere to the {@link SC.StatechartDelegate} mixin.
2563
-
2564
- @property {SC.Object}
2565
-
2566
- @see SC.StatechartDelegate
2567
- */
2568
- delegate: null,
2569
-
2570
- /**
2571
- Computed property that returns an objects that adheres to the
2572
- {@link SC.StatechartDelegate} mixin. If the {@link #delegate} is not
2573
- assigned then this object is the default value returned.
2574
-
2575
- @see SC.StatechartDelegate
2576
- @see #delegate
2577
- */
2578
- statechartDelegate: function() {
2579
- var del = this.get('delegate');
2580
- return this.delegateFor('isStatechartDelegate', del);
2581
- }.property('delegate'),
2582
-
2583
- init: function() {
2584
- if (get(this, 'autoInitStatechart')) {
2585
- this.initStatechart();
2586
- }
2587
- },
2588
-
2589
- destroy: function() {
2590
- var root = get(this, 'rootState'),
2591
- traceKey = get(this, 'statechartTraceKey');
2592
-
2593
- SC.removeObserver(this, traceKey, this, '_statechartTraceDidChange');
2594
-
2595
- root.destroy();
2596
- set(this, 'rootState', null);
2597
- this._super();
2598
- },
2599
-
2600
- /**
2601
- Initializes the statechart. By initializing the statechart, it will create all the states and register
2602
- them with the statechart. Once complete, the statechart can be used to go to states and send actions to.
2603
- */
2604
- initStatechart: function() {
2605
- if (get(this, 'statechartIsInitialized')) return;
2606
-
2607
- this._gotoStateLocked = false;
2608
- this._sendActionLocked = false;
2609
- this._pendingStateTransitions = [];
2610
- this._pendingSentActions = [];
2611
-
2612
- if (get(this, 'monitorIsActive')) {
2613
- set(this, 'monitor', SC.StatechartMonitor.create({ statechart: this }));
2614
- }
2615
-
2616
- var traceKey = get(this, 'statechartTraceKey');
2617
-
2618
- this.addObserver(traceKey, this, '_statechartTraceDidChange');
2619
- this._statechartTraceDidChange();
2620
-
2621
- var trace = get(this, 'allowStatechartTracing'),
2622
- rootState = get(this, 'rootState'),
2623
- msg;
2624
-
2625
- if (trace) this.statechartLogTrace("BEGIN initialize statechart");
2626
-
2627
- // If no root state was explicitly defined then try to construct
2628
- // a root state class
2629
- if (!rootState) {
2630
- rootState = this._constructRootStateClass();
2631
- }
2632
- else if (SC.typeOf(rootState) === "function" && rootState.statePlugin) {
2633
- rootState = rootState.apply(this);
2634
- }
2635
-
2636
- if (!(SC.State.detect(rootState) && rootState.isClass)) {
2637
- msg = "Unable to initialize statechart. Root state must be a state class";
2638
- this.statechartLogError(msg);
2639
- throw msg;
2640
- }
2641
-
2642
- rootState = this.createRootState(rootState, {
2643
- statechart: this,
2644
- stateName: SC.ROOT_STATE_NAME
2645
- });
2646
-
2647
- set(this, 'rootState', rootState);
2648
- rootState.initState();
2649
-
2650
- if (SC.EmptyState.detect(get(rootState, 'initialSubstate'))) {
2651
- msg = "Unable to initialize statechart. Root state must have an initial substate explicilty defined";
2652
- this.statechartLogError(msg);
2653
- throw msg;
2654
- }
2655
-
2656
- if (!SC.empty(get(this, 'initialState'))) {
2657
- var key = 'initialState';
2658
- set(this, key, get(rootState, get(this, key)));
2659
- }
2660
-
2661
- set(this, 'statechartIsInitialized', true);
2662
- this.gotoState(rootState);
2663
-
2664
- if (trace) this.statechartLogTrace("END initialize statechart");
2665
- },
2666
-
2667
- /**
2668
- Will create a root state for the statechart
2669
- */
2670
- createRootState: function(state, attrs) {
2671
- if (!attrs) attrs = {};
2672
- state = state.create(attrs);
2673
- return state;
2674
- },
2675
-
2676
- /**
2677
- Returns an array of all the current states for this statechart
2678
-
2679
- @returns {Array} the current states
2680
- */
2681
- currentStates: function() {
2682
- return getPath(this, 'rootState.currentSubstates');
2683
- }.property().cacheable(),
2684
-
2685
- /**
2686
- Returns the first current state for this statechart.
2687
-
2688
- @return {SC.State}
2689
- */
2690
- firstCurrentState: function() {
2691
- var cs = get(this, 'currentStates');
2692
- return cs ? cs.objectAt(0) : null;
2693
- }.property('currentStates').cacheable(),
2694
-
2695
- /**
2696
- Returns the count of the current states for this statechart
2697
-
2698
- @returns {Number} the count
2699
- */
2700
- currentStateCount: function() {
2701
- return getPath(this, 'currentStates.length');
2702
- }.property('currentStates').cacheable(),
2703
-
2704
- /**
2705
- Checks if a given state is a current state of this statechart.
2706
-
2707
- @param state {State|String} the state to check
2708
- @returns {Boolean} true if the state is a current state, otherwise fals is returned
2709
- */
2710
- stateIsCurrentState: function(state) {
2711
- return get(this, 'rootState').stateIsCurrentSubstate(state);
2712
- },
2713
-
2714
- /**
2715
- Returns an array of all the states that are currently entered for
2716
- this statechart.
2717
-
2718
- @returns {Array} the currently entered states
2719
- */
2720
- enteredStates: function() {
2721
- return getPath(this, 'rootState.enteredSubstates');
2722
- }.property().cacheable(),
2723
-
2724
- /**
2725
- Checks if a given state is a currently entered state of this statechart.
2726
-
2727
- @param state {State|String} the state to check
2728
- @returns {Boolean} true if the state is a currently entered state, otherwise false is returned
2729
- */
2730
- stateIsEntered: function(state) {
2731
- return get(this, 'rootState').stateIsEnteredSubstate(state);
2732
- },
2733
-
2734
- /**
2735
- Checks if the given value represents a state is this statechart
2736
-
2737
- @param value {State|String} either a state object or the name of a state
2738
- @returns {Boolean} true if the state does belong ot the statechart, otherwise false is returned
2739
- */
2740
- doesContainState: function(value) {
2741
- return !SC.none(this.getState(value));
2742
- },
2743
-
2744
- /**
2745
- Gets a state from the statechart that matches the given value
2746
-
2747
- @param value {State|String} either a state object of the name of a state
2748
- @returns {State} if a match then the matching state is returned, otherwise null is returned
2749
- */
2750
- getState: function(value) {
2751
- return get(this, 'rootState').getSubstate(value);
2752
- },
2753
-
2754
- /**
2755
- When called, the statechart will proceed with making state transitions in the statechart starting from
2756
- a current state that meet the statechart conditions. When complete, some or all of the statechart's
2757
- current states will be changed, and all states that were part of the transition process will either
2758
- be exited or entered in a specific order.
2759
-
2760
- The state that is given to go to will not necessarily be a current state when the state transition process
2761
- is complete. The final state or states are dependent on factors such an initial substates, concurrent
2762
- states, and history states.
2763
-
2764
- Because the statechart can have one or more current states, it may be necessary to indicate what current state
2765
- to start from. If no current state to start from is provided, then the statechart will default to using
2766
- the first current state that it has; depending of the make up of the statechart (no concurrent state vs.
2767
- with concurrent states), the outcome may be unexpected. For a statechart with concurrent states, it is best
2768
- to provide a current state in which to start from.
2769
-
2770
- When using history states, the statechart will first make transitions to the given state and then use that
2771
- state's history state and recursively follow each history state's history state until there are no
2772
- more history states to follow. If the given state does not have a history state, then the statechart
2773
- will continue following state transition procedures.
2774
-
2775
- Method can be called in the following ways:
2776
-
2777
- // With one argument.
2778
- gotoState(<state>)
2779
-
2780
- // With two argument.
2781
- gotoState(<state>, <state | boolean | hash>)
2782
-
2783
- // With three argument.
2784
- gotoState(<state>, <state>, <boolean | hash>)
2785
- gotoState(<state>, <boolean>, <hash>)
2786
-
2787
- // With four argument.
2788
- gotoState(<state>, <state>, <boolean>, <hash>)
2789
-
2790
- where <state> is either a SC.State object or a string and <hash> is a regular JS hash object.
2791
-
2792
- @param state {SC.State|String} the state to go to (may not be the final state in the transition process)
2793
- @param fromCurrentState {SC.State|String} Optional. The current state to start the transition process from.
2794
- @param useHistory {Boolean} Optional. Indicates whether to include using history states in the transition process
2795
- @param context {Hash} Optional. A context object that will be passed to all exited and entered states
2796
- */
2797
- gotoState: function(state, fromCurrentState, useHistory, context) {
2798
-
2799
- if (!get(this, 'statechartIsInitialized')) {
2800
- this.statechartLogError("can not go to state %@. statechart has not yet been initialized".fmt(state));
2801
- return;
2802
- }
2803
-
2804
- if (get(this, 'isDestroyed')) {
2805
- this.statechartLogError("can not go to state %@. statechart is destroyed".fmt(this));
2806
- return;
2807
- }
2808
-
2809
- var args = this._processGotoStateArgs(arguments);
2810
-
2811
- state = args.state;
2812
- fromCurrentState = args.fromCurrentState;
2813
- useHistory = args.useHistory;
2814
- context = args.context;
2815
-
2816
- var pivotState = null,
2817
- exitStates = [],
2818
- enterStates = [],
2819
- trace = get(this, 'allowStatechartTracing'),
2820
- rootState = get(this, 'rootState'),
2821
- paramState = state,
2822
- paramFromCurrentState = fromCurrentState,
2823
- msg;
2824
-
2825
- var stateObject = rootState.getSubstate(state);
2826
-
2827
- if (SC.none(stateObject)) {
2828
- this.statechartLogError("Can not to goto state %@. Not a recognized state in statechart".fmt(paramState));
2829
- return;
2830
- }
2831
-
2832
- if (this._gotoStateLocked) {
2833
- // There is a state transition currently happening. Add this requested state
2834
- // transition to the queue of pending state transitions. The request will
2835
- // be invoked after the current state transition is finished.
2836
- this._pendingStateTransitions.push({
2837
- state: stateObject,
2838
- fromCurrentState: fromCurrentState,
2839
- useHistory: useHistory,
2840
- context: context
2841
- });
2842
-
2843
- return;
2844
- }
2845
-
2846
- // Lock the current state transition so that no other requested state transition
2847
- // interferes.
2848
- this._gotoStateLocked = true;
2849
-
2850
- if (!SC.none(fromCurrentState)) {
2851
- // Check to make sure the current state given is actually a current state of this statechart
2852
- fromCurrentState = rootState.getSubstate(fromCurrentState);
2853
- if (SC.none(fromCurrentState) || !get(fromCurrentState, 'isCurrentState')) {
2854
- msg = "Can not to goto state %@. %@ is not a recognized current state in statechart";
2855
- this.statechartLogError(msg.fmt(paramState, paramFromCurrentState));
2856
- this._gotoStateLocked = false;
2857
- return;
2858
- }
2859
- }
2860
- else if (getPath(this, 'currentStates.length') > 0) {
2861
- // No explicit current state to start from; therefore, just use the first current state as
2862
- // a default, if there is a current state.
2863
- fromCurrentState = get(this, 'currentStates')[0];
2864
- msg = "gotoState: fromCurrentState not explicitly provided. Using a default current state to transition from: %@";
2865
- this.statechartLogWarning(msg.fmt(fromCurrentState));
2866
- }
2867
-
2868
- if (trace) {
2869
- this.statechartLogTrace("BEGIN gotoState: %@".fmt(stateObject));
2870
- msg = "starting from current state: %@";
2871
- msg = msg.fmt(fromCurrentState ? fromCurrentState : '---');
2872
- this.statechartLogTrace(msg);
2873
- msg = "current states before: %@";
2874
- msg = msg.fmt(getPath(this, 'currentStates.length') > 0 ? get(this, 'currentStates') : '---');
2875
- this.statechartLogTrace(msg);
2876
- }
2877
-
2878
- // If there is a current state to start the transition process from, then determine what
2879
- // states are to be exited
2880
- if (!SC.none(fromCurrentState)) {
2881
- exitStates = this._createStateChain(fromCurrentState);
2882
- }
2883
-
2884
- // Now determine the initial states to be entered
2885
- enterStates = this._createStateChain(stateObject);
2886
-
2887
- // Get the pivot state to indicate when to go from exiting states to entering states
2888
- pivotState = this._findPivotState(exitStates, enterStates);
2889
-
2890
- if (pivotState) {
2891
- if (trace) this.statechartLogTrace("pivot state = %@".fmt(pivotState));
2892
- if (get(pivotState, 'substatesAreConcurrent')) {
2893
- this.statechartLogError("Can not go to state %@ from %@. Pivot state %@ has concurrent substates.".fmt(stateObject, fromCurrentState, pivotState));
2894
- this._gotoStateLocked = false;
2895
- return;
2896
- }
2897
- }
2898
-
2899
- // Collect what actions to perform for the state transition process
2900
- var gotoStateActions = [];
2901
-
2902
- // Go ahead and find states that are to be exited
2903
- this._traverseStatesToExit(exitStates.shift(), exitStates, pivotState, gotoStateActions);
2904
-
2905
- // Now go find states that are to entered
2906
- if (pivotState !== stateObject) {
2907
- this._traverseStatesToEnter(enterStates.pop(), enterStates, pivotState, useHistory, gotoStateActions);
2908
- } else {
2909
- this._traverseStatesToExit(pivotState, [], null, gotoStateActions);
2910
- this._traverseStatesToEnter(pivotState, null, null, useHistory, gotoStateActions);
2911
- }
2912
-
2913
- // Collected all the state transition actions to be performed. Now execute them.
2914
- this._executeGotoStateActions(stateObject, gotoStateActions, null, context);
2915
- },
2916
-
2917
- /**
2918
- Indicates if the statechart is in an active goto state process
2919
- */
2920
- gotoStateActive: function() {
2921
- return this._gotoStateLocked;
2922
- }.property(),
2923
-
2924
- /**
2925
- Indicates if the statechart is in an active goto state process
2926
- that has been suspended
2927
- */
2928
- gotoStateSuspended: function() {
2929
- return this._gotoStateLocked && !!this._gotoStateSuspendedPoint;
2930
- }.property(),
2931
-
2932
- /**
2933
- Resumes an active goto state transition process that has been suspended.
2934
- */
2935
- resumeGotoState: function() {
2936
- if (!get(this, 'gotoStateSuspended')) {
2937
- this.statechartLogError("Can not resume goto state since it has not been suspended");
2938
- return;
2939
- }
2940
-
2941
- var point = this._gotoStateSuspendedPoint;
2942
- this._executeGotoStateActions(point.gotoState, point.actions, point.marker, point.context);
2943
- },
2944
-
2945
- /** @private */
2946
- _executeGotoStateActions: function(gotoState, actions, marker, context) {
2947
- var action = null,
2948
- len = actions.length,
2949
- actionResult = null;
2950
-
2951
- marker = SC.none(marker) ? 0 : marker;
2952
-
2953
- for (; marker < len; marker += 1) {
2954
- action = actions[marker];
2955
- switch (action.action) {
2956
- case SC.EXIT_STATE:
2957
- actionResult = this._exitState(action.state, context);
2958
- break;
2959
-
2960
- case SC.ENTER_STATE:
2961
- actionResult = this._enterState(action.state, action.currentState, context);
2962
- break;
2963
- }
2964
-
2965
- //
2966
- // Check if the state wants to perform an asynchronous action during
2967
- // the state transition process. If so, then we need to first
2968
- // suspend the state transition process and then invoke the
2969
- // asynchronous action. Once called, it is then up to the state or something
2970
- // else to resume this statechart's state transition process by calling the
2971
- // statechart's resumeGotoState method.
2972
- //
2973
- if (actionResult instanceof SC.Async) {
2974
- this._gotoStateSuspendedPoint = {
2975
- gotoState: gotoState,
2976
- actions: actions,
2977
- marker: marker + 1,
2978
- context: context
2979
- };
2980
-
2981
- actionResult.tryToPerform(action.state);
2982
- return;
2983
- }
2984
- }
2985
-
2986
- this.beginPropertyChanges();
2987
- this.notifyPropertyChange('currentStates');
2988
- this.notifyPropertyChange('enteredStates');
2989
- this.endPropertyChanges();
2990
-
2991
- if (get(this, 'allowStatechartTracing')) {
2992
- this.statechartLogTrace("current states after: %@".fmt(get(this, 'currentStates')));
2993
- this.statechartLogTrace("END gotoState: %@".fmt(gotoState));
2994
- }
2995
-
2996
- this._cleanupStateTransition();
2997
- },
2998
- /** @private */
2999
- _cleanupStateTransition: function() {
3000
- this._currentGotoStateAction = null;
3001
- this._gotoStateSuspendedPoint = null;
3002
- this._gotoStateActions = null;
3003
- this._gotoStateLocked = NO;
3004
- this._flushPendingStateTransition();
3005
- },
3006
-
3007
- /** @private */
3008
- _exitState: function(state, context) {
3009
- var parentState;
3010
-
3011
- if (get(state, 'currentSubstates').indexOf(state) >= 0) {
3012
- parentState = get(state, 'parentState');
3013
- while (parentState) {
3014
- get(parentState, 'currentSubstates').removeObject(state);
3015
- parentState = get(parentState, 'parentState');
3016
- }
3017
- }
3018
-
3019
- parentState = state;
3020
- while (parentState) {
3021
- get(parentState, 'enteredSubstates').removeObject(state);
3022
- parentState = get(parentState, 'parentState');
3023
- }
3024
-
3025
- if (get(this, 'allowStatechartTracing')) this.statechartLogTrace("<-- exiting state: %@".fmt(state));
3026
-
3027
- set(state, 'currentSubstates', []);
3028
- state.notifyPropertyChange('isCurrentState');
3029
-
3030
- state.stateWillBecomeExited(context);
3031
- var result = this.exitState(state, context);
3032
- state.stateDidBecomeExited(context);
3033
-
3034
- if (get(this, 'monitorIsActive')) get(this, 'monitor').pushExitedState(state);
3035
-
3036
- state._traverseStatesToExit_skipState = false;
3037
-
3038
- return result;
3039
- },
3040
-
3041
- /**
3042
- What will actually invoke a state's exitState method.
3043
-
3044
- Called during the state transition process whenever the gotoState method is
3045
- invoked.
3046
-
3047
- @param state {SC.State} the state whose enterState method is to be invoked
3048
- @param context {Hash} a context hash object to provide the enterState method
3049
- */
3050
- exitState: function(state, context) {
3051
- return state.exitState(context);
3052
- },
3053
-
3054
- /** @private */
3055
- _enterState: function(state, current, context) {
3056
- var parentState = get(state, 'parentState');
3057
- if (parentState && !get(state, 'isConcurrentState')) set(parentState, 'historyState', state);
3058
-
3059
- if (current) {
3060
- parentState = state;
3061
- while (parentState) {
3062
- get(parentState, 'currentSubstates').pushObject(state);
3063
- parentState = get(parentState, 'parentState');
3064
- }
3065
- }
3066
-
3067
- parentState = state;
3068
- while (parentState) {
3069
- get(parentState, 'enteredSubstates').pushObject(state);
3070
- parentState = get(parentState, 'parentState');
3071
- }
3072
-
3073
- if (get(this, 'allowStatechartTracing')) this.statechartLogTrace("--> entering state: %@".fmt(state));
3074
-
3075
- state.notifyPropertyChange('isCurrentState');
3076
-
3077
- state.stateWillBecomeEntered(context);
3078
- var result = this.enterState(state, context);
3079
- state.stateDidBecomeEntered(context);
3080
-
3081
- if (get(this, 'monitorIsActive')) get(this, 'monitor').pushEnteredState(state);
3082
-
3083
- return result;
3084
- },
3085
-
3086
- /**
3087
- What will actually invoke a state's enterState method.
3088
-
3089
- Called during the state transition process whenever the gotoState method is
3090
- invoked.
3091
-
3092
- @param state {SC.State} the state whose enterState method is to be invoked
3093
- @param context {Hash} a context hash object to provide the enterState method
3094
- */
3095
- enterState: function(state, context) {
3096
- if (state.enterStateByRoute && SC.kindOf(context, SC.StateRouteHandlerContext)) {
3097
- return state.enterStateByRoute(context);
3098
- } else {
3099
- return state.enterState(context);
3100
- }
3101
- },
3102
-
3103
- /**
3104
- When called, the statechart will proceed to make transitions to the given state then follow that
3105
- state's history state.
3106
-
3107
- You can either go to a given state's history recursively or non-recursively. To go to a state's history
3108
- recursively means to following each history state's history state until no more history states can be
3109
- followed. Non-recursively means to just to the given state's history state but do not recusively follow
3110
- history states. If the given state does not have a history state, then the statechart will just follow
3111
- normal procedures when making state transitions.
3112
-
3113
- Because a statechart can have one or more current states, depending on if the statechart has any concurrent
3114
- states, it is optional to provided current state in which to start the state transition process from. If no
3115
- current state is provided, then the statechart will default to the first current state that it has; which,
3116
- depending on the make up of that statechart, can lead to unexpected outcomes. For a statechart with concurrent
3117
- states, it is best to explicitly supply a current state.
3118
-
3119
- Method can be called in the following ways:
3120
-
3121
- // With one arguments.
3122
- gotoHistoryState(<state>)
3123
-
3124
- // With two arguments.
3125
- gotoHistoryState(<state>, <state | boolean | hash>)
3126
-
3127
- // With three arguments.
3128
- gotoHistoryState(<state>, <state>, <boolean | hash>)
3129
- gotoHistoryState(<state>, <boolean>, <hash>)
3130
-
3131
- // With four argumetns
3132
- gotoHistoryState(<state>, <state>, <boolean>, <hash>)
3133
-
3134
- where <state> is either a SC.State object or a string and <hash> is a regular JS hash object.
3135
-
3136
- @param state {SC.State|String} the state to go to and follow it's history state
3137
- @param fromCurrentState {SC.State|String} Optional. the current state to start the state transition process from
3138
- @param recursive {Boolean} Optional. whether to follow history states recursively.
3139
- */
3140
- gotoHistoryState: function(state, fromCurrentState, recursive, context) {
3141
- if (!get(this, 'statechartIsInitialized')) {
3142
- this.statechartLogError("can not go to state %@'s history state. Statechart has not yet been initialized".fmt(state));
3143
- return;
3144
- }
3145
-
3146
- var args = this._processGotoStateArgs(arguments);
3147
-
3148
- state = args.state;
3149
- fromCurrentState = args.fromCurrentState;
3150
- recursive = args.useHistory;
3151
- context = args.context;
3152
-
3153
- state = this.getState(state);
3154
-
3155
- if (!state) {
3156
- this.statechartLogError("Can not to goto state %@'s history state. Not a recognized state in statechart".fmt(state));
3157
- return;
3158
- }
3159
-
3160
- var historyState = get(state, 'historyState');
3161
-
3162
- if (!recursive) {
3163
- if (historyState) {
3164
- this.gotoState(historyState, fromCurrentState, context);
3165
- } else {
3166
- this.gotoState(state, fromCurrentState, context);
3167
- }
3168
- } else {
3169
- this.gotoState(state, fromCurrentState, true, context);
3170
- }
3171
- },
3172
-
3173
- /**
3174
- Sends a given action to all the statechart's current states.
3175
-
3176
- If a current state does can not respond to the sent action, then the current state's parent state
3177
- will be tried. This process is recursively done until no more parent state can be tried.
3178
-
3179
- @param action {String} name of the action
3180
- @param arg1 {Object} optional argument
3181
- @param arg2 {Object} optional argument
3182
- @returns {SC.Responder} the responder that handled it or null
3183
- */
3184
- sendAction: function(action, arg1, arg2) {
3185
-
3186
- if (get(this, 'isDestroyed')) {
3187
- this.statechartLogError("can send action %@. statechart is destroyed".fmt(action));
3188
- return;
3189
- }
3190
-
3191
- var statechartHandledAction = false,
3192
- actionHandled = false,
3193
- currentStates = get(this, 'currentStates').slice(),
3194
- checkedStates = {},
3195
- len = 0,
3196
- i = 0,
3197
- state = null,
3198
- trace = get(this, 'allowStatechartTracing');
3199
-
3200
- if (this._sendActionLocked || this._gotoStateLocked) {
3201
- // Want to praction any actions from being processed by the states until
3202
- // they have had a chance to handle the most immediate action or completed
3203
- // a state transition
3204
- this._pendingSentActions.push({
3205
- action: action,
3206
- arg1: arg1,
3207
- arg2: arg2
3208
- });
3209
-
3210
- return;
3211
- }
3212
-
3213
- this._sendActionLocked = true;
3214
-
3215
- if (trace) {
3216
- this.statechartLogTrace("BEGIN sendAction: action<%@>".fmt(action));
3217
- }
3218
-
3219
- len = get(currentStates, 'length');
3220
- for (; i < len; i += 1) {
3221
- actionHandled = false;
3222
- state = currentStates[i];
3223
- if (!get(state, 'isCurrentState')) continue;
3224
- while (!actionHandled && state) {
3225
- if (!checkedStates[get(state, 'fullPath')]) {
3226
- actionHandled = state.tryToHandleAction(action, arg1, arg2);
3227
- checkedStates[get(state, 'fullPath')] = true;
3228
- }
3229
- if (!actionHandled) state = get(state, 'parentState');
3230
- else statechartHandledAction = true;
3231
- }
3232
- }
3233
-
3234
- // Now that all the states have had a chance to process the
3235
- // first action, we can go ahead and flush any pending sent actions.
3236
- this._sendActionLocked = false;
3237
-
3238
- if (trace) {
3239
- if (!statechartHandledAction) this.statechartLogTrace("No state was able handle action %@".fmt(action));
3240
- this.statechartLogTrace("END sendAction: action<%@>".fmt(action));
3241
- }
3242
-
3243
- var result = this._flushPendingSentActions();
3244
-
3245
- return statechartHandledAction ? this : (result ? this : null);
3246
- },
3247
-
3248
- /** @private
3249
-
3250
- Creates a chain of states from the given state to the greatest ancestor state (the root state). Used
3251
- when perform state transitions.
3252
- */
3253
- _createStateChain: function(state) {
3254
- var chain = [];
3255
-
3256
- while (state) {
3257
- chain.push(state);
3258
- state = get(state, 'parentState');
3259
- }
3260
-
3261
- return chain;
3262
- },
3263
-
3264
- /** @private
3265
-
3266
- Finds a pivot state from two given state chains. The pivot state is the state indicating when states
3267
- go from being exited to states being entered during the state transition process. The value
3268
- returned is the fist matching state between the two given state chains.
3269
- */
3270
- _findPivotState: function(stateChain1, stateChain2) {
3271
- if (stateChain1.length === 0 || stateChain2.length === 0) return null;
3272
-
3273
- var pivot = stateChain1.find(function(state, index) {
3274
- if (stateChain2.indexOf(state) >= 0) return true;
3275
- });
3276
-
3277
- return pivot;
3278
- },
3279
-
3280
- /** @private
3281
-
3282
- Recursively follow states that are to be exited during a state transition process. The exit
3283
- process is to start from the given state and work its way up to when either all exit
3284
- states have been reached based on a given exit path or when a stop state has been reached.
3285
-
3286
- @param state {State} the state to be exited
3287
- @param exitStatePath {Array} an array representing a path of states that are to be exited
3288
- @param stopState {State} an explicit state in which to stop the exiting process
3289
- */
3290
- _traverseStatesToExit: function(state, exitStatePath, stopState, gotoStateActions) {
3291
- if (!state || state === stopState) return;
3292
-
3293
- var trace = get(this, 'allowStatechartTracing');
3294
-
3295
- // This state has concurrent substates. Therefore we have to make sure we
3296
- // exit them up to this state before we can go any further up the exit chain.
3297
- if (get(state, 'substatesAreConcurrent')) {
3298
- var i = 0,
3299
- currentSubstates = get(state, 'currentSubstates'),
3300
- len = currentSubstates.length,
3301
- currentState = null;
3302
-
3303
- for (; i < len; i += 1) {
3304
- currentState = currentSubstates[i];
3305
- if (currentState._traverseStatesToExit_skipState === true) continue;
3306
- var chain = this._createStateChain(currentState);
3307
- this._traverseStatesToExit(chain.shift(), chain, state, gotoStateActions);
3308
- }
3309
- }
3310
-
3311
- gotoStateActions.push({ action: SC.EXIT_STATE, state: state });
3312
- if (get(state, 'isCurrentState')) state._traverseStatesToExit_skipState = true;
3313
- this._traverseStatesToExit(exitStatePath.shift(), exitStatePath, stopState, gotoStateActions);
3314
- },
3315
-
3316
- /** @private
3317
-
3318
- Recursively follow states that are to be entred during the state transition process. The
3319
- enter process is to start from the given state and work its way down a given enter path. When
3320
- the end of enter path has been reached, then continue entering states based on whether
3321
- an initial substate is defined, there are concurrent substates or history states are to be
3322
- followed; when none of those condition are met then the enter process is done.
3323
-
3324
- @param state {State} the sate to be entered
3325
- @param enterStatePath {Array} an array representing an initial path of states that are to be entered
3326
- @param pivotState {State} The state pivoting when to go from exiting states to entering states
3327
- @param useHistory {Boolean} indicates whether to recursively follow history states
3328
- */
3329
- _traverseStatesToEnter: function(state, enterStatePath, pivotState, useHistory, gotoStateActions) {
3330
- if (!state) return;
3331
-
3332
- var trace = get(this, 'allowStatechartTracing');
3333
-
3334
- // We do not want to enter states in the enter path until the pivot state has been reached. After
3335
- // the pivot state has been reached, then we can go ahead and actually enter states.
3336
- if (pivotState) {
3337
- if (state !== pivotState) {
3338
- this._traverseStatesToEnter(enterStatePath.pop(), enterStatePath, pivotState, useHistory, gotoStateActions);
3339
- } else {
3340
- this._traverseStatesToEnter(enterStatePath.pop(), enterStatePath, null, useHistory, gotoStateActions);
3341
- }
3342
- }
3343
-
3344
- // If no more explicit enter path instructions, then default to enter states based on
3345
- // other criteria
3346
- else if (!enterStatePath || enterStatePath.length === 0) {
3347
- var gotoStateAction = { action: SC.ENTER_STATE, state: state, currentState: false };
3348
- gotoStateActions.push(gotoStateAction);
3349
-
3350
- var initialSubstate = get(state, 'initialSubstate'),
3351
- historyState = get(state, 'historyState');
3352
-
3353
- // State has concurrent substates. Need to enter all of the substates
3354
- if (get(state, 'substatesAreConcurrent')) {
3355
- this._traverseConcurrentStatesToEnter(get(state, 'substates'), null, useHistory, gotoStateActions);
3356
- }
3357
-
3358
- // State has substates and we are instructed to recursively follow the state's
3359
- // history state if it has one.
3360
- else if (get(state, 'hasSubstates') && historyState && useHistory) {
3361
- this._traverseStatesToEnter(historyState, null, null, useHistory, gotoStateActions);
3362
- }
3363
-
3364
- // State has an initial substate to enter
3365
- else if (initialSubstate) {
3366
- if (initialSubstate instanceof SC.HistoryState) {
3367
- if (!useHistory) useHistory = get(initialSubstate, 'isRecursive');
3368
- initialSubstate = get(initialSubstate, 'state');
3369
- }
3370
- this._traverseStatesToEnter(initialSubstate, null, null, useHistory, gotoStateActions);
3371
- }
3372
-
3373
- // Looks like we hit the end of the road. Therefore the state has now become
3374
- // a current state of the statechart.
3375
- else {
3376
- gotoStateAction.currentState = true;
3377
- }
3378
- }
3379
-
3380
- // Still have an explicit enter path to follow, so keep moving through the path.
3381
- else if (enterStatePath.length > 0) {
3382
- gotoStateActions.push({ action: SC.ENTER_STATE, state: state });
3383
- var nextState = enterStatePath.pop();
3384
- this._traverseStatesToEnter(nextState, enterStatePath, null, useHistory, gotoStateActions);
3385
-
3386
- // We hit a state that has concurrent substates. Must go through each of the substates
3387
- // and enter them
3388
- if (get(state, 'substatesAreConcurrent')) {
3389
- this._traverseConcurrentStatesToEnter(get(state, 'substates'), nextState, useHistory, gotoStateActions);
3390
- }
3391
- }
3392
- },
3393
-
3394
- /** @override
3395
-
3396
- Returns true if the named value translates into an executable function on
3397
- any of the statechart's current states or the statechart itself.
3398
-
3399
- @param action {String} the property name to check
3400
- @returns {Boolean}
3401
- */
3402
- respondsTo: function(action) {
3403
- var currentStates = get(this, 'currentStates'),
3404
- len = get(currentStates, 'length'),
3405
- i = 0, state = null;
3406
-
3407
- for (; i < len; i += 1) {
3408
- state = currentStates.objectAt(i);
3409
- while (state) {
3410
- if (state.respondsToAction(action)) return true;
3411
- state = get(state, 'parentState');
3412
- }
3413
- }
3414
-
3415
- // None of the current states can respond. Now check the statechart itself
3416
- return SC.typeOf(this[action]) === "function";
3417
- },
3418
-
3419
- /** @override
3420
-
3421
- Attemps to handle a given action against any of the statechart's current states and the
3422
- statechart itself. If any current state can handle the action or the statechart itself can
3423
- handle the action then true is returned, otherwise false is returned.
3424
-
3425
- @param action {String} what to perform
3426
- @param arg1 {Object} Optional
3427
- @param arg2 {Object} Optional
3428
- @returns {Boolean} true if handled, false if not handled
3429
- */
3430
- tryToPerform: function(action, arg1, arg2) {
3431
- if (this.respondsTo(action)) {
3432
- if (SC.typeOf(this[action]) === "function") return (this[action](arg1, arg2) !== false);
3433
- else return !!this.sendAction(action, arg1, arg2);
3434
- } return false;
3435
- },
3436
-
3437
- /**
3438
- Used to invoke a method on current states. If the method can not be executed
3439
- on a current state, then the state's parent states will be tried in order
3440
- of closest ancestry.
3441
-
3442
- A few notes:
3443
-
3444
- 1. Calling this is not the same as calling sendAction or sendAction.
3445
- Rather, this should be seen as calling normal methods on a state that
3446
- will *not* call gotoState or gotoHistoryState.
3447
- 2. A state will only ever be invoked once per call. So if there are two
3448
- or more current states that have the same parent state, then that parent
3449
- state will only be invoked once if none of the current states are able
3450
- to invoke the given method.
3451
-
3452
- When calling this method, you are able to supply zero ore more arguments
3453
- that can be pass onto the method called on the states. As an example
3454
-
3455
- invokeStateMethod('render', context, firstTime);
3456
-
3457
- The above call will invoke the render method on the current states
3458
- and supply the context and firstTime arguments to the method.
3459
-
3460
- Because a statechart can have more than one current state and the method
3461
- invoked may return a value, the addition of a callback function may be provided
3462
- in order to handle the returned value for each state. As an example, let's say
3463
- we want to call a calculate method on the current states where the method
3464
- will return a value when invoked. We can handle the returned values like so:
3465
-
3466
- invokeStateMethod('calculate', value, function(state, result) {
3467
- // .. handle the result returned from calculate that was invoked
3468
- // on the given state
3469
- })
3470
-
3471
- If the method invoked does not return a value and a callback function is
3472
- supplied, then result value will simply be undefined. In all cases, if
3473
- a callback function is given, it must be the last value supplied to this
3474
- method.
3475
-
3476
- invokeStateMethod will return a value if only one state was able to have
3477
- the given method invoked on it, otherwise no value is returned.
3478
-
3479
- @param methodName {String} methodName a method name
3480
- @param args {Object...} Optional. any additional arguments
3481
- @param func {Function} Optional. a callback function. Must be the last
3482
- value supplied if provided.
3483
-
3484
- @returns a value if the number of current states is one, otherwise undefined
3485
- is returned. The value is the result of the method that got invoked
3486
- on a state.
3487
- */
3488
- invokeStateMethod: function(methodName, args, func) {
3489
- if (methodName === 'unknownAction') {
3490
- this.statechartLogError("can not invoke method unkownAction");
3491
- return;
3492
- }
3493
-
3494
- args = Array.prototype.slice.call(arguments); args.shift();
3495
-
3496
- var len = args.length,
3497
- arg = len > 0 ? args[len - 1] : null,
3498
- callback = SC.typeOf(arg) === "function" ? args.pop() : null,
3499
- currentStates = get(this, 'currentStates'),
3500
- i = 0, state = null, checkedStates = {},
3501
- method, result = undefined, calledStates = 0;
3502
-
3503
- len = get(currentStates, 'length');
3504
-
3505
- for (; i < len; i += 1) {
3506
- state = currentStates.objectAt(i);
3507
- while (state) {
3508
- if (checkedStates[get(state, 'fullPath')]) break;
3509
- checkedStates[get(state, 'fullPath')] = true;
3510
- method = state[methodName];
3511
- if (SC.typeOf(method) === "function" && !method.isActionHandler) {
3512
- result = method.apply(state, args);
3513
- if (callback) callback.call(this, state, result);
3514
- calledStates += 1;
3515
- break;
3516
- }
3517
- state = get(state, 'parentState');
3518
- }
3519
- }
3520
-
3521
- return calledStates === 1 ? result : undefined;
3522
- },
3523
-
3524
- /** @private
3525
-
3526
- Iterate over all the given concurrent states and enter them
3527
- */
3528
- _traverseConcurrentStatesToEnter: function(states, exclude, useHistory, gotoStateActions) {
3529
- var i = 0,
3530
- len = states.length,
3531
- state = null;
3532
-
3533
- for (; i < len; i += 1) {
3534
- state = states[i];
3535
- if (state !== exclude) this._traverseStatesToEnter(state, null, null, useHistory, gotoStateActions);
3536
- }
3537
- },
3538
-
3539
- /** @private
3540
-
3541
- Called by gotoState to flush a pending state transition at the front of the
3542
- pending queue.
3543
- */
3544
- _flushPendingStateTransition: function() {
3545
- if (!this._pendingStateTransitions) {
3546
- this.statechartLogError("Unable to flush pending state transition. _pendingStateTransitions is invalid");
3547
- return;
3548
- }
3549
- var pending = this._pendingStateTransitions.shift();
3550
- if (!pending) return;
3551
- this.gotoState(pending.state, pending.fromCurrentState, pending.useHistory, pending.context);
3552
- },
3553
-
3554
- /** @private
3555
-
3556
- Called by sendAction to flush a pending actions at the front of the pending
3557
- queue
3558
- */
3559
- _flushPendingSentActions: function() {
3560
- var pending = this._pendingSentActions.shift();
3561
- if (!pending) return null;
3562
- return this.sendAction(pending.action, pending.arg1, pending.arg2);
3563
- },
3564
-
3565
- /** @private */
3566
- _monitorIsActiveDidChange: function() {
3567
- if (get(this, 'monitorIsActive') && SC.none(get(this, 'monitor'))) {
3568
- set(this, 'monitor', SC.StatechartMonitor.create());
3569
- }
3570
- }.observes('monitorIsActive'),
3571
-
3572
- /** @private
3573
- Will process the arguments supplied to the gotoState method.
3574
-
3575
- TODO: Come back to this and refactor the code. It works, but it
3576
- could certainly be improved
3577
- */
3578
- _processGotoStateArgs: function(args) {
3579
- var processedArgs = {
3580
- state: null,
3581
- fromCurrentState: null,
3582
- useHistory: false,
3583
- context: null
3584
- },
3585
- len = null,
3586
- value = null;
3587
-
3588
- args = Array.prototype.slice.call(args);
3589
- args = args.filter(function(item) {
3590
- return !(item === undefined);
3591
- });
3592
- len = args.length;
3593
-
3594
- if (len < 1) return processedArgs;
3595
-
3596
- processedArgs.state = args[0];
3597
-
3598
- if (len === 2) {
3599
- value = args[1];
3600
- switch (SC.typeOf(value)) {
3601
- case "boolean":
3602
- processedArgs.useHistory = value;
3603
- break;
3604
- case "object":
3605
- if (!value.get || !value.get('isState')) {
3606
- processedArgs.context = value;
3607
- }
3608
- break;
3609
- default:
3610
- processedArgs.fromCurrentState = value;
3611
- }
3612
- }
3613
- else if (len === 3) {
3614
- value = args[1];
3615
- if (SC.typeOf(value) === "boolean") {
3616
- processedArgs.useHistory = value;
3617
- processedArgs.context = args[2];
3618
- } else {
3619
- processedArgs.fromCurrentState = value;
3620
- value = args[2];
3621
- if (SC.typeOf(value) === "boolean") {
3622
- processedArgs.useHistory = value;
3623
- } else {
3624
- processedArgs.context = value;
3625
- }
3626
- }
3627
- }
3628
- else {
3629
- processedArgs.fromCurrentState = args[1];
3630
- processedArgs.useHistory = args[2];
3631
- processedArgs.context = args[3];
3632
- }
3633
-
3634
- return processedArgs;
3635
- },
3636
-
3637
- /** @private
3638
-
3639
- Will return a newly constructed root state class. The root state will have substates added to
3640
- it based on properties found on this state that derive from a SC.State class. For the
3641
- root state to be successfully built, the following much be met:
3642
-
3643
- - The rootStateExample property must be defined with a class that derives from SC.State
3644
- - Either the initialState or statesAreConcurrent property must be set, but not both
3645
- - There must be one or more states that can be added to the root state
3646
-
3647
- */
3648
- _constructRootStateClass: function() {
3649
- var rsExampleKey = 'rootStateExample',
3650
- rsExample = get(this, rsExampleKey),
3651
- initialState = get(this, 'initialState'),
3652
- statesAreConcurrent = get(this, 'statesAreConcurrent'),
3653
- stateCount = 0,
3654
- key, value, valueIsFunc, attrs = {};
3655
-
3656
- if (SC.typeOf(rsExample) === "function" && rsExample.statePlugin) {
3657
- rsExample = rsExample.apply(this);
3658
- }
3659
-
3660
- if (!(SC.State.detect(rsExample) && rsExample.isClass)) {
3661
- this._logStatechartCreationError("Invalid root state example");
3662
- return null;
3663
- }
3664
-
3665
- if (statesAreConcurrent && !SC.empty(initialState)) {
3666
- this._logStatechartCreationError("Can not assign an initial state when states are concurrent");
3667
- } else if (statesAreConcurrent) {
3668
- attrs.substatesAreConcurrent = true;
3669
- } else if (SC.typeOf(initialState) === "string") {
3670
- attrs.initialSubstate = initialState;
3671
- } else {
3672
- this._logStatechartCreationError("Must either define initial state or assign states as concurrent");
3673
- return null;
3674
- }
3675
-
3676
- for (key in this) {
3677
- if (key === rsExampleKey) continue;
3678
-
3679
- value = this[key];
3680
- valueIsFunc = SC.typeOf(value) === "function";
3681
-
3682
- if (valueIsFunc && value.statePlugin) {
3683
- value = value.apply(this);
3684
- }
3685
-
3686
- if (SC.State.detect(value) && value.isClass && this[key] !== this.constructor) {
3687
- attrs[key] = value;
3688
- stateCount += 1;
3689
- }
3690
- }
3691
-
3692
- if (stateCount === 0) {
3693
- this._logStatechartCreationError("Must define one or more states");
3694
- return null;
3695
- }
3696
-
3697
- return rsExample.extend(attrs);
3698
- },
3699
-
3700
- /** @private */
3701
- _logStatechartCreationError: function(msg) {
3702
- SC.Logger.error("Unable to create statechart for %@: %@.".fmt(this, msg));
3703
- },
3704
-
3705
- /**
3706
- Used to log a statechart trace message
3707
- */
3708
- statechartLogTrace: function(msg) {
3709
- SC.Logger.info("%@: %@".fmt(get(this, 'statechartLogPrefix'), msg));
3710
- },
3711
-
3712
- /**
3713
- Used to log a statechart error message
3714
- */
3715
- statechartLogError: function(msg) {
3716
- SC.Logger.error("ERROR %@: %@".fmt(get(this, 'statechartLogPrefix'), msg));
3717
- },
3718
-
3719
- /**
3720
- Used to log a statechart warning message
3721
- */
3722
- statechartLogWarning: function(msg) {
3723
- if (get(this, 'suppressStatechartWarnings')) return;
3724
- SC.Logger.warn("WARN %@: %@".fmt(get(this, 'statechartLogPrefix'), msg));
3725
- },
3726
-
3727
- /** @property */
3728
- statechartLogPrefix: function() {
3729
- var className = this.constructor.toString(),
3730
- name = get(this, 'name'), prefix;
3731
-
3732
- if (SC.empty(name)) prefix = "%@<%@>".fmt(className, SC.guidFor(this));
3733
- else prefix = "%@<%@, %@>".fmt(className, name, SC.guidFor(this));
3734
-
3735
- return prefix;
3736
- }.property().cacheable(),
3737
-
3738
- /** @private @property */
3739
- allowStatechartTracing: function() {
3740
- var key = get(this, 'statechartTraceKey');
3741
- return get(this, key);
3742
- }.property().cacheable(),
3743
-
3744
- /** @private */
3745
- _statechartTraceDidChange: function() {
3746
- this.notifyPropertyChange('allowStatechartTracing');
3747
- }
3748
-
3749
- };
3750
-
3751
- SC.mixin(SC.StatechartManager, SC.DelegateSupport, SC.StatechartDelegate);
3752
-
3753
- /**
3754
- The default name given to a statechart's root state
3755
- */
3756
- SC.ROOT_STATE_NAME = "__ROOT_STATE__";
3757
-
3758
- /**
3759
- Constants used during the state transition process
3760
- */
3761
- SC.EXIT_STATE = 0;
3762
- SC.ENTER_STATE = 1;
3763
-
3764
- /**
3765
- A Startchart class.
3766
- */
3767
- SC.Statechart = SC.Object.extend(SC.StatechartManager, {
3768
- autoInitStatechart: false
3769
- });
3770
-
3771
- })({});
3772
-
3773
-
3774
- (function(exports) {
3775
- // ==========================================================================
3776
- // Project: SproutCore Statechart
3777
- // Copyright: ©2006-2011 Strobe Inc. and contributors.
3778
- // Portions ©2008-2011 Apple Inc. All rights reserved.
3779
- // License: Licensed under MIT license (see license.js)
3780
- // ==========================================================================
3781
- })({});
3782
-
3783
-
3784
- (function(exports) {
3785
- // ==========================================================================
3786
- // Project: SproutCore Runtime
3787
- // Copyright: ©2011 Strobe Inc. and contributors.
3788
- // License: Licensed under MIT license (see license.js)
3789
- // ==========================================================================
3790
- })({});