forked from olivernn/davis.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
davis.hashRouting.js
253 lines (227 loc) · 7.56 KB
/
davis.hashRouting.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/*!
* Davis - hashRouting
* Copyright (C) 2011 Oliver Nightingale
* MIT Licensed
*/
/**
* Davis.hash is an extension to Davis which swaps out the HTML5 pushState based routing with a location
* hash based approach. It implements the delegate methods of Davis.location however it has some limitations
* when compared with the default pushState based routing.
*
* It is not possible to have multiple history entries of the same location as the hashchange event only fires
* when the hash changes to a different value.
*
* This extension could be used to provide a fallback for browsers that do not support the HTML5 history api,
* however this extension does not take into account what happens when a hash link is used in a browser that
* supports HTML5 history.
*
* When this extension is instantiated, the browser will be redirected to the appropriate location scheme.
* For example, if the current URL is "http://www.example.com/foobar" but the browser doesn't support
* the history api, it will be redirected to "http://www.example.com/#!/foobar".
*
* If this extension is instantiated on a browser that supports the history API, then the hash routing will
* not take effect unless the forceHashRouting option is set to true
*
* To use this extension put this code at before starting your app.
*
* Davis.extend(Davis.hashRouting({ prefix: "!" }))
*
* The extention takes a number of options:
*
* `forceHashRouting` Setting this to true will force hash routing, even if the browser supports
* the history API. This defaults to false.
*
* `normalizeInitialLocation` - If this is true, then the browser will be redirected to the appropriate routing
* as soon as the extension is initialized. This defaults to true.
*
* `prefix`. This string will be prepended to all hash locations. This defaults to '!'
*
* @plugin
*/
Davis.hashRouting = function(options) {
if(!options)
options = {};
/**
* Configuring option defaults
*/
if(typeof(options.forceHashRouting) == 'undefined')
options.forceHashRouting = false;
if(typeof(options.prefix) == 'undefined')
options.prefix = "!";
if(typeof(options.normalizeInitialLocation) == 'undefined')
options.normalizeInitialLocation = "!";
/**
* options.location should be the same as window.location. This option is
* available for the sake of test mocking.
*/
if(typeof(options.location) == 'undefined')
options.location = window.location;
/**
* Storage for callbacks
* @private
*/
var callbacks = [];
/**
* Binds to the onhashchange event if it is available. If this event isn't support,
* a poller will be started to monitor the hash location for changes.
* @private
*/
var bindHashChange = function() {
if("onhashchange" in window) {
jQuery(window).bind('hashchange', checkForLocationChange);
} else {
setTimeout(locationPoller, pollerInterval);
}
};
var invokeCallbacks = function(request) {
Davis.utils.forEach(callbacks, function (callback) {
callback(request);
});
};
/**
* ## Davis.hashRouting.onChange
*
* Adds callbacks to the hashchange event. Davis.location delegates to this method when asinging
* callbacks for when the apps location has changed.
*
* @param {Function} the callback to be fired when the location has changed.
*/
var onChange = function(handler) {
callbacks.push(handler);
};
var hashLocationPattern = new RegExp("#" + options.prefix + "(.*)$")
/**
* ## Davis.hashRouting.current
*
* Returns the apps current location, which for hashRouting is pulled from the location.hash.
* Davis.location delegates to this method for getting the apps current location.
*/
var current = function() {
var match = options.location.hash.match(hashLocationPattern);
if(match)
return match[1];
else
return '/';
};
var onHashChange = function() {
var path = current();
if(path) {
invokeCallbacks( new Davis.Request({
fullPath: path,
method: "get"
})
);
}
};
/**
* ## normalize
*
* Give the hash and non-hash parts of a location, this returns a URL
* that will fit into the current routing schema.
*
* @private
*/
var normalize = function(usingHashRouting, hashLocation, normalLocation) {
if(hashLocation && hashLocation != '/') {
if(usingHashRouting) {
if(normalLocation != '/') {
/*
URL looks like: http://www.example.com/foo#!/bar
We want it to look like: http://www.example.com/#!/bar
*/
return "/#!" + hashLocation;
}
} else {
/*
URL looks like: http://www.example.com/foo#!/bar
We want it to look like: http://www.example.com/bar
*/
return hashLocation;
}
} else {
if(usingHashRouting && normalLocation != '/') {
/*
URL looks like: http://www.example.com/foo
We want it to look like: http://www.example.com/#!/foo
*/
return "/#!" + normalLocation;
}
}
/* URL is find the way it is. Don't forward anywhere */
return null;
};
/**
* On browsers that don't support the onhashchange event, we poll
* window.location to detect a change
*/
var pollerInterval = 500;
getLocation = function() {
return options.location.hash;
};
var lastPolledLocation = getLocation();
var checkForLocationChange = function() {
if(lastPolledLocation != getLocation()) {
lastPolledLocation = getLocation();
onHashChange();
}
};
var locationPoller = function() {
checkForLocationChange();
setTimeout(locationPoller, pollerInterval);
};
/**
* ## Davis.hashRouting.assign and replace
*
* Wrapper around location.assign and location.replace. This will also trigger all onChange callbacks
* that have been registered. Davis.location delegates to this method for setting the apps current
* location as well as replacing the current location for the app with a new location.
*
* @params {Request} the request to set the current location to.
*/
var wrapper = function(request, setter) {
setter("/#" + options.prefix + request.location());
lastPolledLocation = getLocation();
invokeCallbacks(request);
};
var assign = function(request) {
wrapper(request, function(string) {
// IE does not allow you to use call or apply on location.replace or location.assign.
// Keep this in mind if refactoring.
options.location.assign(string);
});
};
var replace = function(request) {
wrapper(request, function(string) {
options.location.replace(string);
});
};
return function(Davis) {
/*
* By default, don't enable this extension if the browser supports the history api.
*/
var usingHashRouting = !Davis.supported() || options.forceHashRouting;
/*
* Forward the web browser to a normalized version of the current URL, if necessary.
*/
if(options.normalizeInitialLocation) {
normalizedLocation = normalize(usingHashRouting, current(), options.location.pathname);
if(normalizedLocation) {
options.location.replace(normalizedLocation);
}
}
if(!usingHashRouting)
return;
/**
* ## Davis.supported
* Overwriting supported because this extension will support any browser.
*/
Davis.supported = function () { return true; }
Davis.location.setLocationDelegate({
assign: assign,
current: current,
replace: replace,
onChange: onChange
});
bindHashChange();
}
};