Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/externals/javelin/lib/History.js
12242 views
1
/**
2
* @requires javelin-stratcom
3
* javelin-install
4
* javelin-uri
5
* javelin-util
6
* @provides javelin-history
7
* @javelin
8
*/
9
10
/**
11
* JX.History provides a stable interface for managing the browser's history
12
* stack. Whenever the history stack mutates, the "history:change" event is
13
* invoked via JX.Stratcom.
14
*
15
* Inspired by History Manager implemented by Christoph Pojer (@cpojer)
16
* @see https://github.com/cpojer/mootools-history
17
*/
18
JX.install('History', {
19
20
statics : {
21
22
// Mechanisms to @{JX.History.install} with (in preferred support order).
23
// The default behavior is to use the best supported mechanism.
24
DEFAULT : Infinity,
25
PUSHSTATE : 3,
26
HASHCHANGE : 2,
27
POLLING : 1,
28
29
// Last path parsed from the URL fragment.
30
_hash : null,
31
32
// Some browsers fire an extra "popstate" on initial page load, so we keep
33
// track of the initial path to normalize behavior (and not fire the extra
34
// event).
35
_initialPath : null,
36
37
// Mechanism used to interface with the browser history stack.
38
_mechanism : null,
39
40
/**
41
* Starts history management. This method must be invoked first before any
42
* other JX.History method can be used.
43
*
44
* @param int An optional mechanism used to interface with the browser
45
* history stack. If it is not supported, the next supported
46
* mechanism will be used.
47
*/
48
install : function(mechanism) {
49
if (__DEV__) {
50
if (JX.History._installed) {
51
JX.$E('JX.History.install(): can only install once.');
52
}
53
JX.History._installed = true;
54
}
55
56
mechanism = mechanism || JX.History.DEFAULT;
57
58
if (mechanism >= JX.History.PUSHSTATE && 'pushState' in history) {
59
JX.History._mechanism = JX.History.PUSHSTATE;
60
JX.History._initialPath = JX.History._getBasePath(location.href);
61
JX.Stratcom.listen('popstate', null, JX.History._handleChange);
62
} else if (mechanism >= JX.History.HASHCHANGE &&
63
'onhashchange' in window) {
64
JX.History._mechanism = JX.History.HASHCHANGE;
65
JX.Stratcom.listen('hashchange', null, JX.History._handleChange);
66
} else {
67
JX.History._mechanism = JX.History.POLLING;
68
setInterval(JX.History._handleChange, 200);
69
}
70
},
71
72
/**
73
* Get the name of the mechanism used to interface with the browser
74
* history stack.
75
*
76
* @return string Mechanism, either pushstate, hashchange, or polling.
77
*/
78
getMechanism : function() {
79
if (__DEV__) {
80
if (!JX.History._installed) {
81
JX.$E(
82
'JX.History.getMechanism(): ' +
83
'must call JX.History.install() first.');
84
}
85
}
86
return JX.History._mechanism;
87
},
88
89
/**
90
* Returns the path on top of the history stack.
91
*
92
* If the HTML5 History API is unavailable and an eligible path exists in
93
* the current URL fragment, the fragment is parsed for a path. Otherwise,
94
* the current URL path is returned.
95
*
96
* @return string Path on top of the history stack.
97
*/
98
getPath : function() {
99
if (__DEV__) {
100
if (!JX.History._installed) {
101
JX.$E(
102
'JX.History.getPath(): ' +
103
'must call JX.History.install() first.');
104
}
105
}
106
if (JX.History.getMechanism() === JX.History.PUSHSTATE) {
107
return JX.History._getBasePath(location.href);
108
} else {
109
var parsed = JX.History._parseFragment(location.hash);
110
return parsed || JX.History._getBasePath(location.href);
111
}
112
},
113
114
/**
115
* Pushes a path onto the history stack.
116
*
117
* @param string Path.
118
* @param wild State object for History API.
119
* @return void
120
*/
121
push : function(path, state) {
122
if (__DEV__) {
123
if (!JX.History._installed) {
124
JX.$E(
125
'JX.History.push(): ' +
126
'must call JX.History.install() first.');
127
}
128
}
129
if (JX.History.getMechanism() === JX.History.PUSHSTATE) {
130
if (JX.History._initialPath && JX.History._initialPath !== path) {
131
JX.History._initialPath = null;
132
}
133
history.pushState(state || null, null, path);
134
JX.History._fire(path, state);
135
} else {
136
location.hash = JX.History._composeFragment(path);
137
}
138
},
139
140
/**
141
* Modifies the path on top of the history stack.
142
*
143
* @param string Path.
144
* @return void
145
*/
146
replace : function(path) {
147
if (__DEV__) {
148
if (!JX.History._installed) {
149
JX.$E(
150
'JX.History.replace(): ' +
151
'must call JX.History.install() first.');
152
}
153
}
154
if (JX.History.getMechanism() === JX.History.PUSHSTATE) {
155
history.replaceState(null, null, path);
156
JX.History._fire(path);
157
} else {
158
var uri = JX.$U(location.href);
159
uri.setFragment(JX.History._composeFragment(path));
160
// Safari bug: "location.replace" does not respect changes made via
161
// setting "location.hash", so use "history.replaceState" if possible.
162
if ('replaceState' in history) {
163
history.replaceState(null, null, uri.toString());
164
JX.History._handleChange();
165
} else {
166
location.replace(uri.toString());
167
}
168
}
169
},
170
171
_handleChange : function(e) {
172
var path = JX.History.getPath();
173
var state = (e && e.getRawEvent().state);
174
175
if (JX.History.getMechanism() === JX.History.PUSHSTATE) {
176
if (path === JX.History._initialPath) {
177
JX.History._initialPath = null;
178
} else {
179
JX.History._fire(path, state);
180
}
181
} else {
182
if (path !== JX.History._hash) {
183
JX.History._hash = path;
184
JX.History._fire(path);
185
}
186
}
187
},
188
189
_fire : function(path, state) {
190
JX.Stratcom.invoke('history:change', null, {
191
path: JX.History._getBasePath(path),
192
state: state
193
});
194
},
195
196
_getBasePath : function(href) {
197
return JX.$U(href).setProtocol(null).setDomain(null).toString();
198
},
199
200
_composeFragment : function(path) {
201
path = JX.History._getBasePath(path);
202
// If the URL fragment does not change, the new path will not get pushed
203
// onto the stack. So we alternate the hash prefix to force a new state.
204
if (JX.History.getPath() === path) {
205
var hash = location.hash;
206
if (hash && hash.charAt(1) === '!') {
207
return '~!' + path;
208
}
209
}
210
return '!' + path;
211
},
212
213
_parseFragment : function(fragment) {
214
if (fragment) {
215
if (fragment.charAt(1) === '!') {
216
return fragment.substr(2);
217
} else if (fragment.substr(1, 2) === '~!') {
218
return fragment.substr(3);
219
}
220
}
221
return null;
222
}
223
224
}
225
226
});
227
228