embient 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
})({});
|