embient 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- })({});