rasputin 0.10.7 → 0.11.1

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