embient 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +3 -3
- data/lib/embient/version.rb +1 -1
- data/vendor/assets/javascripts/embient/{addons/ember-data.js → ember-data.js} +0 -0
- data/vendor/assets/javascripts/embient/ember-layout.js +204 -0
- data/vendor/assets/javascripts/embient/ember-routemanager.js +553 -0
- metadata +11 -12
- data/vendor/assets/javascripts/embient/addons/sproutcore-routing.js +0 -570
- data/vendor/assets/javascripts/embient/addons/sproutcore-statechart.js +0 -3790
- data/vendor/assets/javascripts/embient/require.js +0 -2052
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
embient (0.0.
|
4
|
+
embient (0.0.4)
|
5
5
|
emberjs-rails
|
6
6
|
rails (>= 3.1.0)
|
7
7
|
|
@@ -37,12 +37,12 @@ GEM
|
|
37
37
|
multi_json (~> 1.0)
|
38
38
|
arel (3.0.2)
|
39
39
|
builder (3.0.0)
|
40
|
-
emberjs-rails (2012.
|
40
|
+
emberjs-rails (2012.2.22)
|
41
41
|
hamlbars
|
42
42
|
rails (~> 3.1)
|
43
43
|
erubis (2.7.0)
|
44
44
|
haml (3.1.4)
|
45
|
-
hamlbars (2012.
|
45
|
+
hamlbars (2012.2.22)
|
46
46
|
haml
|
47
47
|
sprockets
|
48
48
|
tilt
|
data/lib/embient/version.rb
CHANGED
File without changes
|
@@ -0,0 +1,204 @@
|
|
1
|
+
|
2
|
+
(function(exports) {
|
3
|
+
var get = Ember.get, set = Ember.set, meta = Ember.meta;
|
4
|
+
|
5
|
+
/**
|
6
|
+
@class
|
7
|
+
This is an extension of Ember.ContainerView who's content
|
8
|
+
is restricted to a single child dicated by a property
|
9
|
+
on another view.
|
10
|
+
*/
|
11
|
+
Ember.Handlebars.FrameView = Ember.ContainerView.extend({
|
12
|
+
/**
|
13
|
+
The view corresponding to the containing template
|
14
|
+
*/
|
15
|
+
blockContainer: null,
|
16
|
+
|
17
|
+
/**
|
18
|
+
The property on the blockContainer which dictates the
|
19
|
+
contents of this container.
|
20
|
+
*/
|
21
|
+
childPath: 'content',
|
22
|
+
|
23
|
+
/**
|
24
|
+
Set up the listener on the view corresponding to the
|
25
|
+
containing template
|
26
|
+
*/
|
27
|
+
init: function() {
|
28
|
+
this._super();
|
29
|
+
var blockContainer = get(this, 'blockContainer');
|
30
|
+
blockContainer.addObserver(get(this, 'childPath'), this, 'contentDidUpdate');
|
31
|
+
this.contentDidUpdate();
|
32
|
+
},
|
33
|
+
|
34
|
+
/** @private
|
35
|
+
Fired when the property specified by contentPath changes on
|
36
|
+
the blockContainer
|
37
|
+
*/
|
38
|
+
contentDidUpdate: function() {
|
39
|
+
var blockContainer = this.get('blockContainer');
|
40
|
+
var view = blockContainer.getPath(get(this, 'childPath'));
|
41
|
+
ember_assert(view instanceof Ember.View, "dynamicView's content must be set to a subclass of Ember.View'");
|
42
|
+
var childViews = this.get('_childViews');
|
43
|
+
var len = childViews.get('length');
|
44
|
+
var views = view ? [view] : [];
|
45
|
+
childViews.replace(0, len, views);
|
46
|
+
},
|
47
|
+
|
48
|
+
destroy: function() {
|
49
|
+
this._super();
|
50
|
+
var blockContainer = get(this, 'blockContainer');
|
51
|
+
blockContainer.removeObserver(get(this, 'childPath'), this, 'contentDidUpdate');
|
52
|
+
}
|
53
|
+
});
|
54
|
+
|
55
|
+
Ember.Handlebars.dynamicViewHelper = Ember.Object.create({
|
56
|
+
|
57
|
+
findContainingBlock: function(view) {
|
58
|
+
if (!view) {
|
59
|
+
return view;
|
60
|
+
}
|
61
|
+
// We are using _parentView here, because we need to go through the virtual YieldViews, so we can treat them differently.
|
62
|
+
else if (view instanceof Ember.Handlebars.FrameView) {
|
63
|
+
var blockContainer = Ember.get(view, 'blockContainer');
|
64
|
+
return this.findContainingBlock(Ember.get(blockContainer, '_parentView'));
|
65
|
+
}
|
66
|
+
else if (view.isVirtual) {
|
67
|
+
return this.findContainingBlock(Ember.get(view, '_parentView'));
|
68
|
+
}
|
69
|
+
return view;
|
70
|
+
},
|
71
|
+
|
72
|
+
helper: function(name, options) {
|
73
|
+
// If no name is provided, use default and swap parameters
|
74
|
+
if (name && name.data && name.data.isRenderData) {
|
75
|
+
options = name;
|
76
|
+
name = false;
|
77
|
+
}
|
78
|
+
|
79
|
+
var blockContainer = Ember.Handlebars.dynamicViewHelper.findContainingBlock(options.data.view);
|
80
|
+
|
81
|
+
if(name) {
|
82
|
+
options.hash.childPath = name;
|
83
|
+
}
|
84
|
+
options.hash.blockContainer = blockContainer;
|
85
|
+
return Ember.Handlebars.helpers.view.call(this, 'Ember.Handlebars.FrameView', options);
|
86
|
+
}
|
87
|
+
|
88
|
+
});
|
89
|
+
|
90
|
+
Ember.Handlebars.registerHelper('dynamicView', Ember.Handlebars.dynamicViewHelper.helper);
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
})({});
|
95
|
+
|
96
|
+
|
97
|
+
(function(exports) {
|
98
|
+
var get = Ember.get, set = Ember.set;
|
99
|
+
|
100
|
+
/**
|
101
|
+
@class
|
102
|
+
A convenient extension of Ember.State which makes it easy
|
103
|
+
to swap out dynamic content during state transitions.
|
104
|
+
*/
|
105
|
+
Ember.LayoutState = Ember.State.extend({
|
106
|
+
/**
|
107
|
+
Convenience property to bind to.
|
108
|
+
*/
|
109
|
+
active: false,
|
110
|
+
|
111
|
+
isViewState: true,
|
112
|
+
|
113
|
+
/**
|
114
|
+
The property to set in the nearest parent view
|
115
|
+
when this state is entered.
|
116
|
+
*/
|
117
|
+
contentPath: 'content',
|
118
|
+
|
119
|
+
init: function() {
|
120
|
+
// This is currently experimental. We allow
|
121
|
+
// the view itself to define it's substates
|
122
|
+
// for better encapsulation. To do this, set
|
123
|
+
// the layoutStates property.
|
124
|
+
var viewClass = get(this, 'viewClass');
|
125
|
+
if(viewClass) {
|
126
|
+
var layoutStates = get(viewClass, 'proto').layoutStates;
|
127
|
+
set(this, 'states', layoutStates);
|
128
|
+
}
|
129
|
+
|
130
|
+
this._super();
|
131
|
+
},
|
132
|
+
|
133
|
+
enter: function(stateManager, transition) {
|
134
|
+
this._super(stateManager, transition);
|
135
|
+
|
136
|
+
set(this, 'active', true);
|
137
|
+
|
138
|
+
var viewClass = get(this, 'viewClass'), view;
|
139
|
+
ember_assert('view cannot be set directly, use viewClass instead', !this.get('view'));
|
140
|
+
view = this.createView(stateManager, transition);
|
141
|
+
this.set('view', view);
|
142
|
+
|
143
|
+
if (view) {
|
144
|
+
ember_assert('view must be an Ember.View', view instanceof Ember.View);
|
145
|
+
|
146
|
+
|
147
|
+
// if there is another view in the hierarchy then
|
148
|
+
// set its content
|
149
|
+
var parentView = get(this, 'parentView') || get(stateManager, 'rootView');
|
150
|
+
if(parentView) {
|
151
|
+
Ember.setPath(parentView, get(this, 'contentPath'), view);
|
152
|
+
}
|
153
|
+
// otherwise we just append to the rootElement on the
|
154
|
+
// state manager
|
155
|
+
else {
|
156
|
+
var root = stateManager.get('rootElement') || 'body';
|
157
|
+
view.appendTo(root);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
},
|
161
|
+
|
162
|
+
exit: function(stateManager, transition) {
|
163
|
+
var view = get(this, 'view');
|
164
|
+
|
165
|
+
var parentView = get(this, 'parentView') || get(stateManager, 'rootView');
|
166
|
+
if(parentView) {
|
167
|
+
Ember.setPath(parentView, get(this, 'contentPath'), null);
|
168
|
+
}
|
169
|
+
else {
|
170
|
+
view.remove();
|
171
|
+
}
|
172
|
+
set(this, 'view', null);
|
173
|
+
set(this, 'active', false);
|
174
|
+
this._super(stateManager, transition);
|
175
|
+
},
|
176
|
+
|
177
|
+
/**
|
178
|
+
Instantiates viewClass. This method can be
|
179
|
+
overridden.
|
180
|
+
*/
|
181
|
+
createView: function(stateManager, transition) {
|
182
|
+
var viewClass = get(this, 'viewClass');
|
183
|
+
ember_assert('viewClass must extend Ember.View', Ember.View.detect(viewClass));
|
184
|
+
return viewClass.create();
|
185
|
+
},
|
186
|
+
|
187
|
+
/**
|
188
|
+
Recursively find the nearest parent view
|
189
|
+
in the state hierarchy
|
190
|
+
*/
|
191
|
+
parentView: Ember.computed(function() {
|
192
|
+
var state = this.get('parentState');
|
193
|
+
while(state && !state.get('view')) {
|
194
|
+
state = state.get('parentState');
|
195
|
+
}
|
196
|
+
return state && state.get('view');
|
197
|
+
}).property()
|
198
|
+
});
|
199
|
+
|
200
|
+
})({});
|
201
|
+
|
202
|
+
|
203
|
+
(function(exports) {
|
204
|
+
})({});
|
@@ -0,0 +1,553 @@
|
|
1
|
+
|
2
|
+
(function(exports) {
|
3
|
+
var get = Ember.get, set = Ember.set;
|
4
|
+
|
5
|
+
/**
|
6
|
+
Whether the browser supports HTML5 history.
|
7
|
+
*/
|
8
|
+
var supportsHistory = !!(window.history && window.history.pushState);
|
9
|
+
|
10
|
+
/**
|
11
|
+
Whether the browser supports the hashchange event.
|
12
|
+
*/
|
13
|
+
var supportsHashChange = ('onhashchange' in window) && (document.documentMode === undefined || document.documentMode > 7);
|
14
|
+
|
15
|
+
/**
|
16
|
+
@class
|
17
|
+
Ember.RouteManager manages the browser location and changes states accordingly
|
18
|
+
to the current location. The location can be programmatically set as follows:
|
19
|
+
|
20
|
+
routeManager.set('location', 'notes/edit/4');
|
21
|
+
|
22
|
+
Ember.RouteManager also supports HTML5 history, which uses a '/' instead of a
|
23
|
+
'#' in the URLs, so that all your website's URLs are consistent.
|
24
|
+
*/
|
25
|
+
Ember.RouteManager = Ember.StateManager.extend({
|
26
|
+
|
27
|
+
/**
|
28
|
+
Set this property to true if you want to use HTML5 history, if available on
|
29
|
+
the browser, instead of the location hash.
|
30
|
+
|
31
|
+
HTML 5 history uses the history.pushState method and the window's popstate
|
32
|
+
event.
|
33
|
+
|
34
|
+
By default it is false, so your URLs will look like:
|
35
|
+
|
36
|
+
http://domain.tld/my_app#notes/edit/4
|
37
|
+
|
38
|
+
If set to true and the browser supports pushState(), your URLs will look
|
39
|
+
like:
|
40
|
+
|
41
|
+
http://domain.tld/my_app/notes/edit/4
|
42
|
+
|
43
|
+
You will also need to make sure that baseURI is properly configured, as
|
44
|
+
well as your server so that your routes are properly pointing to your
|
45
|
+
SproutCore application.
|
46
|
+
|
47
|
+
@see http://dev.w3.org/html5/spec/history.html#the-history-interface
|
48
|
+
@property
|
49
|
+
@type {Boolean}
|
50
|
+
*/
|
51
|
+
wantsHistory: false,
|
52
|
+
|
53
|
+
/**
|
54
|
+
A read-only boolean indicating whether or not HTML5 history is used. Based
|
55
|
+
on the value of wantsHistory and the browser's support for pushState.
|
56
|
+
|
57
|
+
@see wantsHistory
|
58
|
+
@property
|
59
|
+
@type {Boolean}
|
60
|
+
*/
|
61
|
+
usesHistory: null,
|
62
|
+
|
63
|
+
/**
|
64
|
+
The base URI used to resolve routes (which are relative URLs). Only used
|
65
|
+
when usesHistory is equal to true.
|
66
|
+
|
67
|
+
The build tools automatically configure this value if you have the
|
68
|
+
html5_history option activated in the Buildfile:
|
69
|
+
|
70
|
+
config :my_app, :html5_history => true
|
71
|
+
|
72
|
+
Alternatively, it uses by default the value of the href attribute of the
|
73
|
+
<base> tag of the HTML document. For example:
|
74
|
+
|
75
|
+
<base href="http://domain.tld/my_app">
|
76
|
+
|
77
|
+
The value can also be customized before or during the exectution of the
|
78
|
+
main() method.
|
79
|
+
|
80
|
+
@see http://www.w3.org/TR/html5/semantics.html#the-base-element
|
81
|
+
@property
|
82
|
+
@type {String}
|
83
|
+
*/
|
84
|
+
baseURI: document.baseURI,
|
85
|
+
|
86
|
+
/** @private
|
87
|
+
A boolean value indicating whether or not the ping method has been called
|
88
|
+
to setup the Ember.routes.
|
89
|
+
|
90
|
+
@property
|
91
|
+
@type {Boolean}
|
92
|
+
*/
|
93
|
+
_didSetup: false,
|
94
|
+
|
95
|
+
/** @private
|
96
|
+
Internal representation of the current location hash.
|
97
|
+
|
98
|
+
@property
|
99
|
+
@type {String}
|
100
|
+
*/
|
101
|
+
_location: null,
|
102
|
+
|
103
|
+
/** @private
|
104
|
+
Internal method used to extract and merge the parameters of a URL.
|
105
|
+
|
106
|
+
@returns {Hash}
|
107
|
+
*/
|
108
|
+
_extractParametersAndRoute: function(obj) {
|
109
|
+
var params = {}, route = obj.route || '', separator, parts, i, len, crumbs, key;
|
110
|
+
separator = (route.indexOf('?') < 0 && route.indexOf('&') >= 0) ? '&' : '?';
|
111
|
+
parts = route.split(separator);
|
112
|
+
route = parts[0];
|
113
|
+
if(parts.length === 1) {
|
114
|
+
parts = [];
|
115
|
+
} else if(parts.length === 2) {
|
116
|
+
parts = parts[1].split('&');
|
117
|
+
} else if(parts.length > 2) {
|
118
|
+
parts.shift();
|
119
|
+
}
|
120
|
+
|
121
|
+
// extract the parameters from the route string
|
122
|
+
len = parts.length;
|
123
|
+
for( i = 0; i < len; ++i) {
|
124
|
+
crumbs = parts[i].split('=');
|
125
|
+
params[crumbs[0]] = crumbs[1];
|
126
|
+
}
|
127
|
+
|
128
|
+
// overlay any parameter passed in obj
|
129
|
+
for(key in obj) {
|
130
|
+
if(obj.hasOwnProperty(key) && key !== 'route') {
|
131
|
+
params[key] = '' + obj[key];
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
// build the route
|
136
|
+
parts = [];
|
137
|
+
for(key in params) {
|
138
|
+
parts.push([key, params[key]].join('='));
|
139
|
+
}
|
140
|
+
params.params = separator + parts.join('&');
|
141
|
+
params.route = route;
|
142
|
+
|
143
|
+
return params;
|
144
|
+
},
|
145
|
+
|
146
|
+
/**
|
147
|
+
The current location hash. It is the part in the browser's location after
|
148
|
+
the '#' mark.
|
149
|
+
|
150
|
+
@property
|
151
|
+
@type {String}
|
152
|
+
*/
|
153
|
+
location: Ember.computed(function(key, value) {
|
154
|
+
this._skipRoute = false;
|
155
|
+
return this._extractLocation(key, value);
|
156
|
+
}).property(),
|
157
|
+
|
158
|
+
_extractLocation: function(key, value) {
|
159
|
+
var crumbs, encodedValue;
|
160
|
+
|
161
|
+
if(value !== undefined) {
|
162
|
+
if(value === null) {
|
163
|
+
value = '';
|
164
|
+
}
|
165
|
+
|
166
|
+
if( typeof (value) === 'object') {
|
167
|
+
crumbs = this._extractParametersAndRoute(value);
|
168
|
+
value = crumbs.route + crumbs.params;
|
169
|
+
}
|
170
|
+
|
171
|
+
if(!this._skipPush && (!Ember.empty(value) || (this._location && this._location !== value))) {
|
172
|
+
encodedValue = encodeURI(value);
|
173
|
+
|
174
|
+
if(this.usesHistory) {
|
175
|
+
if(encodedValue.length > 0) {
|
176
|
+
encodedValue = '/' + encodedValue;
|
177
|
+
}
|
178
|
+
window.history.pushState(null, null, get(this, 'baseURI') + encodedValue);
|
179
|
+
} else if(encodedValue.length > 0 || window.location.hash.length > 0) {
|
180
|
+
window.location.hash = encodedValue;
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
this._location = value;
|
185
|
+
}
|
186
|
+
|
187
|
+
return this._location;
|
188
|
+
},
|
189
|
+
|
190
|
+
updateLocation: function(loc) {
|
191
|
+
this._skipRoute = true;
|
192
|
+
return this._extractLocation('location', loc);
|
193
|
+
},
|
194
|
+
|
195
|
+
/**
|
196
|
+
You usually don't need to call this method. It is done automatically after
|
197
|
+
the application has been initialized.
|
198
|
+
|
199
|
+
It registers for the hashchange event if available. If not, it creates a
|
200
|
+
timer that looks for location changes every 150ms.
|
201
|
+
*/
|
202
|
+
ping: function() {
|
203
|
+
if(!this._didSetup) {
|
204
|
+
this._didSetup = true;
|
205
|
+
var state;
|
206
|
+
|
207
|
+
if(get(this, 'wantsHistory') && supportsHistory) {
|
208
|
+
this.usesHistory = true;
|
209
|
+
|
210
|
+
// Move any hash state to url state
|
211
|
+
// TODO: Make sure we have a hash before adding slash
|
212
|
+
state = window.location.hash.slice(1);
|
213
|
+
if(state.length > 0) {
|
214
|
+
state = '/' + state;
|
215
|
+
window.history.replaceState(null, null, get(this, 'baseURI') + state);
|
216
|
+
}
|
217
|
+
|
218
|
+
this.popState();
|
219
|
+
this.popState = jQuery.proxy(this.popState, this);
|
220
|
+
jQuery(window).bind('popstate', this.popState);
|
221
|
+
|
222
|
+
} else {
|
223
|
+
this.usesHistory = false;
|
224
|
+
|
225
|
+
if(get(this, 'wantsHistory')) {
|
226
|
+
// Move any url state to hash
|
227
|
+
var base = get(this, 'baseURI');
|
228
|
+
var loc = (base.charAt(0) === '/') ? document.location.pathname : document.location.href.replace(document.location.hash, '');
|
229
|
+
state = loc.slice(base.length + 1);
|
230
|
+
if(state.length > 0) {
|
231
|
+
window.location.href = base + '#' + state;
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
if(supportsHashChange) {
|
236
|
+
this.hashChange();
|
237
|
+
this.hashChange = jQuery.proxy(this.hashChange, this);
|
238
|
+
jQuery(window).bind('hashchange', this.hashChange);
|
239
|
+
|
240
|
+
} else {
|
241
|
+
// we don't use a Ember.Timer because we don't want
|
242
|
+
// a run loop to be triggered at each ping
|
243
|
+
var invokeHashChange = function() {
|
244
|
+
this.hashChange();
|
245
|
+
this._timerId = setTimeout(invokeHashChange, 100);
|
246
|
+
};
|
247
|
+
|
248
|
+
invokeHashChange();
|
249
|
+
}
|
250
|
+
}
|
251
|
+
}
|
252
|
+
},
|
253
|
+
|
254
|
+
destroy: function() {
|
255
|
+
if(this._didSetup) {
|
256
|
+
if(get(this, 'wantsHistory') && supportsHistory) {
|
257
|
+
jQuery(window).unbind('popstate', this.popState);
|
258
|
+
} else {
|
259
|
+
if(supportsHashChange) {
|
260
|
+
jQuery(window).unbind('hashchange', this.hashChange);
|
261
|
+
} else {
|
262
|
+
clearTimeout(this._timerId);
|
263
|
+
}
|
264
|
+
}
|
265
|
+
}
|
266
|
+
this._super();
|
267
|
+
},
|
268
|
+
|
269
|
+
/**
|
270
|
+
Ember.RouteManager currently automatically starts listening
|
271
|
+
for browser location changes when created.
|
272
|
+
*/
|
273
|
+
init: function() {
|
274
|
+
this._super();
|
275
|
+
if(!this._didSetup) {
|
276
|
+
this.ping();
|
277
|
+
}
|
278
|
+
},
|
279
|
+
|
280
|
+
/**
|
281
|
+
Observer of the 'location' property that calls the correct route handler
|
282
|
+
when the location changes.
|
283
|
+
*/
|
284
|
+
locationDidChange: Ember.observer(function() {
|
285
|
+
this.trigger();
|
286
|
+
}, 'location'),
|
287
|
+
|
288
|
+
/**
|
289
|
+
Triggers a route even if already in that route (does change the location, if
|
290
|
+
it is not already changed, as well).
|
291
|
+
|
292
|
+
If the location is not the same as the supplied location, this simply lets
|
293
|
+
"location" handle it (which ends up coming back to here).
|
294
|
+
*/
|
295
|
+
trigger: function() {
|
296
|
+
var location = get(this, 'location'), params, route;
|
297
|
+
params = this._extractParametersAndRoute({
|
298
|
+
route: location
|
299
|
+
});
|
300
|
+
location = params.route;
|
301
|
+
delete params.route;
|
302
|
+
delete params.params;
|
303
|
+
|
304
|
+
var result = this.getState(location, params);
|
305
|
+
if(result) {
|
306
|
+
set(this, 'params', result.params);
|
307
|
+
|
308
|
+
// We switch states in two phases. The point of this is to handle
|
309
|
+
// parameter-only location changes. This will correspond to the same
|
310
|
+
// state path in the manager, but states with parts with changed
|
311
|
+
// parameters should be re-entered:
|
312
|
+
|
313
|
+
// 1. We go to the earliest clean state. This prevents
|
314
|
+
// unnecessary transitions.
|
315
|
+
if(result.cleanStates.length > 0) {
|
316
|
+
var cleanState = result.cleanStates.join('.');
|
317
|
+
this.goToState(cleanState);
|
318
|
+
}
|
319
|
+
// 2. We transition to the dirty state. This forces dirty
|
320
|
+
// states to be transitioned.
|
321
|
+
if(result.dirtyStates.length > 0) {
|
322
|
+
var dirtyState = result.cleanStates.concat(result.dirtyStates).join('.');
|
323
|
+
this.goToState(dirtyState);
|
324
|
+
}
|
325
|
+
} else {
|
326
|
+
var states = get(this, 'states');
|
327
|
+
if(states && get(states, "404")) {
|
328
|
+
this.goToState("404");
|
329
|
+
}
|
330
|
+
}
|
331
|
+
},
|
332
|
+
|
333
|
+
getState: function(route, params) {
|
334
|
+
var parts = route.split('/');
|
335
|
+
parts = parts.filter(function(part) {
|
336
|
+
return part !== '';
|
337
|
+
});
|
338
|
+
|
339
|
+
return this._findState(parts, this, [], [], params, false);
|
340
|
+
},
|
341
|
+
|
342
|
+
/** @private
|
343
|
+
Recursive helper that the state and the params if a match is found
|
344
|
+
*/
|
345
|
+
_findState: function(parts, state, cleanStates, dirtyStates, params) {
|
346
|
+
parts = Ember.copy(parts);
|
347
|
+
|
348
|
+
var hasChildren = false, name, states, childState;
|
349
|
+
// sort desc based on priority
|
350
|
+
states = [];
|
351
|
+
for(name in state.states) {
|
352
|
+
// 404 state is special and not matched
|
353
|
+
childState = state.states[name];
|
354
|
+
if(name == "404" || !Ember.State.detect(childState) && !( childState instanceof Ember.State)) {
|
355
|
+
continue;
|
356
|
+
}
|
357
|
+
states.push({
|
358
|
+
name: name,
|
359
|
+
state: childState
|
360
|
+
});
|
361
|
+
}
|
362
|
+
states = states.sort(function(a, b) {
|
363
|
+
return (b.state.get('priority') || 0) - (a.state.get('priority') || 0);
|
364
|
+
});
|
365
|
+
|
366
|
+
for(var i = 0; i < states.length; i++) {
|
367
|
+
name = states[i].name;
|
368
|
+
childState = states[i].state;
|
369
|
+
if(!( childState instanceof Ember.State)) {
|
370
|
+
continue;
|
371
|
+
}
|
372
|
+
hasChildren = true;
|
373
|
+
|
374
|
+
var result = this._matchState(parts, childState, params);
|
375
|
+
if(!result) {
|
376
|
+
continue;
|
377
|
+
}
|
378
|
+
|
379
|
+
var newParams = Ember.copy(params);
|
380
|
+
jQuery.extend(newParams, result.params);
|
381
|
+
|
382
|
+
var dirty = dirtyStates.length > 0 || result.dirty;
|
383
|
+
var newCleanStates = cleanStates;
|
384
|
+
var newDirtyStates = dirtyStates;
|
385
|
+
if(dirty) {
|
386
|
+
newDirtyStates = Ember.copy(newDirtyStates);
|
387
|
+
newDirtyStates.push(name);
|
388
|
+
} else {
|
389
|
+
newCleanStates = Ember.copy(newCleanStates);
|
390
|
+
newCleanStates.push(name);
|
391
|
+
}
|
392
|
+
result = this._findState(result.parts, childState, newCleanStates, newDirtyStates, newParams);
|
393
|
+
if(result) {
|
394
|
+
return result;
|
395
|
+
}
|
396
|
+
}
|
397
|
+
|
398
|
+
if(!hasChildren && parts.length === 0) {
|
399
|
+
return {
|
400
|
+
state: state,
|
401
|
+
params: params,
|
402
|
+
cleanStates: cleanStates,
|
403
|
+
dirtyStates: dirtyStates
|
404
|
+
};
|
405
|
+
}
|
406
|
+
return null;
|
407
|
+
},
|
408
|
+
|
409
|
+
/** @private
|
410
|
+
Check if a state accepts the parts with the params
|
411
|
+
|
412
|
+
Returns the remaining parts as well as merged params if
|
413
|
+
the state accepts.
|
414
|
+
|
415
|
+
Will also set the dirty flag if the route is the same but
|
416
|
+
the parameters have changed
|
417
|
+
*/
|
418
|
+
_matchState: function(parts, state, params) {
|
419
|
+
parts = Ember.copy(parts);
|
420
|
+
params = Ember.copy(params);
|
421
|
+
var dirty = false;
|
422
|
+
var route = get(state, 'route');
|
423
|
+
if(route) {
|
424
|
+
var partDefinitions;
|
425
|
+
// route could be either a string or regex
|
426
|
+
if( typeof route == "string") {
|
427
|
+
partDefinitions = route.split('/');
|
428
|
+
} else if( route instanceof RegExp) {
|
429
|
+
partDefinitions = [route];
|
430
|
+
} else {
|
431
|
+
ember_assert("route must be either a string or regexp", false);
|
432
|
+
}
|
433
|
+
|
434
|
+
for(var i = 0; i < partDefinitions.length; i++) {
|
435
|
+
if(parts.length === 0) {
|
436
|
+
return false;
|
437
|
+
}
|
438
|
+
var part = parts.shift();
|
439
|
+
var partDefinition = partDefinitions[i];
|
440
|
+
var partParams = this._matchPart(partDefinition, part);
|
441
|
+
if(!partParams) {
|
442
|
+
return false;
|
443
|
+
}
|
444
|
+
|
445
|
+
var oldParams = this.get('params') || {};
|
446
|
+
for(var param in partParams) {
|
447
|
+
dirty = dirty || (oldParams[param] != partParams[param]);
|
448
|
+
}
|
449
|
+
|
450
|
+
jQuery.extend(params, partParams);
|
451
|
+
}
|
452
|
+
}
|
453
|
+
|
454
|
+
if(Ember.typeOf(state.willAccept) == 'function') {
|
455
|
+
if(!state.willAccept(params)) {
|
456
|
+
return false;
|
457
|
+
}
|
458
|
+
}
|
459
|
+
|
460
|
+
return {
|
461
|
+
parts: parts,
|
462
|
+
params: params,
|
463
|
+
dirty: dirty
|
464
|
+
};
|
465
|
+
},
|
466
|
+
|
467
|
+
/** @private
|
468
|
+
Returns params if the part matches the partDefinition
|
469
|
+
*/
|
470
|
+
_matchPart: function(partDefinition, part) {
|
471
|
+
// Handle string parts
|
472
|
+
if( typeof partDefinition == "string") {
|
473
|
+
|
474
|
+
switch (partDefinition.slice(0, 1)) {
|
475
|
+
// 1. dynamic routes
|
476
|
+
case ':':
|
477
|
+
var name = partDefinition.slice(1, partDefinition.length);
|
478
|
+
var params = {};
|
479
|
+
params[name] = part;
|
480
|
+
return params;
|
481
|
+
|
482
|
+
// 2. wildcard routes
|
483
|
+
case '*':
|
484
|
+
return {};
|
485
|
+
|
486
|
+
// 3. static routes
|
487
|
+
default:
|
488
|
+
if(partDefinition == part)
|
489
|
+
return {};
|
490
|
+
break;
|
491
|
+
}
|
492
|
+
|
493
|
+
return false;
|
494
|
+
}
|
495
|
+
|
496
|
+
// Handle RegExp parts
|
497
|
+
return partDefinition.test(part) ? {} : false;
|
498
|
+
},
|
499
|
+
|
500
|
+
/**
|
501
|
+
Event handler for the hashchange event. Called automatically by the browser
|
502
|
+
if it supports the hashchange event, or by our timer if not.
|
503
|
+
*/
|
504
|
+
hashChange: function(event) {
|
505
|
+
var loc = window.location.hash;
|
506
|
+
var routes = this;
|
507
|
+
|
508
|
+
// Remove the '#' prefix
|
509
|
+
loc = (loc && loc.length > 0) ? loc.slice(1, loc.length) : '';
|
510
|
+
|
511
|
+
if(!jQuery.browser.mozilla) {
|
512
|
+
// because of bug https://bugzilla.mozilla.org/show_bug.cgi?id=483304
|
513
|
+
loc = decodeURI(loc);
|
514
|
+
}
|
515
|
+
|
516
|
+
if(get(routes, 'location') !== loc && !routes._skipRoute) {
|
517
|
+
Ember.run.once(function() {
|
518
|
+
routes._skipPush = true;
|
519
|
+
set(routes, 'location', loc);
|
520
|
+
routes._skipPush = false;
|
521
|
+
});
|
522
|
+
|
523
|
+
}
|
524
|
+
routes._skipRoute = false;
|
525
|
+
},
|
526
|
+
|
527
|
+
popState: function(event) {
|
528
|
+
var routes = this;
|
529
|
+
var base = get(routes, 'baseURI'), loc = (base.charAt(0) === '/') ? document.location.pathname : document.location.href;
|
530
|
+
|
531
|
+
if(loc.slice(0, base.length) === base) {
|
532
|
+
// Remove the base prefix and the extra '/'
|
533
|
+
loc = loc.slice(base.length + 1, loc.length);
|
534
|
+
|
535
|
+
if(get(routes, 'location') !== loc && !routes._skipRoute) {
|
536
|
+
Ember.run.once(function() {
|
537
|
+
routes._skipPush = true;
|
538
|
+
set(routes, 'location', loc);
|
539
|
+
routes._skipPush = false;
|
540
|
+
});
|
541
|
+
|
542
|
+
}
|
543
|
+
}
|
544
|
+
routes._skipRoute = false;
|
545
|
+
}
|
546
|
+
|
547
|
+
});
|
548
|
+
|
549
|
+
})({});
|
550
|
+
|
551
|
+
|
552
|
+
(function(exports) {
|
553
|
+
})({});
|