wiselinks 0.7.3 → 1.0.0

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.
@@ -1,4 +1,5 @@
1
1
  #= require_tree ./lib
2
+ # require_tree ./lib_old
2
3
 
3
4
  #= require _page
4
5
  #= require _link
@@ -10,6 +11,7 @@ class Wiselinks
10
11
 
11
12
  @options = $.extend(this._defaults(), @options)
12
13
  if this.enabled()
14
+ if @options.disable_suid then History.options.disableSuid = true
13
15
  @page = new _Wiselinks.Page($target, @options)
14
16
 
15
17
  enabled: ->
@@ -20,8 +22,9 @@ class Wiselinks
20
22
 
21
23
  reload: () ->
22
24
  @page.reload()
23
-
25
+
24
26
  _defaults: ->
27
+ disable_suid: true
25
28
  html4: true
26
29
  html4_root_path: '/'
27
30
  html4_normalize_path: true
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Wiselinks
4
4
  module Version
5
- MAJOR = 0
6
- MINOR = 7
7
- PATCH = 3
5
+ MAJOR = 1
6
+ MINOR = 0
7
+ PATCH = 0
8
8
  BUILD = nil
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
data/wiselinks.gemspec CHANGED
@@ -7,13 +7,13 @@ require 'wiselinks/version'
7
7
  Gem::Specification.new do |gem|
8
8
  gem.name = 'wiselinks'
9
9
  gem.version = Wiselinks::Version::STRING
10
- gem.authors = ['Igor Alexandrov', 'Alexey Solilin', 'Julia Egorova', 'Alexandr Borisov']
11
- gem.email = 'igor.alexandrov@gmail.com'
10
+ gem.authors = ['Igor Alexandrov', 'Alexey Solilin', 'Julia Egorova', 'Alexandr Borisov']
11
+ gem.email = 'igor.alexandrov@gmail.com'
12
12
  gem.summary = 'Wiselinks makes following links and submitting some forms in your web application smarter and faster'
13
13
  gem.homepage = 'http://github.com/igor-alexandrov/wiselinks'
14
14
  gem.licenses = ['MIT']
15
15
 
16
- gem.files = `git ls-files`.split($/)
16
+ gem.files = `git ls-files | grep -v 'build/*'`.split($/)
17
17
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
metadata CHANGED
@@ -1,7 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wiselinks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 1.0.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Igor Alexandrov
@@ -11,25 +12,28 @@ authors:
11
12
  autorequire:
12
13
  bindir: bin
13
14
  cert_chain: []
14
- date: 2013-07-04 00:00:00.000000000 Z
15
+ date: 2013-07-10 00:00:00.000000000 Z
15
16
  dependencies:
16
17
  - !ruby/object:Gem::Dependency
17
18
  name: rspec
18
19
  requirement: !ruby/object:Gem::Requirement
20
+ none: false
19
21
  requirements:
20
- - - '>='
22
+ - - ! '>='
21
23
  - !ruby/object:Gem::Version
22
24
  version: '0'
23
25
  type: :development
24
26
  prerelease: false
25
27
  version_requirements: !ruby/object:Gem::Requirement
28
+ none: false
26
29
  requirements:
27
- - - '>='
30
+ - - ! '>='
28
31
  - !ruby/object:Gem::Version
29
32
  version: '0'
30
33
  - !ruby/object:Gem::Dependency
31
34
  name: webmock
32
35
  requirement: !ruby/object:Gem::Requirement
36
+ none: false
33
37
  requirements:
34
38
  - - ~>
35
39
  - !ruby/object:Gem::Version
@@ -37,6 +41,7 @@ dependencies:
37
41
  type: :development
38
42
  prerelease: false
39
43
  version_requirements: !ruby/object:Gem::Requirement
44
+ none: false
40
45
  requirements:
41
46
  - - ~>
42
47
  - !ruby/object:Gem::Version
@@ -44,160 +49,183 @@ dependencies:
44
49
  - !ruby/object:Gem::Dependency
45
50
  name: shoulda
46
51
  requirement: !ruby/object:Gem::Requirement
52
+ none: false
47
53
  requirements:
48
- - - '>='
54
+ - - ! '>='
49
55
  - !ruby/object:Gem::Version
50
56
  version: '0'
51
57
  type: :development
52
58
  prerelease: false
53
59
  version_requirements: !ruby/object:Gem::Requirement
60
+ none: false
54
61
  requirements:
55
- - - '>='
62
+ - - ! '>='
56
63
  - !ruby/object:Gem::Version
57
64
  version: '0'
58
65
  - !ruby/object:Gem::Dependency
59
66
  name: simplecov
60
67
  requirement: !ruby/object:Gem::Requirement
68
+ none: false
61
69
  requirements:
62
- - - '>='
70
+ - - ! '>='
63
71
  - !ruby/object:Gem::Version
64
72
  version: '0'
65
73
  type: :development
66
74
  prerelease: false
67
75
  version_requirements: !ruby/object:Gem::Requirement
76
+ none: false
68
77
  requirements:
69
- - - '>='
78
+ - - ! '>='
70
79
  - !ruby/object:Gem::Version
71
80
  version: '0'
72
81
  - !ruby/object:Gem::Dependency
73
82
  name: coveralls
74
83
  requirement: !ruby/object:Gem::Requirement
84
+ none: false
75
85
  requirements:
76
- - - '>='
86
+ - - ! '>='
77
87
  - !ruby/object:Gem::Version
78
88
  version: '0'
79
89
  type: :development
80
90
  prerelease: false
81
91
  version_requirements: !ruby/object:Gem::Requirement
92
+ none: false
82
93
  requirements:
83
- - - '>='
94
+ - - ! '>='
84
95
  - !ruby/object:Gem::Version
85
96
  version: '0'
86
97
  - !ruby/object:Gem::Dependency
87
98
  name: rake
88
99
  requirement: !ruby/object:Gem::Requirement
100
+ none: false
89
101
  requirements:
90
- - - '>='
102
+ - - ! '>='
91
103
  - !ruby/object:Gem::Version
92
104
  version: '0'
93
105
  type: :development
94
106
  prerelease: false
95
107
  version_requirements: !ruby/object:Gem::Requirement
108
+ none: false
96
109
  requirements:
97
- - - '>='
110
+ - - ! '>='
98
111
  - !ruby/object:Gem::Version
99
112
  version: '0'
100
113
  - !ruby/object:Gem::Dependency
101
114
  name: bundler
102
115
  requirement: !ruby/object:Gem::Requirement
116
+ none: false
103
117
  requirements:
104
- - - '>='
118
+ - - ! '>='
105
119
  - !ruby/object:Gem::Version
106
120
  version: '0'
107
121
  type: :development
108
122
  prerelease: false
109
123
  version_requirements: !ruby/object:Gem::Requirement
124
+ none: false
110
125
  requirements:
111
- - - '>='
126
+ - - ! '>='
112
127
  - !ruby/object:Gem::Version
113
128
  version: '0'
114
129
  - !ruby/object:Gem::Dependency
115
130
  name: sqlite3
116
131
  requirement: !ruby/object:Gem::Requirement
132
+ none: false
117
133
  requirements:
118
- - - '>='
134
+ - - ! '>='
119
135
  - !ruby/object:Gem::Version
120
136
  version: '0'
121
137
  type: :development
122
138
  prerelease: false
123
139
  version_requirements: !ruby/object:Gem::Requirement
140
+ none: false
124
141
  requirements:
125
- - - '>='
142
+ - - ! '>='
126
143
  - !ruby/object:Gem::Version
127
144
  version: '0'
128
145
  - !ruby/object:Gem::Dependency
129
146
  name: rspec
130
147
  requirement: !ruby/object:Gem::Requirement
148
+ none: false
131
149
  requirements:
132
- - - '>='
150
+ - - ! '>='
133
151
  - !ruby/object:Gem::Version
134
152
  version: '0'
135
153
  type: :development
136
154
  prerelease: false
137
155
  version_requirements: !ruby/object:Gem::Requirement
156
+ none: false
138
157
  requirements:
139
- - - '>='
158
+ - - ! '>='
140
159
  - !ruby/object:Gem::Version
141
160
  version: '0'
142
161
  - !ruby/object:Gem::Dependency
143
162
  name: rspec-rails
144
163
  requirement: !ruby/object:Gem::Requirement
164
+ none: false
145
165
  requirements:
146
- - - '>='
166
+ - - ! '>='
147
167
  - !ruby/object:Gem::Version
148
168
  version: '0'
149
169
  type: :development
150
170
  prerelease: false
151
171
  version_requirements: !ruby/object:Gem::Requirement
172
+ none: false
152
173
  requirements:
153
- - - '>='
174
+ - - ! '>='
154
175
  - !ruby/object:Gem::Version
155
176
  version: '0'
156
177
  - !ruby/object:Gem::Dependency
157
178
  name: factory_girl
158
179
  requirement: !ruby/object:Gem::Requirement
180
+ none: false
159
181
  requirements:
160
- - - '>='
182
+ - - ! '>='
161
183
  - !ruby/object:Gem::Version
162
184
  version: '0'
163
185
  type: :development
164
186
  prerelease: false
165
187
  version_requirements: !ruby/object:Gem::Requirement
188
+ none: false
166
189
  requirements:
167
- - - '>='
190
+ - - ! '>='
168
191
  - !ruby/object:Gem::Version
169
192
  version: '0'
170
193
  - !ruby/object:Gem::Dependency
171
194
  name: faker
172
195
  requirement: !ruby/object:Gem::Requirement
196
+ none: false
173
197
  requirements:
174
- - - '>='
198
+ - - ! '>='
175
199
  - !ruby/object:Gem::Version
176
200
  version: '0'
177
201
  type: :development
178
202
  prerelease: false
179
203
  version_requirements: !ruby/object:Gem::Requirement
204
+ none: false
180
205
  requirements:
181
- - - '>='
206
+ - - ! '>='
182
207
  - !ruby/object:Gem::Version
183
208
  version: '0'
184
209
  - !ruby/object:Gem::Dependency
185
210
  name: capybara
186
211
  requirement: !ruby/object:Gem::Requirement
212
+ none: false
187
213
  requirements:
188
- - - '>='
214
+ - - ! '>='
189
215
  - !ruby/object:Gem::Version
190
216
  version: '0'
191
217
  type: :development
192
218
  prerelease: false
193
219
  version_requirements: !ruby/object:Gem::Requirement
220
+ none: false
194
221
  requirements:
195
- - - '>='
222
+ - - ! '>='
196
223
  - !ruby/object:Gem::Version
197
224
  version: '0'
198
225
  - !ruby/object:Gem::Dependency
199
226
  name: rails
200
227
  requirement: !ruby/object:Gem::Requirement
228
+ none: false
201
229
  requirements:
202
230
  - - ~>
203
231
  - !ruby/object:Gem::Version
@@ -205,6 +233,7 @@ dependencies:
205
233
  type: :development
206
234
  prerelease: false
207
235
  version_requirements: !ruby/object:Gem::Requirement
236
+ none: false
208
237
  requirements:
209
238
  - - ~>
210
239
  - !ruby/object:Gem::Version
@@ -212,43 +241,49 @@ dependencies:
212
241
  - !ruby/object:Gem::Dependency
213
242
  name: coffee-rails
214
243
  requirement: !ruby/object:Gem::Requirement
244
+ none: false
215
245
  requirements:
216
- - - '>='
246
+ - - ! '>='
217
247
  - !ruby/object:Gem::Version
218
248
  version: '0'
219
249
  type: :development
220
250
  prerelease: false
221
251
  version_requirements: !ruby/object:Gem::Requirement
252
+ none: false
222
253
  requirements:
223
- - - '>='
254
+ - - ! '>='
224
255
  - !ruby/object:Gem::Version
225
256
  version: '0'
226
257
  - !ruby/object:Gem::Dependency
227
258
  name: closure-compiler
228
259
  requirement: !ruby/object:Gem::Requirement
260
+ none: false
229
261
  requirements:
230
- - - '>='
262
+ - - ! '>='
231
263
  - !ruby/object:Gem::Version
232
264
  version: '0'
233
265
  type: :development
234
266
  prerelease: false
235
267
  version_requirements: !ruby/object:Gem::Requirement
268
+ none: false
236
269
  requirements:
237
- - - '>='
270
+ - - ! '>='
238
271
  - !ruby/object:Gem::Version
239
272
  version: '0'
240
273
  - !ruby/object:Gem::Dependency
241
274
  name: coffeelint
242
275
  requirement: !ruby/object:Gem::Requirement
276
+ none: false
243
277
  requirements:
244
- - - '>='
278
+ - - ! '>='
245
279
  - !ruby/object:Gem::Version
246
280
  version: '0'
247
281
  type: :development
248
282
  prerelease: false
249
283
  version_requirements: !ruby/object:Gem::Requirement
284
+ none: false
250
285
  requirements:
251
- - - '>='
286
+ - - ! '>='
252
287
  - !ruby/object:Gem::Version
253
288
  version: '0'
254
289
  description:
@@ -267,21 +302,14 @@ files:
267
302
  - README.md
268
303
  - Rakefile
269
304
  - app/views/layouts/wiselinks.html.erb
270
- - build/wiselinks-0.7.3.js
271
- - build/wiselinks-0.7.3.min.js
272
- - build/wiselinks-0.7.3.min.js.gz
273
305
  - compiler.jar
274
306
  - lib/assets/javascripts/_form.js.coffee
275
307
  - lib/assets/javascripts/_link.js.coffee
276
308
  - lib/assets/javascripts/_page.js.coffee
277
309
  - lib/assets/javascripts/_request_manager.js.coffee
278
- - lib/assets/javascripts/lib/_history.adapter.jquery.js
279
- - lib/assets/javascripts/lib/_history.html4.js
280
- - lib/assets/javascripts/lib/_history.js
281
- - lib/assets/javascripts/lib/_json2.js
310
+ - lib/assets/javascripts/lib/native.history.js
282
311
  - lib/assets/javascripts/wiselinks.js.coffee
283
312
  - lib/wiselinks.rb
284
- - lib/wiselinks/builder.rb
285
313
  - lib/wiselinks/controller_methods.rb
286
314
  - lib/wiselinks/helpers.rb
287
315
  - lib/wiselinks/logger.rb
@@ -359,26 +387,33 @@ files:
359
387
  homepage: http://github.com/igor-alexandrov/wiselinks
360
388
  licenses:
361
389
  - MIT
362
- metadata: {}
363
390
  post_install_message:
364
391
  rdoc_options: []
365
392
  require_paths:
366
393
  - lib
367
394
  required_ruby_version: !ruby/object:Gem::Requirement
395
+ none: false
368
396
  requirements:
369
- - - '>='
397
+ - - ! '>='
370
398
  - !ruby/object:Gem::Version
371
399
  version: '0'
400
+ segments:
401
+ - 0
402
+ hash: 3217867254501092990
372
403
  required_rubygems_version: !ruby/object:Gem::Requirement
404
+ none: false
373
405
  requirements:
374
- - - '>='
406
+ - - ! '>='
375
407
  - !ruby/object:Gem::Version
376
408
  version: '0'
409
+ segments:
410
+ - 0
411
+ hash: 3217867254501092990
377
412
  requirements: []
378
413
  rubyforge_project:
379
- rubygems_version: 2.0.3
414
+ rubygems_version: 1.8.24
380
415
  signing_key:
381
- specification_version: 4
416
+ specification_version: 3
382
417
  summary: Wiselinks makes following links and submitting some forms in your web application
383
418
  smarter and faster
384
419
  test_files:
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: ff45b38e288d992f9f0caac5e9c421fa55f89d08
4
- data.tar.gz: 267a33bf4cace68a13da6d39293fe0f80b147691
5
- SHA512:
6
- metadata.gz: e52123b5f8965db4e49edca5571f9117dfc7308cc83211e665016167e15dae3e59f881c38c17a89e78f2b8068e0488b65edf82ad5476922255b336aca21cdbde
7
- data.tar.gz: 89e98116b5d6073612380e2a720b491597501b650d68dd2d6c442589690b102c0d81f33b278443473b59a4acd9ee20b0d7840d31bff97230980f170d8876bb75
@@ -1,3541 +0,0 @@
1
- /**
2
- * Wiselinks-0.7.3
3
- * @copyright 2012-2013 Igor Alexandrov, Alexey Solilin, Julia Egorova, Alexandr Borisov
4
- * @preserve https://github.com/igor-alexandrov/wiselinks
5
- */
6
-
7
- // Generated by CoffeeScript 1.6.3
8
- (function() {
9
- var Form, Link, Page, RequestManager, Wiselinks;
10
-
11
- Form = (function() {
12
- function Form(page, $form) {
13
- this.page = page;
14
- this.$form = $form;
15
- }
16
-
17
- Form.prototype.process = function() {
18
- var self;
19
- self = this;
20
- if (self._include_blank_url_params()) {
21
- return self.page.load(self._url(), self._target(), self._type());
22
- } else {
23
- return self._without_blank_url_params(function() {
24
- return self.page.load(self._url(), self._target(), self._type());
25
- });
26
- }
27
- };
28
-
29
- Form.prototype._without_blank_url_params = function(callback) {
30
- var $disable, selector;
31
- selector = 'select:not(:disabled),input:not(:disabled)';
32
- $disable = this.$form.find(selector).filter(function() {
33
- return !$(this).val();
34
- });
35
- $disable.attr('disabled', true);
36
- callback();
37
- return $disable.attr('disabled', false);
38
- };
39
-
40
- Form.prototype._params = function() {
41
- var hash, item, name, _i, _len, _ref;
42
- hash = {};
43
- _ref = this.$form.serializeArray();
44
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
45
- item = _ref[_i];
46
- if (item.name !== 'utf8') {
47
- name = item.name.indexOf('[]', item.name.length - '[]'.length) !== -1 ? item.name.substr(0, item.name.length - 2) : item.name;
48
- if (hash[name] != null) {
49
- hash[name] = hash[name] + ("," + item.value);
50
- } else {
51
- hash[name] = item.value;
52
- }
53
- }
54
- }
55
- return hash;
56
- };
57
-
58
- Form.prototype._include_blank_url_params = function() {
59
- return this.$form.data('include-blank-url-params') === true;
60
- };
61
-
62
- Form.prototype._optimize_url_params = function() {
63
- return this.$form.data('optimize-url-params') !== false;
64
- };
65
-
66
- Form.prototype._type = function() {
67
- if (this.$form.data('push') === 'partial') {
68
- return 'partial';
69
- } else {
70
- return 'template';
71
- }
72
- };
73
-
74
- Form.prototype._target = function() {
75
- return this.$form.data('target');
76
- };
77
-
78
- Form.prototype._url = function() {
79
- var key, params, self, serialized, url, value;
80
- self = this;
81
- serialized = (function() {
82
- var _ref;
83
- if (self._optimize_url_params()) {
84
- params = [];
85
- _ref = this._params();
86
- for (key in _ref) {
87
- value = _ref[key];
88
- params.push("" + key + "=" + (encodeURIComponent(value)));
89
- }
90
- return params.join('&');
91
- } else {
92
- return this.$form.serialize();
93
- }
94
- }).call(this);
95
- url = this.$form.attr("action");
96
- if (serialized.length > 0) {
97
- url += "?" + serialized;
98
- }
99
- return url;
100
- };
101
-
102
- return Form;
103
-
104
- })();
105
-
106
- if (window._Wiselinks == null) {
107
- window._Wiselinks = {};
108
- }
109
-
110
- window._Wiselinks.Form = Form;
111
-
112
- Link = (function() {
113
- function Link(page, $link) {
114
- this.page = page;
115
- this.$link = $link;
116
- }
117
-
118
- Link.prototype.allows_process = function(event) {
119
- return !(this._cross_origin_link(event.currentTarget) || this._non_standard_click(event));
120
- };
121
-
122
- Link.prototype.process = function() {
123
- var type;
124
- type = this.$link.data('push') === 'partial' ? 'partial' : 'template';
125
- return this.page.load(this.$link.attr("href"), this.$link.data('target'), type);
126
- };
127
-
128
- Link.prototype._cross_origin_link = function(link) {
129
- return (location.protocol !== link.protocol) || (location.port !== link.port) || (location.host.split(':')[0] !== link.host.split(':')[0]);
130
- };
131
-
132
- Link.prototype._non_standard_click = function(event) {
133
- return event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
134
- };
135
-
136
- return Link;
137
-
138
- })();
139
-
140
- if (window._Wiselinks === void 0) {
141
- window._Wiselinks = {};
142
- }
143
-
144
- window._Wiselinks.Link = Link;
145
-
146
- Page = (function() {
147
- function Page($target, options) {
148
- var self;
149
- this.$target = $target;
150
- this.options = options;
151
- self = this;
152
- this.template_id = new Date().getTime();
153
- this.request_manager = new _Wiselinks.RequestManager(this.options);
154
- self._try_target(this.$target);
155
- if (History.emulated.pushState && this.options.html4 === true) {
156
- if (window.location.href.indexOf('#!') === -1 && this.options.html4_normalize_path === true && window.location.pathname !== this.options.html4_root_path) {
157
- window.location.href = "" + window.location.protocol + "//" + window.location.host + this.options.html4_root_path + "#!" + window.location.pathname;
158
- }
159
- if (window.location.hash.indexOf('#!') !== -1) {
160
- self._call(self._make_state(window.location.hash.substring(2)));
161
- }
162
- }
163
- History.Adapter.bind(window, "statechange", function(event, data) {
164
- var state;
165
- state = History.getState();
166
- if (self._template_id_changed(state)) {
167
- return self._call(self._reset_state(state));
168
- } else {
169
- return self._call(state);
170
- }
171
- });
172
- $(document).on('click', 'a[data-push], a[data-replace]', function(event) {
173
- var link;
174
- if ((link = new _Wiselinks.Link(self, $(this))).allows_process(event)) {
175
- event.preventDefault();
176
- link.process();
177
- return false;
178
- }
179
- });
180
- $(document).on('submit', 'form[data-push], form[data-replace]', function(event) {
181
- var form;
182
- if ((form = new _Wiselinks.Form(self, $(this)))) {
183
- event.preventDefault();
184
- form.process();
185
- return false;
186
- }
187
- });
188
- }
189
-
190
- Page.prototype.load = function(url, target, render) {
191
- if (render == null) {
192
- render = 'template';
193
- }
194
- if (render !== 'partial') {
195
- this.template_id = new Date().getTime();
196
- }
197
- if (target != null) {
198
- this._try_target($(target));
199
- }
200
- return History.pushState({
201
- timestamp: new Date().getTime(),
202
- template_id: this.template_id,
203
- render: render,
204
- target: target,
205
- referer: window.location.href
206
- }, document.title, url);
207
- };
208
-
209
- Page.prototype.reload = function() {
210
- return History.replaceState({
211
- timestamp: new Date().getTime(),
212
- template_id: this.template_id,
213
- render: 'template',
214
- referer: window.location.href
215
- }, document.title, History.getState().url);
216
- };
217
-
218
- Page.prototype._call = function(state) {
219
- var $target;
220
- $target = state.data.target != null ? $(state.data.target) : this.$target;
221
- return this.request_manager.call($target, state);
222
- };
223
-
224
- Page.prototype._template_id_changed = function(state) {
225
- return (state.data.template_id == null) || state.data.template_id !== this.template_id;
226
- };
227
-
228
- Page.prototype._make_state = function(url, target, render, referer) {
229
- if (render == null) {
230
- render = 'template';
231
- }
232
- return {
233
- url: url,
234
- data: {
235
- target: target,
236
- render: render,
237
- referer: referer
238
- }
239
- };
240
- };
241
-
242
- Page.prototype._reset_state = function(state) {
243
- if (state.data == null) {
244
- state.data = {};
245
- }
246
- state.data.target = null;
247
- state.data.render = 'template';
248
- return state;
249
- };
250
-
251
- Page.prototype._try_target = function($target) {
252
- if ($target.length === 0 && this.options.target_missing === 'exception') {
253
- throw new Error("[Wiselinks] Target missing: `" + $target.selector + "`");
254
- }
255
- };
256
-
257
- return Page;
258
-
259
- })();
260
-
261
- if (window._Wiselinks === void 0) {
262
- window._Wiselinks = {};
263
- }
264
-
265
- window._Wiselinks.Page = Page;
266
-
267
- RequestManager = (function() {
268
- function RequestManager(options) {
269
- this.options = options != null ? options : {};
270
- }
271
-
272
- RequestManager.prototype.call = function($target, state) {
273
- var self;
274
- self = this;
275
- if (this.redirected != null) {
276
- this.redirected = null;
277
- return;
278
- }
279
- self._loading($target, state);
280
- return $.ajax({
281
- url: state.url,
282
- headers: {
283
- 'X-Wiselinks': state.data.render,
284
- 'X-Wiselinks-Referer': state.data.referer
285
- },
286
- dataType: "html"
287
- }).done(function(data, status, xhr) {
288
- var assets_digest, url;
289
- url = self._normalize(xhr.getResponseHeader('X-Wiselinks-Url'));
290
- assets_digest = xhr.getResponseHeader('X-Wiselinks-Assets-Digest');
291
- if (self._assets_changed(assets_digest)) {
292
- return window.location.reload(true);
293
- } else {
294
- state = History.getState();
295
- if ((url != null) && (url !== self._normalize(window.location.href))) {
296
- self._redirect_to(url, $target, state, xhr);
297
- }
298
- $target.html(data);
299
- self._title(xhr.getResponseHeader('X-Wiselinks-Title'));
300
- return self._done($target, status, state.url, data);
301
- }
302
- }).fail(function(xhr, status, error) {
303
- return self._fail($target, status, state.url, error);
304
- }).always(function(data_or_xhr, status, xhr_or_error) {
305
- return self._always($target, status, state.url);
306
- });
307
- };
308
-
309
- RequestManager.prototype._normalize = function(url) {
310
- if (url == null) {
311
- return;
312
- }
313
- url = url.replace(/\/+$/, '');
314
- return url;
315
- };
316
-
317
- RequestManager.prototype._assets_changed = function(assets_digest) {
318
- return (this.options.assets_digest != null) && this.options.assets_digest !== assets_digest;
319
- };
320
-
321
- RequestManager.prototype._redirect_to = function(url, $target, state, xhr) {
322
- if (xhr && xhr.readyState < 4) {
323
- xhr.onreadystatechange = $.noop;
324
- xhr.abort();
325
- }
326
- this.redirected = true;
327
- $(document).trigger('page:redirected', [$target, state.data.render, url]);
328
- return History.replaceState(state.data, document.title, url);
329
- };
330
-
331
- RequestManager.prototype._loading = function($target, state) {
332
- return $(document).trigger('page:loading', [$target, state.data.render, state.url]);
333
- };
334
-
335
- RequestManager.prototype._done = function($target, status, url, data) {
336
- return $(document).trigger('page:done', [$target, status, url, data]);
337
- };
338
-
339
- RequestManager.prototype._fail = function($target, status, url, error) {
340
- return $(document).trigger('page:fail', [$target, status, url, error]);
341
- };
342
-
343
- RequestManager.prototype._always = function($target, status, url) {
344
- return $(document).trigger('page:always', [$target, status, url]);
345
- };
346
-
347
- RequestManager.prototype._title = function(value) {
348
- if (value != null) {
349
- $(document).trigger('page:title', decodeURI(value));
350
- return document.title = decodeURI(value);
351
- }
352
- };
353
-
354
- return RequestManager;
355
-
356
- })();
357
-
358
- if (window._Wiselinks === void 0) {
359
- window._Wiselinks = {};
360
- }
361
-
362
- window._Wiselinks.RequestManager = RequestManager;
363
-
364
- Wiselinks = (function() {
365
- function Wiselinks($target, options) {
366
- if ($target == null) {
367
- $target = $('body');
368
- }
369
- this.options = options != null ? options : {};
370
- this._try_jquery();
371
- this.options = $.extend(this._defaults(), this.options);
372
- if (this.enabled()) {
373
- this.page = new _Wiselinks.Page($target, this.options);
374
- }
375
- }
376
-
377
- Wiselinks.prototype.enabled = function() {
378
- return !History.emulated.pushState || this.options.html4 === true;
379
- };
380
-
381
- Wiselinks.prototype.load = function(url, target, render) {
382
- if (render == null) {
383
- render = 'template';
384
- }
385
- return this.page.load(url, target, render);
386
- };
387
-
388
- Wiselinks.prototype.reload = function() {
389
- return this.page.reload();
390
- };
391
-
392
- Wiselinks.prototype._defaults = function() {
393
- return {
394
- html4: true,
395
- html4_root_path: '/',
396
- html4_normalize_path: true,
397
- target_missing: null,
398
- assets_digest: $("meta[name='assets-digest']").attr("content")
399
- };
400
- };
401
-
402
- Wiselinks.prototype._try_jquery = function() {
403
- if (window.jQuery == null) {
404
- throw new Error("[Wiselinks] jQuery is not loaded");
405
- }
406
- };
407
-
408
- return Wiselinks;
409
-
410
- })();
411
-
412
- window.Wiselinks = Wiselinks;
413
-
414
- }).call(this);
415
- /**
416
- * History.js jQuery Adapter
417
- * @author Benjamin Arthur Lupton <contact@balupton.com>
418
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
419
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
420
- */
421
-
422
- // Closure
423
- (function(window,undefined){
424
- "use strict";
425
-
426
- // Localise Globals
427
- var
428
- History = window.History = window.History||{},
429
- jQuery = window.jQuery;
430
-
431
- // Check Existence
432
- if ( typeof History.Adapter !== 'undefined' ) {
433
- throw new Error('History.js Adapter has already been loaded...');
434
- }
435
-
436
- // Add the Adapter
437
- History.Adapter = {
438
- /**
439
- * History.Adapter.bind(el,event,callback)
440
- * @param {Element|string} el
441
- * @param {string} event - custom and standard events
442
- * @param {function} callback
443
- * @return {void}
444
- */
445
- bind: function(el,event,callback){
446
- jQuery(el).bind(event,callback);
447
- },
448
-
449
- /**
450
- * History.Adapter.trigger(el,event)
451
- * @param {Element|string} el
452
- * @param {string} event - custom and standard events
453
- * @param {Object=} extra - a object of extra event data (optional)
454
- * @return {void}
455
- */
456
- trigger: function(el,event,extra){
457
- jQuery(el).trigger(event,extra);
458
- },
459
-
460
- /**
461
- * History.Adapter.extractEventData(key,event,extra)
462
- * @param {string} key - key for the event data to extract
463
- * @param {string} event - custom and standard events
464
- * @param {Object=} extra - a object of extra event data (optional)
465
- * @return {mixed}
466
- */
467
- extractEventData: function(key,event,extra){
468
- // jQuery Native then jQuery Custom
469
- var result = (event && event.originalEvent && event.originalEvent[key]) || (extra && extra[key]) || undefined;
470
-
471
- // Return
472
- return result;
473
- },
474
-
475
- /**
476
- * History.Adapter.onDomLoad(callback)
477
- * @param {function} callback
478
- * @return {void}
479
- */
480
- onDomLoad: function(callback) {
481
- jQuery(callback);
482
- }
483
- };
484
-
485
- // Try and Initialise History
486
- if ( typeof History.init !== 'undefined' ) {
487
- History.init();
488
- }
489
-
490
- })(window);
491
- /**
492
- * History.js HTML4 Support
493
- * Depends on the HTML5 Support
494
- * @author Benjamin Arthur Lupton <contact@balupton.com>
495
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
496
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
497
- */
498
-
499
- (function(window,undefined){
500
- "use strict";
501
-
502
- // ========================================================================
503
- // Initialise
504
-
505
- // Localise Globals
506
- var
507
- document = window.document, // Make sure we are using the correct document
508
- setTimeout = window.setTimeout||setTimeout,
509
- clearTimeout = window.clearTimeout||clearTimeout,
510
- setInterval = window.setInterval||setInterval,
511
- History = window.History = window.History||{}; // Public History Object
512
-
513
- // Check Existence
514
- if ( typeof History.initHtml4 !== 'undefined' ) {
515
- throw new Error('History.js HTML4 Support has already been loaded...');
516
- }
517
-
518
-
519
- // ========================================================================
520
- // Initialise HTML4 Support
521
-
522
- // Initialise HTML4 Support
523
- History.initHtml4 = function(){
524
- // Initialise
525
- if ( typeof History.initHtml4.initialized !== 'undefined' ) {
526
- // Already Loaded
527
- return false;
528
- }
529
- else {
530
- History.initHtml4.initialized = true;
531
- }
532
-
533
-
534
- // ====================================================================
535
- // Properties
536
-
537
- /**
538
- * History.enabled
539
- * Is History enabled?
540
- */
541
- History.enabled = true;
542
-
543
-
544
- // ====================================================================
545
- // Hash Storage
546
-
547
- /**
548
- * History.savedHashes
549
- * Store the hashes in an array
550
- */
551
- History.savedHashes = [];
552
-
553
- /**
554
- * History.isLastHash(newHash)
555
- * Checks if the hash is the last hash
556
- * @param {string} newHash
557
- * @return {boolean} true
558
- */
559
- History.isLastHash = function(newHash){
560
- // Prepare
561
- var oldHash = History.getHashByIndex(),
562
- isLast;
563
-
564
- // Check
565
- isLast = newHash === oldHash;
566
-
567
- // Return isLast
568
- return isLast;
569
- };
570
-
571
- /**
572
- * History.saveHash(newHash)
573
- * Push a Hash
574
- * @param {string} newHash
575
- * @return {boolean} true
576
- */
577
- History.saveHash = function(newHash){
578
- // Check Hash
579
- if ( History.isLastHash(newHash) ) {
580
- return false;
581
- }
582
-
583
- // Push the Hash
584
- History.savedHashes.push(newHash);
585
-
586
- // Return true
587
- return true;
588
- };
589
-
590
- /**
591
- * History.getHashByIndex()
592
- * Gets a hash by the index
593
- * @param {integer} index
594
- * @return {string}
595
- */
596
- History.getHashByIndex = function(index){
597
- // Prepare
598
- var hash = null;
599
-
600
- // Handle
601
- if ( typeof index === 'undefined' ) {
602
- // Get the last inserted
603
- hash = History.savedHashes[History.savedHashes.length-1];
604
- }
605
- else if ( index < 0 ) {
606
- // Get from the end
607
- hash = History.savedHashes[History.savedHashes.length+index];
608
- }
609
- else {
610
- // Get from the beginning
611
- hash = History.savedHashes[index];
612
- }
613
-
614
- // Return hash
615
- return hash;
616
- };
617
-
618
-
619
- // ====================================================================
620
- // Discarded States
621
-
622
- /**
623
- * History.discardedHashes
624
- * A hashed array of discarded hashes
625
- */
626
- History.discardedHashes = {};
627
-
628
- /**
629
- * History.discardedStates
630
- * A hashed array of discarded states
631
- */
632
- History.discardedStates = {};
633
-
634
- /**
635
- * History.discardState(State)
636
- * Discards the state by ignoring it through History
637
- * @param {object} State
638
- * @return {true}
639
- */
640
- History.discardState = function(discardedState,forwardState,backState){
641
- //History.debug('History.discardState', arguments);
642
- // Prepare
643
- var discardedStateHash = History.getHashByState(discardedState),
644
- discardObject;
645
-
646
- // Create Discard Object
647
- discardObject = {
648
- 'discardedState': discardedState,
649
- 'backState': backState,
650
- 'forwardState': forwardState
651
- };
652
-
653
- // Add to DiscardedStates
654
- History.discardedStates[discardedStateHash] = discardObject;
655
-
656
- // Return true
657
- return true;
658
- };
659
-
660
- /**
661
- * History.discardHash(hash)
662
- * Discards the hash by ignoring it through History
663
- * @param {string} hash
664
- * @return {true}
665
- */
666
- History.discardHash = function(discardedHash,forwardState,backState){
667
- //History.debug('History.discardState', arguments);
668
- // Create Discard Object
669
- var discardObject = {
670
- 'discardedHash': discardedHash,
671
- 'backState': backState,
672
- 'forwardState': forwardState
673
- };
674
-
675
- // Add to discardedHash
676
- History.discardedHashes[discardedHash] = discardObject;
677
-
678
- // Return true
679
- return true;
680
- };
681
-
682
- /**
683
- * History.discardState(State)
684
- * Checks to see if the state is discarded
685
- * @param {object} State
686
- * @return {bool}
687
- */
688
- History.discardedState = function(State){
689
- // Prepare
690
- var StateHash = History.getHashByState(State),
691
- discarded;
692
-
693
- // Check
694
- discarded = History.discardedStates[StateHash]||false;
695
-
696
- // Return true
697
- return discarded;
698
- };
699
-
700
- /**
701
- * History.discardedHash(hash)
702
- * Checks to see if the state is discarded
703
- * @param {string} State
704
- * @return {bool}
705
- */
706
- History.discardedHash = function(hash){
707
- // Check
708
- var discarded = History.discardedHashes[hash]||false;
709
-
710
- // Return true
711
- return discarded;
712
- };
713
-
714
- /**
715
- * History.recycleState(State)
716
- * Allows a discarded state to be used again
717
- * @param {object} data
718
- * @param {string} title
719
- * @param {string} url
720
- * @return {true}
721
- */
722
- History.recycleState = function(State){
723
- //History.debug('History.recycleState', arguments);
724
- // Prepare
725
- var StateHash = History.getHashByState(State);
726
-
727
- // Remove from DiscardedStates
728
- if ( History.discardedState(State) ) {
729
- delete History.discardedStates[StateHash];
730
- }
731
-
732
- // Return true
733
- return true;
734
- };
735
-
736
-
737
- // ====================================================================
738
- // HTML4 HashChange Support
739
-
740
- if ( History.emulated.hashChange ) {
741
- /*
742
- * We must emulate the HTML4 HashChange Support by manually checking for hash changes
743
- */
744
-
745
- /**
746
- * History.hashChangeInit()
747
- * Init the HashChange Emulation
748
- */
749
- History.hashChangeInit = function(){
750
- // Define our Checker Function
751
- History.checkerFunction = null;
752
-
753
- // Define some variables that will help in our checker function
754
- var lastDocumentHash = '',
755
- iframeId, iframe,
756
- lastIframeHash, checkerRunning;
757
-
758
- // Handle depending on the browser
759
- if ( History.isInternetExplorer() ) {
760
- // IE6 and IE7
761
- // We need to use an iframe to emulate the back and forward buttons
762
-
763
- // Create iFrame
764
- iframeId = 'historyjs-iframe';
765
- iframe = document.createElement('iframe');
766
-
767
- // Adjust iFarme
768
- iframe.setAttribute('id', iframeId);
769
- iframe.style.display = 'none';
770
-
771
- // Append iFrame
772
- document.body.appendChild(iframe);
773
-
774
- // Create initial history entry
775
- iframe.contentWindow.document.open();
776
- iframe.contentWindow.document.close();
777
-
778
- // Define some variables that will help in our checker function
779
- lastIframeHash = '';
780
- checkerRunning = false;
781
-
782
- // Define the checker function
783
- History.checkerFunction = function(){
784
- // Check Running
785
- if ( checkerRunning ) {
786
- return false;
787
- }
788
-
789
- // Update Running
790
- checkerRunning = true;
791
-
792
- // Fetch
793
- var documentHash = History.getHash()||'',
794
- iframeHash = History.unescapeHash(iframe.contentWindow.document.location.hash)||'';
795
-
796
- // The Document Hash has changed (application caused)
797
- if ( documentHash !== lastDocumentHash ) {
798
- // Equalise
799
- lastDocumentHash = documentHash;
800
-
801
- // Create a history entry in the iframe
802
- if ( iframeHash !== documentHash ) {
803
- //History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash);
804
-
805
- // Equalise
806
- lastIframeHash = iframeHash = documentHash;
807
-
808
- // Create History Entry
809
- iframe.contentWindow.document.open();
810
- iframe.contentWindow.document.close();
811
-
812
- // Update the iframe's hash
813
- iframe.contentWindow.document.location.hash = History.escapeHash(documentHash);
814
- }
815
-
816
- // Trigger Hashchange Event
817
- History.Adapter.trigger(window,'hashchange');
818
- }
819
-
820
- // The iFrame Hash has changed (back button caused)
821
- else if ( iframeHash !== lastIframeHash ) {
822
- //History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash);
823
-
824
- // Equalise
825
- lastIframeHash = iframeHash;
826
-
827
- // Update the Hash
828
- History.setHash(iframeHash,false);
829
- }
830
-
831
- // Reset Running
832
- checkerRunning = false;
833
-
834
- // Return true
835
- return true;
836
- };
837
- }
838
- else {
839
- // We are not IE
840
- // Firefox 1 or 2, Opera
841
-
842
- // Define the checker function
843
- History.checkerFunction = function(){
844
- // Prepare
845
- var documentHash = History.getHash();
846
-
847
- // The Document Hash has changed (application caused)
848
- if ( documentHash !== lastDocumentHash ) {
849
- // Equalise
850
- lastDocumentHash = documentHash;
851
-
852
- // Trigger Hashchange Event
853
- History.Adapter.trigger(window,'hashchange');
854
- }
855
-
856
- // Return true
857
- return true;
858
- };
859
- }
860
-
861
- // Apply the checker function
862
- History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval));
863
-
864
- // Done
865
- return true;
866
- }; // History.hashChangeInit
867
-
868
- // Bind hashChangeInit
869
- History.Adapter.onDomLoad(History.hashChangeInit);
870
-
871
- } // History.emulated.hashChange
872
-
873
-
874
- // ====================================================================
875
- // HTML5 State Support
876
-
877
- // Non-Native pushState Implementation
878
- if ( History.emulated.pushState ) {
879
- /*
880
- * We must emulate the HTML5 State Management by using HTML4 HashChange
881
- */
882
-
883
- /**
884
- * History.onHashChange(event)
885
- * Trigger HTML5's window.onpopstate via HTML4 HashChange Support
886
- */
887
- History.onHashChange = function(event){
888
- //History.debug('History.onHashChange', arguments);
889
-
890
- // Prepare
891
- var currentUrl = ((event && event.newURL) || document.location.href),
892
- currentHash = History.getHashByUrl(currentUrl),
893
- currentState = null,
894
- currentStateHash = null,
895
- currentStateHashExits = null,
896
- discardObject;
897
-
898
- // Check if we are the same state
899
- if ( History.isLastHash(currentHash) ) {
900
- // There has been no change (just the page's hash has finally propagated)
901
- //History.debug('History.onHashChange: no change');
902
- History.busy(false);
903
- return false;
904
- }
905
-
906
- // Reset the double check
907
- History.doubleCheckComplete();
908
-
909
- // Store our location for use in detecting back/forward direction
910
- History.saveHash(currentHash);
911
-
912
- // Expand Hash
913
- if ( currentHash && History.isTraditionalAnchor(currentHash) ) {
914
- //History.debug('History.onHashChange: traditional anchor', currentHash);
915
- // Traditional Anchor Hash
916
- History.Adapter.trigger(window,'anchorchange');
917
- History.busy(false);
918
- return false;
919
- }
920
-
921
- // Create State
922
- currentState = History.extractState(History.getFullUrl(currentHash||document.location.href,false),true);
923
-
924
- // Check if we are the same state
925
- if ( History.isLastSavedState(currentState) ) {
926
- //History.debug('History.onHashChange: no change');
927
- // There has been no change (just the page's hash has finally propagated)
928
- History.busy(false);
929
- return false;
930
- }
931
-
932
- // Create the state Hash
933
- currentStateHash = History.getHashByState(currentState);
934
-
935
- // Check if we are DiscardedState
936
- discardObject = History.discardedState(currentState);
937
- if ( discardObject ) {
938
- // Ignore this state as it has been discarded and go back to the state before it
939
- if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) {
940
- // We are going backwards
941
- //History.debug('History.onHashChange: go backwards');
942
- History.back(false);
943
- } else {
944
- // We are going forwards
945
- //History.debug('History.onHashChange: go forwards');
946
- History.forward(false);
947
- }
948
- return false;
949
- }
950
-
951
- // Push the new HTML5 State
952
- //History.debug('History.onHashChange: success hashchange');
953
- History.pushState(currentState.data,currentState.title,currentState.url,false);
954
-
955
- // End onHashChange closure
956
- return true;
957
- };
958
- History.Adapter.bind(window,'hashchange',History.onHashChange);
959
-
960
- /**
961
- * History.pushState(data,title,url)
962
- * Add a new State to the history object, become it, and trigger onpopstate
963
- * We have to trigger for HTML4 compatibility
964
- * @param {object} data
965
- * @param {string} title
966
- * @param {string} url
967
- * @return {true}
968
- */
969
- History.pushState = function(data,title,url,queue){
970
- //History.debug('History.pushState: called', arguments);
971
-
972
- // Check the State
973
- if ( History.getHashByUrl(url) ) {
974
- throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
975
- }
976
-
977
- // Handle Queueing
978
- if ( queue !== false && History.busy() ) {
979
- // Wait + Push to Queue
980
- //History.debug('History.pushState: we must wait', arguments);
981
- History.pushQueue({
982
- scope: History,
983
- callback: History.pushState,
984
- args: arguments,
985
- queue: queue
986
- });
987
- return false;
988
- }
989
-
990
- // Make Busy
991
- History.busy(true);
992
-
993
- // Fetch the State Object
994
- var newState = History.createStateObject(data,title,url),
995
- newStateHash = History.getHashByState(newState),
996
- oldState = History.getState(false),
997
- oldStateHash = History.getHashByState(oldState),
998
- html4Hash = History.getHash();
999
-
1000
- // Store the newState
1001
- History.storeState(newState);
1002
- History.expectedStateId = newState.id;
1003
-
1004
- // Recycle the State
1005
- History.recycleState(newState);
1006
-
1007
- // Force update of the title
1008
- // History.setTitle(newState);
1009
-
1010
- // Check if we are the same State
1011
- if ( newStateHash === oldStateHash ) {
1012
- //History.debug('History.pushState: no change', newStateHash);
1013
- History.busy(false);
1014
- return false;
1015
- }
1016
-
1017
- // Update HTML4 Hash
1018
- if ( newStateHash !== html4Hash && newStateHash !== History.getShortUrl(document.location.href) ) {
1019
- //History.debug('History.pushState: update hash', newStateHash, html4Hash);
1020
- History.setHash(newStateHash,false);
1021
- return false;
1022
- }
1023
-
1024
- // Update HTML5 State
1025
- History.saveState(newState);
1026
-
1027
- // Fire HTML5 Event
1028
- //History.debug('History.pushState: trigger popstate');
1029
- History.Adapter.trigger(window,'statechange');
1030
- History.busy(false);
1031
-
1032
- // End pushState closure
1033
- return true;
1034
- };
1035
-
1036
- /**
1037
- * History.replaceState(data,title,url)
1038
- * Replace the State and trigger onpopstate
1039
- * We have to trigger for HTML4 compatibility
1040
- * @param {object} data
1041
- * @param {string} title
1042
- * @param {string} url
1043
- * @return {true}
1044
- */
1045
- History.replaceState = function(data,title,url,queue){
1046
- //History.debug('History.replaceState: called', arguments);
1047
-
1048
- // Check the State
1049
- if ( History.getHashByUrl(url) ) {
1050
- throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1051
- }
1052
-
1053
- // Handle Queueing
1054
- if ( queue !== false && History.busy() ) {
1055
- // Wait + Push to Queue
1056
- //History.debug('History.replaceState: we must wait', arguments);
1057
- History.pushQueue({
1058
- scope: History,
1059
- callback: History.replaceState,
1060
- args: arguments,
1061
- queue: queue
1062
- });
1063
- return false;
1064
- }
1065
-
1066
- // Make Busy
1067
- History.busy(true);
1068
-
1069
- // Fetch the State Objects
1070
- var newState = History.createStateObject(data,title,url),
1071
- oldState = History.getState(false),
1072
- previousState = History.getStateByIndex(-2);
1073
-
1074
- // Discard Old State
1075
- History.discardState(oldState,newState,previousState);
1076
-
1077
- // Alias to PushState
1078
- History.pushState(newState.data,newState.title,newState.url,false);
1079
-
1080
- // End replaceState closure
1081
- return true;
1082
- };
1083
-
1084
- } // History.emulated.pushState
1085
-
1086
-
1087
-
1088
- // ====================================================================
1089
- // Initialise
1090
-
1091
- // Non-Native pushState Implementation
1092
- if ( History.emulated.pushState ) {
1093
- /**
1094
- * Ensure initial state is handled correctly
1095
- */
1096
- if ( History.getHash() && !History.emulated.hashChange ) {
1097
- History.Adapter.onDomLoad(function(){
1098
- History.Adapter.trigger(window,'hashchange');
1099
- });
1100
- }
1101
-
1102
- } // History.emulated.pushState
1103
-
1104
- }; // History.initHtml4
1105
-
1106
- // Try and Initialise History
1107
- if ( typeof History.init !== 'undefined' ) {
1108
- History.init();
1109
- }
1110
-
1111
- })(window);/**
1112
- * History.js Core
1113
- * @author Benjamin Arthur Lupton <contact@balupton.com>
1114
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
1115
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
1116
- */
1117
-
1118
- (function(window,undefined){
1119
- "use strict";
1120
-
1121
- // ========================================================================
1122
- // Initialise
1123
-
1124
- // Localise Globals
1125
- var
1126
- console = window.console||undefined, // Prevent a JSLint complain
1127
- document = window.document, // Make sure we are using the correct document
1128
- navigator = window.navigator, // Make sure we are using the correct navigator
1129
- sessionStorage = window.sessionStorage||false, // sessionStorage
1130
- setTimeout = window.setTimeout,
1131
- clearTimeout = window.clearTimeout,
1132
- setInterval = window.setInterval,
1133
- clearInterval = window.clearInterval,
1134
- JSON = window.JSON,
1135
- alert = window.alert,
1136
- History = window.History = window.History||{}, // Public History Object
1137
- history = window.history; // Old History Object
1138
-
1139
- // MooTools Compatibility
1140
- JSON.stringify = JSON.stringify||JSON.encode;
1141
- JSON.parse = JSON.parse||JSON.decode;
1142
-
1143
- // Check Existence
1144
- if ( typeof History.init !== 'undefined' ) {
1145
- throw new Error('History.js Core has already been loaded...');
1146
- }
1147
-
1148
- // Initialise History
1149
- History.init = function(){
1150
- // Check Load Status of Adapter
1151
- if ( typeof History.Adapter === 'undefined' ) {
1152
- return false;
1153
- }
1154
-
1155
- // Check Load Status of Core
1156
- if ( typeof History.initCore !== 'undefined' ) {
1157
- History.initCore();
1158
- }
1159
-
1160
- // Check Load Status of HTML4 Support
1161
- if ( typeof History.initHtml4 !== 'undefined' ) {
1162
- History.initHtml4();
1163
- }
1164
-
1165
- // Return true
1166
- return true;
1167
- };
1168
-
1169
-
1170
- // ========================================================================
1171
- // Initialise Core
1172
-
1173
- // Initialise Core
1174
- History.initCore = function(){
1175
- // Initialise
1176
- if ( typeof History.initCore.initialized !== 'undefined' ) {
1177
- // Already Loaded
1178
- return false;
1179
- }
1180
- else {
1181
- History.initCore.initialized = true;
1182
- }
1183
-
1184
-
1185
- // ====================================================================
1186
- // Options
1187
-
1188
- /**
1189
- * History.options
1190
- * Configurable options
1191
- */
1192
- History.options = History.options||{};
1193
-
1194
- /**
1195
- * History.options.hashChangeInterval
1196
- * How long should the interval be before hashchange checks
1197
- */
1198
- History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
1199
-
1200
- /**
1201
- * History.options.safariPollInterval
1202
- * How long should the interval be before safari poll checks
1203
- */
1204
- History.options.safariPollInterval = History.options.safariPollInterval || 500;
1205
-
1206
- /**
1207
- * History.options.doubleCheckInterval
1208
- * How long should the interval be before we perform a double check
1209
- */
1210
- History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
1211
-
1212
- /**
1213
- * History.options.storeInterval
1214
- * How long should we wait between store calls
1215
- */
1216
- History.options.storeInterval = History.options.storeInterval || 1000;
1217
-
1218
- /**
1219
- * History.options.busyDelay
1220
- * How long should we wait between busy events
1221
- */
1222
- History.options.busyDelay = History.options.busyDelay || 250;
1223
-
1224
- /**
1225
- * History.options.debug
1226
- * If true will enable debug messages to be logged
1227
- */
1228
- History.options.debug = History.options.debug || false;
1229
-
1230
- /**
1231
- * History.options.initialTitle
1232
- * What is the title of the initial state
1233
- */
1234
- History.options.initialTitle = History.options.initialTitle || document.title;
1235
-
1236
-
1237
- // ====================================================================
1238
- // Interval record
1239
-
1240
- /**
1241
- * History.intervalList
1242
- * List of intervals set, to be cleared when document is unloaded.
1243
- */
1244
- History.intervalList = [];
1245
-
1246
- /**
1247
- * History.clearAllIntervals
1248
- * Clears all setInterval instances.
1249
- */
1250
- History.clearAllIntervals = function(){
1251
- var i, il = History.intervalList;
1252
- if (typeof il !== "undefined" && il !== null) {
1253
- for (i = 0; i < il.length; i++) {
1254
- clearInterval(il[i]);
1255
- }
1256
- History.intervalList = null;
1257
- }
1258
- };
1259
-
1260
-
1261
- // ====================================================================
1262
- // Debug
1263
-
1264
- /**
1265
- * History.debug(message,...)
1266
- * Logs the passed arguments if debug enabled
1267
- */
1268
- History.debug = function(){
1269
- if ( (History.options.debug||false) ) {
1270
- History.log.apply(History,arguments);
1271
- }
1272
- };
1273
-
1274
- /**
1275
- * History.log(message,...)
1276
- * Logs the passed arguments
1277
- */
1278
- History.log = function(){
1279
- // Prepare
1280
- var
1281
- consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
1282
- textarea = document.getElementById('log'),
1283
- message,
1284
- i,n,
1285
- args,arg
1286
- ;
1287
-
1288
- // Write to Console
1289
- if ( consoleExists ) {
1290
- args = Array.prototype.slice.call(arguments);
1291
- message = args.shift();
1292
- if ( typeof console.debug !== 'undefined' ) {
1293
- console.debug.apply(console,[message,args]);
1294
- }
1295
- else {
1296
- console.log.apply(console,[message,args]);
1297
- }
1298
- }
1299
- else {
1300
- message = ("\n"+arguments[0]+"\n");
1301
- }
1302
-
1303
- // Write to log
1304
- for ( i=1,n=arguments.length; i<n; ++i ) {
1305
- arg = arguments[i];
1306
- if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
1307
- try {
1308
- arg = JSON.stringify(arg);
1309
- }
1310
- catch ( Exception ) {
1311
- // Recursive Object
1312
- }
1313
- }
1314
- message += "\n"+arg+"\n";
1315
- }
1316
-
1317
- // Textarea
1318
- if ( textarea ) {
1319
- textarea.value += message+"\n-----\n";
1320
- textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
1321
- }
1322
- // No Textarea, No Console
1323
- else if ( !consoleExists ) {
1324
- alert(message);
1325
- }
1326
-
1327
- // Return true
1328
- return true;
1329
- };
1330
-
1331
-
1332
- // ====================================================================
1333
- // Emulated Status
1334
-
1335
- /**
1336
- * History.getInternetExplorerMajorVersion()
1337
- * Get's the major version of Internet Explorer
1338
- * @return {integer}
1339
- * @license Public Domain
1340
- * @author Benjamin Arthur Lupton <contact@balupton.com>
1341
- * @author James Padolsey <https://gist.github.com/527683>
1342
- */
1343
- History.getInternetExplorerMajorVersion = function(){
1344
- var result = History.getInternetExplorerMajorVersion.cached =
1345
- (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
1346
- ? History.getInternetExplorerMajorVersion.cached
1347
- : (function(){
1348
- var v = 3,
1349
- div = document.createElement('div'),
1350
- all = div.getElementsByTagName('i');
1351
- while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
1352
- return (v > 4) ? v : false;
1353
- })()
1354
- ;
1355
- return result;
1356
- };
1357
-
1358
- /**
1359
- * History.isInternetExplorer()
1360
- * Are we using Internet Explorer?
1361
- * @return {boolean}
1362
- * @license Public Domain
1363
- * @author Benjamin Arthur Lupton <contact@balupton.com>
1364
- */
1365
- History.isInternetExplorer = function(){
1366
- var result =
1367
- History.isInternetExplorer.cached =
1368
- (typeof History.isInternetExplorer.cached !== 'undefined')
1369
- ? History.isInternetExplorer.cached
1370
- : Boolean(History.getInternetExplorerMajorVersion())
1371
- ;
1372
- return result;
1373
- };
1374
-
1375
- /**
1376
- * History.emulated
1377
- * Which features require emulating?
1378
- */
1379
- History.emulated = {
1380
- pushState: !Boolean(
1381
- window.history && window.history.pushState && window.history.replaceState
1382
- && !(
1383
- (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
1384
- || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
1385
- )
1386
- ),
1387
- hashChange: Boolean(
1388
- !(('onhashchange' in window) || ('onhashchange' in document))
1389
- ||
1390
- (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
1391
- )
1392
- };
1393
-
1394
- /**
1395
- * History.enabled
1396
- * Is History enabled?
1397
- */
1398
- History.enabled = !History.emulated.pushState;
1399
-
1400
- /**
1401
- * History.bugs
1402
- * Which bugs are present
1403
- */
1404
- History.bugs = {
1405
- /**
1406
- * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
1407
- * https://bugs.webkit.org/show_bug.cgi?id=56249
1408
- */
1409
- setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
1410
-
1411
- /**
1412
- * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
1413
- * https://bugs.webkit.org/show_bug.cgi?id=42940
1414
- */
1415
- safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
1416
-
1417
- /**
1418
- * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
1419
- */
1420
- ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
1421
-
1422
- /**
1423
- * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
1424
- */
1425
- hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
1426
- };
1427
-
1428
- /**
1429
- * History.isEmptyObject(obj)
1430
- * Checks to see if the Object is Empty
1431
- * @param {Object} obj
1432
- * @return {boolean}
1433
- */
1434
- History.isEmptyObject = function(obj) {
1435
- for ( var name in obj ) {
1436
- return false;
1437
- }
1438
- return true;
1439
- };
1440
-
1441
- /**
1442
- * History.cloneObject(obj)
1443
- * Clones a object and eliminate all references to the original contexts
1444
- * @param {Object} obj
1445
- * @return {Object}
1446
- */
1447
- History.cloneObject = function(obj) {
1448
- var hash,newObj;
1449
- if ( obj ) {
1450
- hash = JSON.stringify(obj);
1451
- newObj = JSON.parse(hash);
1452
- }
1453
- else {
1454
- newObj = {};
1455
- }
1456
- return newObj;
1457
- };
1458
-
1459
-
1460
- // ====================================================================
1461
- // URL Helpers
1462
-
1463
- /**
1464
- * History.getRootUrl()
1465
- * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
1466
- * @return {String} rootUrl
1467
- */
1468
- History.getRootUrl = function(){
1469
- // Create
1470
- var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
1471
- if ( document.location.port||false ) {
1472
- rootUrl += ':'+document.location.port;
1473
- }
1474
- rootUrl += '/';
1475
-
1476
- // Return
1477
- return rootUrl;
1478
- };
1479
-
1480
- /**
1481
- * History.getBaseHref()
1482
- * Fetches the `href` attribute of the `<base href="...">` element if it exists
1483
- * @return {String} baseHref
1484
- */
1485
- History.getBaseHref = function(){
1486
- // Create
1487
- var
1488
- baseElements = document.getElementsByTagName('base'),
1489
- baseElement = null,
1490
- baseHref = '';
1491
-
1492
- // Test for Base Element
1493
- if ( baseElements.length === 1 ) {
1494
- // Prepare for Base Element
1495
- baseElement = baseElements[0];
1496
- baseHref = baseElement.href.replace(/[^\/]+$/,'');
1497
- }
1498
-
1499
- // Adjust trailing slash
1500
- baseHref = baseHref.replace(/\/+$/,'');
1501
- if ( baseHref ) baseHref += '/';
1502
-
1503
- // Return
1504
- return baseHref;
1505
- };
1506
-
1507
- /**
1508
- * History.getBaseUrl()
1509
- * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
1510
- * @return {String} baseUrl
1511
- */
1512
- History.getBaseUrl = function(){
1513
- // Create
1514
- var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
1515
-
1516
- // Return
1517
- return baseUrl;
1518
- };
1519
-
1520
- /**
1521
- * History.getPageUrl()
1522
- * Fetches the URL of the current page
1523
- * @return {String} pageUrl
1524
- */
1525
- History.getPageUrl = function(){
1526
- // Fetch
1527
- var
1528
- State = History.getState(false,false),
1529
- stateUrl = (State||{}).url||document.location.href,
1530
- pageUrl;
1531
-
1532
- // Create
1533
- pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
1534
- return (/\!/).test(part) ? part : part+'/';
1535
- });
1536
-
1537
- // Return
1538
- return pageUrl;
1539
- };
1540
-
1541
- /**
1542
- * History.getBasePageUrl()
1543
- * Fetches the Url of the directory of the current page
1544
- * @return {String} basePageUrl
1545
- */
1546
- History.getBasePageUrl = function(){
1547
- // Create
1548
- var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
1549
- return (/[^\/]$/).test(part) ? '' : part;
1550
- }).replace(/\/+$/,'')+'/';
1551
-
1552
- // Return
1553
- return basePageUrl;
1554
- };
1555
-
1556
- /**
1557
- * History.getFullUrl(url)
1558
- * Ensures that we have an absolute URL and not a relative URL
1559
- * @param {string} url
1560
- * @param {Boolean} allowBaseHref
1561
- * @return {string} fullUrl
1562
- */
1563
- History.getFullUrl = function(url,allowBaseHref){
1564
- // Prepare
1565
- var fullUrl = url, firstChar = url.substring(0,1);
1566
- allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
1567
-
1568
- // Check
1569
- if ( /[a-z]+\:\/\//.test(url) ) {
1570
- // Full URL
1571
- }
1572
- else if ( firstChar === '/' ) {
1573
- // Root URL
1574
- fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
1575
- }
1576
- else if ( firstChar === '#' ) {
1577
- // Anchor URL
1578
- fullUrl = History.getPageUrl().replace(/#!*/,'')+url;
1579
- }
1580
- else if ( firstChar === '?' ) {
1581
- // Query URL
1582
- fullUrl = History.getPageUrl().replace(/[\?#]!*/,'')+url;
1583
- }
1584
- else {
1585
- // Relative URL
1586
- if ( allowBaseHref ) {
1587
- fullUrl = History.getBaseUrl()+url.replace(/^(\!\/)+/,'');
1588
- } else {
1589
- fullUrl = History.getBasePageUrl()+url.replace(/^(\!\/)+/,'');
1590
- }
1591
- // We have an if condition above as we do not want hashes
1592
- // which are relative to the baseHref in our URLs
1593
- // as if the baseHref changes, then all our bookmarks
1594
- // would now point to different locations
1595
- // whereas the basePageUrl will always stay the same
1596
- }
1597
-
1598
- // Return
1599
- return fullUrl.replace(/\#$/,'');
1600
- };
1601
-
1602
- /**
1603
- * History.getShortUrl(url)
1604
- * Ensures that we have a relative URL and not a absolute URL
1605
- * @param {string} url
1606
- * @return {string} url
1607
- */
1608
- History.getShortUrl = function(url){
1609
- // Prepare
1610
- var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
1611
-
1612
- // Trim baseUrl
1613
- if ( History.emulated.pushState ) {
1614
- // We are in a if statement as when pushState is not emulated
1615
- // The actual url these short urls are relative to can change
1616
- // So within the same session, we the url may end up somewhere different
1617
- shortUrl = shortUrl.replace(baseUrl,'');
1618
- }
1619
-
1620
- // Trim rootUrl
1621
- shortUrl = shortUrl.replace(rootUrl,'/');
1622
-
1623
- // Ensure we can still detect it as a state
1624
- // if ( History.isTraditionalAnchor(shortUrl) ) {
1625
- // shortUrl = './'+shortUrl;
1626
- // // shortUrl = '/'+shortUrl;
1627
- // }
1628
-
1629
- shortUrl = '!/'+shortUrl;
1630
-
1631
- // Clean It
1632
- shortUrl = shortUrl.replace(/^(\!\/)+/g,'!/').replace(/\#$/,'');
1633
-
1634
- // Return
1635
- return shortUrl;
1636
- };
1637
-
1638
-
1639
- // ====================================================================
1640
- // State Storage
1641
-
1642
- /**
1643
- * History.store
1644
- * The store for all session specific data
1645
- */
1646
- History.store = {};
1647
-
1648
- /**
1649
- * History.idToState
1650
- * 1-1: State ID to State Object
1651
- */
1652
- History.idToState = History.idToState||{};
1653
-
1654
- /**
1655
- * History.stateToId
1656
- * 1-1: State String to State ID
1657
- */
1658
- History.stateToId = History.stateToId||{};
1659
-
1660
- /**
1661
- * History.urlToId
1662
- * 1-1: State URL to State ID
1663
- */
1664
- History.urlToId = History.urlToId||{};
1665
-
1666
- /**
1667
- * History.storedStates
1668
- * Store the states in an array
1669
- */
1670
- History.storedStates = History.storedStates||[];
1671
-
1672
- /**
1673
- * History.savedStates
1674
- * Saved the states in an array
1675
- */
1676
- History.savedStates = History.savedStates||[];
1677
-
1678
- /**
1679
- * History.noramlizeStore()
1680
- * Noramlize the store by adding necessary values
1681
- */
1682
- History.normalizeStore = function(){
1683
- History.store.idToState = History.store.idToState||{};
1684
- History.store.urlToId = History.store.urlToId||{};
1685
- History.store.stateToId = History.store.stateToId||{};
1686
- };
1687
-
1688
- /**
1689
- * History.getState()
1690
- * Get an object containing the data, title and url of the current state
1691
- * @param {Boolean} friendly
1692
- * @param {Boolean} create
1693
- * @return {Object} State
1694
- */
1695
- History.getState = function(friendly,create){
1696
- // Prepare
1697
- if ( typeof friendly === 'undefined' ) { friendly = true; }
1698
- if ( typeof create === 'undefined' ) { create = true; }
1699
-
1700
- // Fetch
1701
- var State = History.getLastSavedState();
1702
-
1703
- // Create
1704
- if ( !State && create ) {
1705
- State = History.createStateObject();
1706
- }
1707
-
1708
- // Adjust
1709
- if ( friendly ) {
1710
- State = History.cloneObject(State);
1711
- State.url = State.cleanUrl||State.url;
1712
- }
1713
-
1714
- // Return
1715
- return State;
1716
- };
1717
-
1718
- /**
1719
- * History.getIdByState(State)
1720
- * Gets a ID for a State
1721
- * @param {State} newState
1722
- * @return {String} id
1723
- */
1724
- History.getIdByState = function(newState){
1725
-
1726
- // Fetch ID
1727
- var id = History.extractId(newState.url),
1728
- str;
1729
-
1730
- if ( !id ) {
1731
- // Find ID via State String
1732
- str = History.getStateString(newState);
1733
- if ( typeof History.stateToId[str] !== 'undefined' ) {
1734
- id = History.stateToId[str];
1735
- }
1736
- else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
1737
- id = History.store.stateToId[str];
1738
- }
1739
- else {
1740
- // Generate a new ID
1741
- while ( true ) {
1742
- id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
1743
- if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
1744
- break;
1745
- }
1746
- }
1747
-
1748
- // Apply the new State to the ID
1749
- History.stateToId[str] = id;
1750
- History.idToState[id] = newState;
1751
- }
1752
- }
1753
-
1754
- // Return ID
1755
- return id;
1756
- };
1757
-
1758
- /**
1759
- * History.normalizeState(State)
1760
- * Expands a State Object
1761
- * @param {object} State
1762
- * @return {object}
1763
- */
1764
- History.normalizeState = function(oldState){
1765
- // Variables
1766
- var newState, dataNotEmpty;
1767
-
1768
- // Prepare
1769
- if ( !oldState || (typeof oldState !== 'object') ) {
1770
- oldState = {};
1771
- }
1772
-
1773
- // Check
1774
- if ( typeof oldState.normalized !== 'undefined' ) {
1775
- return oldState;
1776
- }
1777
-
1778
- // Adjust
1779
- if ( !oldState.data || (typeof oldState.data !== 'object') ) {
1780
- oldState.data = {};
1781
- }
1782
-
1783
- // ----------------------------------------------------------------
1784
-
1785
- // Create
1786
- newState = {};
1787
- newState.normalized = true;
1788
- newState.title = oldState.title||'';
1789
- newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href));
1790
- newState.hash = History.getShortUrl(newState.url);
1791
- newState.data = History.cloneObject(oldState.data);
1792
-
1793
- // Fetch ID
1794
- newState.id = History.getIdByState(newState);
1795
-
1796
- // ----------------------------------------------------------------
1797
-
1798
- // Clean the URL
1799
- newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
1800
- newState.url = newState.cleanUrl;
1801
-
1802
- // Check to see if we have more than just a url
1803
- dataNotEmpty = !History.isEmptyObject(newState.data);
1804
-
1805
- // Apply
1806
- if ( newState.title || dataNotEmpty ) {
1807
- // Add ID to Hash
1808
- newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
1809
- if ( !/\?/.test(newState.hash) ) {
1810
- newState.hash += '?';
1811
- }
1812
- newState.hash += '&_suid='+newState.id;
1813
- }
1814
-
1815
- // Create the Hashed URL
1816
- newState.hashedUrl = History.getFullUrl(newState.hash);
1817
-
1818
- // ----------------------------------------------------------------
1819
-
1820
- // Update the URL if we have a duplicate
1821
- if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
1822
- newState.url = newState.hashedUrl;
1823
- }
1824
-
1825
- // ----------------------------------------------------------------
1826
-
1827
- // Return
1828
- return newState;
1829
- };
1830
-
1831
- /**
1832
- * History.createStateObject(data,title,url)
1833
- * Creates a object based on the data, title and url state params
1834
- * @param {object} data
1835
- * @param {string} title
1836
- * @param {string} url
1837
- * @return {object}
1838
- */
1839
- History.createStateObject = function(data,title,url){
1840
- // Hashify
1841
- var State = {
1842
- 'data': data,
1843
- 'title': title,
1844
- 'url': url
1845
- };
1846
-
1847
- // Expand the State
1848
- State = History.normalizeState(State);
1849
-
1850
- // Return object
1851
- return State;
1852
- };
1853
-
1854
- /**
1855
- * History.getStateById(id)
1856
- * Get a state by it's UID
1857
- * @param {String} id
1858
- */
1859
- History.getStateById = function(id){
1860
- // Prepare
1861
- id = String(id);
1862
-
1863
- // Retrieve
1864
- var State = History.idToState[id] || History.store.idToState[id] || undefined;
1865
-
1866
- // Return State
1867
- return State;
1868
- };
1869
-
1870
- /**
1871
- * Get a State's String
1872
- * @param {State} passedState
1873
- */
1874
- History.getStateString = function(passedState){
1875
- // Prepare
1876
- var State, cleanedState, str;
1877
-
1878
- // Fetch
1879
- State = History.normalizeState(passedState);
1880
-
1881
- // Clean
1882
- cleanedState = {
1883
- data: State.data,
1884
- title: passedState.title,
1885
- url: passedState.url
1886
- };
1887
-
1888
- // Fetch
1889
- str = JSON.stringify(cleanedState);
1890
-
1891
- // Return
1892
- return str;
1893
- };
1894
-
1895
- /**
1896
- * Get a State's ID
1897
- * @param {State} passedState
1898
- * @return {String} id
1899
- */
1900
- History.getStateId = function(passedState){
1901
- // Prepare
1902
- var State, id;
1903
-
1904
- // Fetch
1905
- State = History.normalizeState(passedState);
1906
-
1907
- // Fetch
1908
- id = State.id;
1909
-
1910
- // Return
1911
- return id;
1912
- };
1913
-
1914
- /**
1915
- * History.getHashByState(State)
1916
- * Creates a Hash for the State Object
1917
- * @param {State} passedState
1918
- * @return {String} hash
1919
- */
1920
- History.getHashByState = function(passedState){
1921
- // Prepare
1922
- var State, hash;
1923
-
1924
- // Fetch
1925
- State = History.normalizeState(passedState);
1926
-
1927
- // Hash
1928
- hash = State.hash;
1929
-
1930
- // Return
1931
- return hash;
1932
- };
1933
-
1934
- /**
1935
- * History.extractId(url_or_hash)
1936
- * Get a State ID by it's URL or Hash
1937
- * @param {string} url_or_hash
1938
- * @return {string} id
1939
- */
1940
- History.extractId = function ( url_or_hash ) {
1941
- // Prepare
1942
- var id,parts,url;
1943
-
1944
- // Extract
1945
- parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
1946
- url = parts ? (parts[1]||url_or_hash) : url_or_hash;
1947
- id = parts ? String(parts[2]||'') : '';
1948
-
1949
- // Return
1950
- return id||false;
1951
- };
1952
-
1953
- /**
1954
- * History.isTraditionalAnchor
1955
- * Checks to see if the url is a traditional anchor or not
1956
- * @param {String} url_or_hash
1957
- * @return {Boolean}
1958
- */
1959
- History.isTraditionalAnchor = function(url_or_hash){
1960
- // Check
1961
- var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
1962
-
1963
- // Return
1964
- return isTraditional;
1965
- };
1966
-
1967
- /**
1968
- * History.extractState
1969
- * Get a State by it's URL or Hash
1970
- * @param {String} url_or_hash
1971
- * @return {State|null}
1972
- */
1973
- History.extractState = function(url_or_hash,create){
1974
- // Prepare
1975
- var State = null, id, url;
1976
- create = create||false;
1977
-
1978
- // Fetch SUID
1979
- id = History.extractId(url_or_hash);
1980
- if ( id ) {
1981
- State = History.getStateById(id);
1982
- }
1983
-
1984
- // Fetch SUID returned no State
1985
- if ( !State ) {
1986
- // Fetch URL
1987
- url = History.getFullUrl(url_or_hash);
1988
-
1989
- // Check URL
1990
- id = History.getIdByUrl(url)||false;
1991
- if ( id ) {
1992
- State = History.getStateById(id);
1993
- }
1994
-
1995
- // Create State
1996
- if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
1997
- State = History.createStateObject(null,null,url);
1998
- }
1999
- }
2000
-
2001
- // Return
2002
- return State;
2003
- };
2004
-
2005
- /**
2006
- * History.getIdByUrl()
2007
- * Get a State ID by a State URL
2008
- */
2009
- History.getIdByUrl = function(url){
2010
- // Fetch
2011
- var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
2012
-
2013
- // Return
2014
- return id;
2015
- };
2016
-
2017
- /**
2018
- * History.getLastSavedState()
2019
- * Get an object containing the data, title and url of the current state
2020
- * @return {Object} State
2021
- */
2022
- History.getLastSavedState = function(){
2023
- return History.savedStates[History.savedStates.length-1]||undefined;
2024
- };
2025
-
2026
- /**
2027
- * History.getLastStoredState()
2028
- * Get an object containing the data, title and url of the current state
2029
- * @return {Object} State
2030
- */
2031
- History.getLastStoredState = function(){
2032
- return History.storedStates[History.storedStates.length-1]||undefined;
2033
- };
2034
-
2035
- /**
2036
- * History.hasUrlDuplicate
2037
- * Checks if a Url will have a url conflict
2038
- * @param {Object} newState
2039
- * @return {Boolean} hasDuplicate
2040
- */
2041
- History.hasUrlDuplicate = function(newState) {
2042
- // Prepare
2043
- var hasDuplicate = false,
2044
- oldState;
2045
-
2046
- // Fetch
2047
- oldState = History.extractState(newState.url);
2048
-
2049
- // Check
2050
- hasDuplicate = oldState && oldState.id !== newState.id;
2051
-
2052
- // Return
2053
- return hasDuplicate;
2054
- };
2055
-
2056
- /**
2057
- * History.storeState
2058
- * Store a State
2059
- * @param {Object} newState
2060
- * @return {Object} newState
2061
- */
2062
- History.storeState = function(newState){
2063
- // Store the State
2064
- History.urlToId[newState.url] = newState.id;
2065
-
2066
- // Push the State
2067
- History.storedStates.push(History.cloneObject(newState));
2068
-
2069
- // Return newState
2070
- return newState;
2071
- };
2072
-
2073
- /**
2074
- * History.isLastSavedState(newState)
2075
- * Tests to see if the state is the last state
2076
- * @param {Object} newState
2077
- * @return {boolean} isLast
2078
- */
2079
- History.isLastSavedState = function(newState){
2080
- // Prepare
2081
- var isLast = false,
2082
- newId, oldState, oldId;
2083
-
2084
- // Check
2085
- if ( History.savedStates.length ) {
2086
- newId = newState.id;
2087
- oldState = History.getLastSavedState();
2088
- oldId = oldState.id;
2089
-
2090
- // Check
2091
- isLast = (newId === oldId);
2092
- }
2093
-
2094
- // Return
2095
- return isLast;
2096
- };
2097
-
2098
- /**
2099
- * History.saveState
2100
- * Push a State
2101
- * @param {Object} newState
2102
- * @return {boolean} changed
2103
- */
2104
- History.saveState = function(newState){
2105
- // Check Hash
2106
- if ( History.isLastSavedState(newState) ) {
2107
- return false;
2108
- }
2109
-
2110
- // Push the State
2111
- History.savedStates.push(History.cloneObject(newState));
2112
-
2113
- // Return true
2114
- return true;
2115
- };
2116
-
2117
- /**
2118
- * History.getStateByIndex()
2119
- * Gets a state by the index
2120
- * @param {integer} index
2121
- * @return {Object}
2122
- */
2123
- History.getStateByIndex = function(index){
2124
- // Prepare
2125
- var State = null;
2126
-
2127
- // Handle
2128
- if ( typeof index === 'undefined' ) {
2129
- // Get the last inserted
2130
- State = History.savedStates[History.savedStates.length-1];
2131
- }
2132
- else if ( index < 0 ) {
2133
- // Get from the end
2134
- State = History.savedStates[History.savedStates.length+index];
2135
- }
2136
- else {
2137
- // Get from the beginning
2138
- State = History.savedStates[index];
2139
- }
2140
-
2141
- // Return State
2142
- return State;
2143
- };
2144
-
2145
-
2146
- // ====================================================================
2147
- // Hash Helpers
2148
-
2149
- /**
2150
- * History.getHash()
2151
- * Gets the current document hash
2152
- * @return {string}
2153
- */
2154
- History.getHash = function(){
2155
- var hash = History.unescapeHash(document.location.hash);
2156
- return hash;
2157
- };
2158
-
2159
- /**
2160
- * History.unescapeString()
2161
- * Unescape a string
2162
- * @param {String} str
2163
- * @return {string}
2164
- */
2165
- History.unescapeString = function(str){
2166
- // Prepare
2167
- var result = str,
2168
- tmp;
2169
-
2170
- // Unescape hash
2171
- while ( true ) {
2172
- tmp = window.decodeURI(result);
2173
- if ( tmp === result ) {
2174
- break;
2175
- }
2176
- result = tmp;
2177
- }
2178
-
2179
- // Return result
2180
- return result;
2181
- };
2182
-
2183
- /**
2184
- * History.unescapeHash()
2185
- * normalize and Unescape a Hash
2186
- * @param {String} hash
2187
- * @return {string}
2188
- */
2189
- History.unescapeHash = function(hash){
2190
- // Prepare
2191
- var result = History.normalizeHash(hash);
2192
-
2193
- // Unescape hash
2194
- result = History.unescapeString(result);
2195
-
2196
- // Return result
2197
- return result;
2198
- };
2199
-
2200
- /**
2201
- * History.normalizeHash()
2202
- * normalize a hash across browsers
2203
- * @return {string}
2204
- */
2205
- History.normalizeHash = function(hash){
2206
- // Prepare
2207
- var result = hash.replace(/[^#]*#/,'').replace(/#!*/, '');
2208
-
2209
- // Return result
2210
- return result;
2211
- };
2212
-
2213
- /**
2214
- * History.setHash(hash)
2215
- * Sets the document hash
2216
- * @param {string} hash
2217
- * @return {History}
2218
- */
2219
- History.setHash = function(hash,queue){
2220
- // Prepare
2221
- var adjustedHash, State, pageUrl;
2222
-
2223
- // Handle Queueing
2224
- if ( queue !== false && History.busy() ) {
2225
- // Wait + Push to Queue
2226
- //History.debug('History.setHash: we must wait', arguments);
2227
- History.pushQueue({
2228
- scope: History,
2229
- callback: History.setHash,
2230
- args: arguments,
2231
- queue: queue
2232
- });
2233
- return false;
2234
- }
2235
-
2236
- // Log
2237
- //History.debug('History.setHash: called',hash);
2238
-
2239
- // Prepare
2240
- adjustedHash = History.escapeHash(hash);
2241
-
2242
- // Make Busy + Continue
2243
- History.busy(true);
2244
-
2245
- // Check if hash is a state
2246
- State = History.extractState(hash,true);
2247
- if ( State && !History.emulated.pushState ) {
2248
- // Hash is a state so skip the setHash
2249
- //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
2250
-
2251
- // PushState
2252
- History.pushState(State.data,State.title,State.url,false);
2253
- }
2254
- else if ( document.location.hash !== adjustedHash ) {
2255
- // Hash is a proper hash, so apply it
2256
-
2257
- // Handle browser bugs
2258
- if ( History.bugs.setHash ) {
2259
- // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
2260
-
2261
- // Fetch the base page
2262
- pageUrl = History.getPageUrl();
2263
-
2264
- // Safari hash apply
2265
- History.pushState(null,null,pageUrl+'#'+adjustedHash,false);
2266
- }
2267
- else {
2268
- // Normal hash apply
2269
- document.location.hash = adjustedHash;
2270
- }
2271
- }
2272
-
2273
- // Chain
2274
- return History;
2275
- };
2276
-
2277
- /**
2278
- * History.escape()
2279
- * normalize and Escape a Hash
2280
- * @return {string}
2281
- */
2282
- History.escapeHash = function(hash){
2283
- // Prepare
2284
- var result = History.normalizeHash(hash);
2285
-
2286
- // Escape hash
2287
- result = window.encodeURI(result);
2288
-
2289
- // IE6 Escape Bug
2290
- if ( !History.bugs.hashEscape ) {
2291
- // Restore common parts
2292
- result = result
2293
- .replace(/\%21/g,'!')
2294
- .replace(/\%26/g,'&')
2295
- .replace(/\%3D/g,'=')
2296
- .replace(/\%3F/g,'?');
2297
- }
2298
-
2299
- // Return result
2300
- return result;
2301
- };
2302
-
2303
- /**
2304
- * History.getHashByUrl(url)
2305
- * Extracts the Hash from a URL
2306
- * @param {string} url
2307
- * @return {string} url
2308
- */
2309
- History.getHashByUrl = function(url){
2310
- // Extract the hash
2311
- var hash = String(url)
2312
- .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
2313
- ;
2314
-
2315
- // Unescape hash
2316
- hash = History.unescapeHash(hash);
2317
-
2318
- // Return hash
2319
- return hash;
2320
- };
2321
-
2322
- /**
2323
- * History.setTitle(title)
2324
- * Applies the title to the document
2325
- * @param {State} newState
2326
- * @return {Boolean}
2327
- */
2328
- History.setTitle = function(newState){
2329
- // Prepare
2330
- var title = newState.title,
2331
- firstState;
2332
-
2333
- // Initial
2334
- if ( !title ) {
2335
- firstState = History.getStateByIndex(0);
2336
- if ( firstState && firstState.url === newState.url ) {
2337
- title = firstState.title||History.options.initialTitle;
2338
- }
2339
- }
2340
-
2341
- // Apply
2342
- try {
2343
- document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
2344
- }
2345
- catch ( Exception ) { }
2346
- document.title = title;
2347
-
2348
- // Chain
2349
- return History;
2350
- };
2351
-
2352
-
2353
- // ====================================================================
2354
- // Queueing
2355
-
2356
- /**
2357
- * History.queues
2358
- * The list of queues to use
2359
- * First In, First Out
2360
- */
2361
- History.queues = [];
2362
-
2363
- /**
2364
- * History.busy(value)
2365
- * @param {boolean} value [optional]
2366
- * @return {boolean} busy
2367
- */
2368
- History.busy = function(value){
2369
- // Apply
2370
- if ( typeof value !== 'undefined' ) {
2371
- //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
2372
- History.busy.flag = value;
2373
- }
2374
- // Default
2375
- else if ( typeof History.busy.flag === 'undefined' ) {
2376
- History.busy.flag = false;
2377
- }
2378
-
2379
- // Queue
2380
- if ( !History.busy.flag ) {
2381
- // Execute the next item in the queue
2382
- clearTimeout(History.busy.timeout);
2383
- var fireNext = function(){
2384
- var i, queue, item;
2385
- if ( History.busy.flag ) return;
2386
- for ( i=History.queues.length-1; i >= 0; --i ) {
2387
- queue = History.queues[i];
2388
- if ( queue.length === 0 ) continue;
2389
- item = queue.shift();
2390
- History.fireQueueItem(item);
2391
- History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
2392
- }
2393
- };
2394
- History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
2395
- }
2396
-
2397
- // Return
2398
- return History.busy.flag;
2399
- };
2400
-
2401
- /**
2402
- * History.busy.flag
2403
- */
2404
- History.busy.flag = false;
2405
-
2406
- /**
2407
- * History.fireQueueItem(item)
2408
- * Fire a Queue Item
2409
- * @param {Object} item
2410
- * @return {Mixed} result
2411
- */
2412
- History.fireQueueItem = function(item){
2413
- return item.callback.apply(item.scope||History,item.args||[]);
2414
- };
2415
-
2416
- /**
2417
- * History.pushQueue(callback,args)
2418
- * Add an item to the queue
2419
- * @param {Object} item [scope,callback,args,queue]
2420
- */
2421
- History.pushQueue = function(item){
2422
- // Prepare the queue
2423
- History.queues[item.queue||0] = History.queues[item.queue||0]||[];
2424
-
2425
- // Add to the queue
2426
- History.queues[item.queue||0].push(item);
2427
-
2428
- // Chain
2429
- return History;
2430
- };
2431
-
2432
- /**
2433
- * History.queue (item,queue), (func,queue), (func), (item)
2434
- * Either firs the item now if not busy, or adds it to the queue
2435
- */
2436
- History.queue = function(item,queue){
2437
- // Prepare
2438
- if ( typeof item === 'function' ) {
2439
- item = {
2440
- callback: item
2441
- };
2442
- }
2443
- if ( typeof queue !== 'undefined' ) {
2444
- item.queue = queue;
2445
- }
2446
-
2447
- // Handle
2448
- if ( History.busy() ) {
2449
- History.pushQueue(item);
2450
- } else {
2451
- History.fireQueueItem(item);
2452
- }
2453
-
2454
- // Chain
2455
- return History;
2456
- };
2457
-
2458
- /**
2459
- * History.clearQueue()
2460
- * Clears the Queue
2461
- */
2462
- History.clearQueue = function(){
2463
- History.busy.flag = false;
2464
- History.queues = [];
2465
- return History;
2466
- };
2467
-
2468
-
2469
- // ====================================================================
2470
- // IE Bug Fix
2471
-
2472
- /**
2473
- * History.stateChanged
2474
- * States whether or not the state has changed since the last double check was initialised
2475
- */
2476
- History.stateChanged = false;
2477
-
2478
- /**
2479
- * History.doubleChecker
2480
- * Contains the timeout used for the double checks
2481
- */
2482
- History.doubleChecker = false;
2483
-
2484
- /**
2485
- * History.doubleCheckComplete()
2486
- * Complete a double check
2487
- * @return {History}
2488
- */
2489
- History.doubleCheckComplete = function(){
2490
- // Update
2491
- History.stateChanged = true;
2492
-
2493
- // Clear
2494
- History.doubleCheckClear();
2495
-
2496
- // Chain
2497
- return History;
2498
- };
2499
-
2500
- /**
2501
- * History.doubleCheckClear()
2502
- * Clear a double check
2503
- * @return {History}
2504
- */
2505
- History.doubleCheckClear = function(){
2506
- // Clear
2507
- if ( History.doubleChecker ) {
2508
- clearTimeout(History.doubleChecker);
2509
- History.doubleChecker = false;
2510
- }
2511
-
2512
- // Chain
2513
- return History;
2514
- };
2515
-
2516
- /**
2517
- * History.doubleCheck()
2518
- * Create a double check
2519
- * @return {History}
2520
- */
2521
- History.doubleCheck = function(tryAgain){
2522
- // Reset
2523
- History.stateChanged = false;
2524
- History.doubleCheckClear();
2525
-
2526
- // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
2527
- // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
2528
- if ( History.bugs.ieDoubleCheck ) {
2529
- // Apply Check
2530
- History.doubleChecker = setTimeout(
2531
- function(){
2532
- History.doubleCheckClear();
2533
- if ( !History.stateChanged ) {
2534
- //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
2535
- // Re-Attempt
2536
- tryAgain();
2537
- }
2538
- return true;
2539
- },
2540
- History.options.doubleCheckInterval
2541
- );
2542
- }
2543
-
2544
- // Chain
2545
- return History;
2546
- };
2547
-
2548
-
2549
- // ====================================================================
2550
- // Safari Bug Fix
2551
-
2552
- /**
2553
- * History.safariStatePoll()
2554
- * Poll the current state
2555
- * @return {History}
2556
- */
2557
- History.safariStatePoll = function(){
2558
- // Poll the URL
2559
-
2560
- // Get the Last State which has the new URL
2561
- var
2562
- urlState = History.extractState(document.location.href),
2563
- newState;
2564
-
2565
- // Check for a difference
2566
- if ( !History.isLastSavedState(urlState) ) {
2567
- newState = urlState;
2568
- }
2569
- else {
2570
- return;
2571
- }
2572
-
2573
- // Check if we have a state with that url
2574
- // If not create it
2575
- if ( !newState ) {
2576
- //History.debug('History.safariStatePoll: new');
2577
- newState = History.createStateObject();
2578
- }
2579
-
2580
- // Apply the New State
2581
- //History.debug('History.safariStatePoll: trigger');
2582
- History.Adapter.trigger(window,'popstate');
2583
-
2584
- // Chain
2585
- return History;
2586
- };
2587
-
2588
-
2589
- // ====================================================================
2590
- // State Aliases
2591
-
2592
- /**
2593
- * History.back(queue)
2594
- * Send the browser history back one item
2595
- * @param {Integer} queue [optional]
2596
- */
2597
- History.back = function(queue){
2598
- //History.debug('History.back: called', arguments);
2599
-
2600
- // Handle Queueing
2601
- if ( queue !== false && History.busy() ) {
2602
- // Wait + Push to Queue
2603
- //History.debug('History.back: we must wait', arguments);
2604
- History.pushQueue({
2605
- scope: History,
2606
- callback: History.back,
2607
- args: arguments,
2608
- queue: queue
2609
- });
2610
- return false;
2611
- }
2612
-
2613
- // Make Busy + Continue
2614
- History.busy(true);
2615
-
2616
- // Fix certain browser bugs that prevent the state from changing
2617
- History.doubleCheck(function(){
2618
- History.back(false);
2619
- });
2620
-
2621
- // Go back
2622
- history.go(-1);
2623
-
2624
- // End back closure
2625
- return true;
2626
- };
2627
-
2628
- /**
2629
- * History.forward(queue)
2630
- * Send the browser history forward one item
2631
- * @param {Integer} queue [optional]
2632
- */
2633
- History.forward = function(queue){
2634
- //History.debug('History.forward: called', arguments);
2635
-
2636
- // Handle Queueing
2637
- if ( queue !== false && History.busy() ) {
2638
- // Wait + Push to Queue
2639
- //History.debug('History.forward: we must wait', arguments);
2640
- History.pushQueue({
2641
- scope: History,
2642
- callback: History.forward,
2643
- args: arguments,
2644
- queue: queue
2645
- });
2646
- return false;
2647
- }
2648
-
2649
- // Make Busy + Continue
2650
- History.busy(true);
2651
-
2652
- // Fix certain browser bugs that prevent the state from changing
2653
- History.doubleCheck(function(){
2654
- History.forward(false);
2655
- });
2656
-
2657
- // Go forward
2658
- history.go(1);
2659
-
2660
- // End forward closure
2661
- return true;
2662
- };
2663
-
2664
- /**
2665
- * History.go(index,queue)
2666
- * Send the browser history back or forward index times
2667
- * @param {Integer} queue [optional]
2668
- */
2669
- History.go = function(index,queue){
2670
- //History.debug('History.go: called', arguments);
2671
-
2672
- // Prepare
2673
- var i;
2674
-
2675
- // Handle
2676
- if ( index > 0 ) {
2677
- // Forward
2678
- for ( i=1; i<=index; ++i ) {
2679
- History.forward(queue);
2680
- }
2681
- }
2682
- else if ( index < 0 ) {
2683
- // Backward
2684
- for ( i=-1; i>=index; --i ) {
2685
- History.back(queue);
2686
- }
2687
- }
2688
- else {
2689
- throw new Error('History.go: History.go requires a positive or negative integer passed.');
2690
- }
2691
-
2692
- // Chain
2693
- return History;
2694
- };
2695
-
2696
-
2697
- // ====================================================================
2698
- // HTML5 State Support
2699
-
2700
- // Non-Native pushState Implementation
2701
- if ( History.emulated.pushState ) {
2702
- /*
2703
- * Provide Skeleton for HTML4 Browsers
2704
- */
2705
-
2706
- // Prepare
2707
- var emptyFunction = function(){};
2708
- History.pushState = History.pushState||emptyFunction;
2709
- History.replaceState = History.replaceState||emptyFunction;
2710
- } // History.emulated.pushState
2711
-
2712
- // Native pushState Implementation
2713
- else {
2714
- /*
2715
- * Use native HTML5 History API Implementation
2716
- */
2717
-
2718
- /**
2719
- * History.onPopState(event,extra)
2720
- * Refresh the Current State
2721
- */
2722
- History.onPopState = function(event,extra){
2723
- // Prepare
2724
- var stateId = false, newState = false, currentHash, currentState;
2725
-
2726
- // Reset the double check
2727
- History.doubleCheckComplete();
2728
-
2729
- // Check for a Hash, and handle apporiatly
2730
- currentHash = History.getHash();
2731
- if ( currentHash ) {
2732
- // Expand Hash
2733
- currentState = History.extractState(currentHash||document.location.href,true);
2734
- if ( currentState ) {
2735
- // We were able to parse it, it must be a State!
2736
- // Let's forward to replaceState
2737
- //History.debug('History.onPopState: state anchor', currentHash, currentState);
2738
- History.replaceState(currentState.data, currentState.title, currentState.url, false);
2739
- }
2740
- else {
2741
- // Traditional Anchor
2742
- //History.debug('History.onPopState: traditional anchor', currentHash);
2743
- History.Adapter.trigger(window,'anchorchange');
2744
- History.busy(false);
2745
- }
2746
-
2747
- // We don't care for hashes
2748
- History.expectedStateId = false;
2749
- return false;
2750
- }
2751
-
2752
- // Ensure
2753
- stateId = History.Adapter.extractEventData('state',event,extra) || false;
2754
-
2755
- // Fetch State
2756
- if ( stateId ) {
2757
- // Vanilla: Back/forward button was used
2758
- newState = History.getStateById(stateId);
2759
- }
2760
- else if ( History.expectedStateId ) {
2761
- // Vanilla: A new state was pushed, and popstate was called manually
2762
- newState = History.getStateById(History.expectedStateId);
2763
- }
2764
- else {
2765
- // Initial State
2766
- newState = History.extractState(document.location.href);
2767
- }
2768
-
2769
- // The State did not exist in our store
2770
- if ( !newState ) {
2771
- // Regenerate the State
2772
- newState = History.createStateObject(null,null,document.location.href);
2773
- }
2774
-
2775
- // Clean
2776
- History.expectedStateId = false;
2777
-
2778
- // Check if we are the same state
2779
- if ( History.isLastSavedState(newState) ) {
2780
- // There has been no change (just the page's hash has finally propagated)
2781
- //History.debug('History.onPopState: no change', newState, History.savedStates);
2782
- History.busy(false);
2783
- return false;
2784
- }
2785
-
2786
- // Store the State
2787
- History.storeState(newState);
2788
- History.saveState(newState);
2789
-
2790
- // Force update of the title
2791
- History.setTitle(newState);
2792
-
2793
- // Fire Our Event
2794
- History.Adapter.trigger(window,'statechange');
2795
- History.busy(false);
2796
-
2797
- // Return true
2798
- return true;
2799
- };
2800
- History.Adapter.bind(window,'popstate',History.onPopState);
2801
-
2802
- /**
2803
- * History.pushState(data,title,url)
2804
- * Add a new State to the history object, become it, and trigger onpopstate
2805
- * We have to trigger for HTML4 compatibility
2806
- * @param {object} data
2807
- * @param {string} title
2808
- * @param {string} url
2809
- * @return {true}
2810
- */
2811
- History.pushState = function(data,title,url,queue){
2812
- //History.debug('History.pushState: called', arguments);
2813
-
2814
- // Check the State
2815
- if ( History.getHashByUrl(url) && History.emulated.pushState ) {
2816
- throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
2817
- }
2818
-
2819
- // Handle Queueing
2820
- if ( queue !== false && History.busy() ) {
2821
- // Wait + Push to Queue
2822
- //History.debug('History.pushState: we must wait', arguments);
2823
- History.pushQueue({
2824
- scope: History,
2825
- callback: History.pushState,
2826
- args: arguments,
2827
- queue: queue
2828
- });
2829
- return false;
2830
- }
2831
-
2832
- // Make Busy + Continue
2833
- History.busy(true);
2834
-
2835
- // Create the newState
2836
- var newState = History.createStateObject(data,title,url);
2837
-
2838
- // Check it
2839
- if ( History.isLastSavedState(newState) ) {
2840
- // Won't be a change
2841
- History.busy(false);
2842
- }
2843
- else {
2844
- // Store the newState
2845
- History.storeState(newState);
2846
- History.expectedStateId = newState.id;
2847
-
2848
- // Push the newState
2849
- history.pushState(newState.id,newState.title,newState.url);
2850
-
2851
- // Fire HTML5 Event
2852
- History.Adapter.trigger(window,'popstate');
2853
- }
2854
-
2855
- // End pushState closure
2856
- return true;
2857
- };
2858
-
2859
- /**
2860
- * History.replaceState(data,title,url)
2861
- * Replace the State and trigger onpopstate
2862
- * We have to trigger for HTML4 compatibility
2863
- * @param {object} data
2864
- * @param {string} title
2865
- * @param {string} url
2866
- * @return {true}
2867
- */
2868
- History.replaceState = function(data,title,url,queue){
2869
- //History.debug('History.replaceState: called', arguments);
2870
-
2871
- // Check the State
2872
- if ( History.getHashByUrl(url) && History.emulated.pushState ) {
2873
- throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
2874
- }
2875
-
2876
- // Handle Queueing
2877
- if ( queue !== false && History.busy() ) {
2878
- // Wait + Push to Queue
2879
- //History.debug('History.replaceState: we must wait', arguments);
2880
- History.pushQueue({
2881
- scope: History,
2882
- callback: History.replaceState,
2883
- args: arguments,
2884
- queue: queue
2885
- });
2886
- return false;
2887
- }
2888
-
2889
- // Make Busy + Continue
2890
- History.busy(true);
2891
-
2892
- // Create the newState
2893
- var newState = History.createStateObject(data,title,url);
2894
-
2895
- // Check it
2896
- if ( History.isLastSavedState(newState) ) {
2897
- // Won't be a change
2898
- History.busy(false);
2899
- }
2900
- else {
2901
- // Store the newState
2902
- History.storeState(newState);
2903
- History.expectedStateId = newState.id;
2904
-
2905
- // Push the newState
2906
- history.replaceState(newState.id,newState.title,newState.url);
2907
-
2908
- // Fire HTML5 Event
2909
- History.Adapter.trigger(window,'popstate');
2910
- }
2911
-
2912
- // End replaceState closure
2913
- return true;
2914
- };
2915
-
2916
- } // !History.emulated.pushState
2917
-
2918
-
2919
- // ====================================================================
2920
- // Initialise
2921
-
2922
- /**
2923
- * Load the Store
2924
- */
2925
- if ( sessionStorage ) {
2926
- // Fetch
2927
- try {
2928
- History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
2929
- }
2930
- catch ( err ) {
2931
- History.store = {};
2932
- }
2933
-
2934
- // Normalize
2935
- History.normalizeStore();
2936
- }
2937
- else {
2938
- // Default Load
2939
- History.store = {};
2940
- History.normalizeStore();
2941
- }
2942
-
2943
- /**
2944
- * Clear Intervals on exit to prevent memory leaks
2945
- */
2946
- History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
2947
- History.Adapter.bind(window,"unload",History.clearAllIntervals);
2948
-
2949
- /**
2950
- * Create the initial State
2951
- */
2952
- History.saveState(History.storeState(History.extractState(document.location.href,true)));
2953
-
2954
- /**
2955
- * Bind for Saving Store
2956
- */
2957
- if ( sessionStorage ) {
2958
- // When the page is closed
2959
- History.onUnload = function(){
2960
- // Prepare
2961
- var currentStore, item;
2962
-
2963
- // Fetch
2964
- try {
2965
- currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
2966
- }
2967
- catch ( err ) {
2968
- currentStore = {};
2969
- }
2970
-
2971
- // Ensure
2972
- currentStore.idToState = currentStore.idToState || {};
2973
- currentStore.urlToId = currentStore.urlToId || {};
2974
- currentStore.stateToId = currentStore.stateToId || {};
2975
-
2976
- // Sync
2977
- for ( item in History.idToState ) {
2978
- if ( !History.idToState.hasOwnProperty(item) ) {
2979
- continue;
2980
- }
2981
- currentStore.idToState[item] = History.idToState[item];
2982
- }
2983
- for ( item in History.urlToId ) {
2984
- if ( !History.urlToId.hasOwnProperty(item) ) {
2985
- continue;
2986
- }
2987
- currentStore.urlToId[item] = History.urlToId[item];
2988
- }
2989
- for ( item in History.stateToId ) {
2990
- if ( !History.stateToId.hasOwnProperty(item) ) {
2991
- continue;
2992
- }
2993
- currentStore.stateToId[item] = History.stateToId[item];
2994
- }
2995
-
2996
- // Update
2997
- History.store = currentStore;
2998
- History.normalizeStore();
2999
-
3000
- // Store
3001
- sessionStorage.setItem('History.store',JSON.stringify(currentStore));
3002
- };
3003
-
3004
- // For Internet Explorer
3005
- History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
3006
-
3007
- // For Other Browsers
3008
- History.Adapter.bind(window,'beforeunload',History.onUnload);
3009
- History.Adapter.bind(window,'unload',History.onUnload);
3010
-
3011
- // Both are enabled for consistency
3012
- }
3013
-
3014
- // Non-Native pushState Implementation
3015
- if ( !History.emulated.pushState ) {
3016
- // Be aware, the following is only for native pushState implementations
3017
- // If you are wanting to include something for all browsers
3018
- // Then include it above this if block
3019
-
3020
- /**
3021
- * Setup Safari Fix
3022
- */
3023
- if ( History.bugs.safariPoll ) {
3024
- History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
3025
- }
3026
-
3027
- /**
3028
- * Ensure Cross Browser Compatibility
3029
- */
3030
- if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
3031
- /**
3032
- * Fix Safari HashChange Issue
3033
- */
3034
-
3035
- // Setup Alias
3036
- History.Adapter.bind(window,'hashchange',function(){
3037
- History.Adapter.trigger(window,'popstate');
3038
- });
3039
-
3040
- // Initialise Alias
3041
- if ( History.getHash() ) {
3042
- History.Adapter.onDomLoad(function(){
3043
- History.Adapter.trigger(window,'hashchange');
3044
- });
3045
- }
3046
- }
3047
-
3048
- } // !History.emulated.pushState
3049
-
3050
-
3051
- }; // History.initCore
3052
-
3053
- // Try and Initialise History
3054
- History.init();
3055
-
3056
- })(window);/*
3057
- json2.js
3058
- 2012-10-08
3059
-
3060
- Public Domain.
3061
-
3062
- NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
3063
-
3064
- See http://www.JSON.org/js.html
3065
-
3066
-
3067
- This code should be minified before deployment.
3068
- See http://javascript.crockford.com/jsmin.html
3069
-
3070
- USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
3071
- NOT CONTROL.
3072
-
3073
-
3074
- This file creates a global JSON object containing two methods: stringify
3075
- and parse.
3076
-
3077
- JSON.stringify(value, replacer, space)
3078
- value any JavaScript value, usually an object or array.
3079
-
3080
- replacer an optional parameter that determines how object
3081
- values are stringified for objects. It can be a
3082
- function or an array of strings.
3083
-
3084
- space an optional parameter that specifies the indentation
3085
- of nested structures. If it is omitted, the text will
3086
- be packed without extra whitespace. If it is a number,
3087
- it will specify the number of spaces to indent at each
3088
- level. If it is a string (such as '\t' or '&nbsp;'),
3089
- it contains the characters used to indent at each level.
3090
-
3091
- This method produces a JSON text from a JavaScript value.
3092
-
3093
- When an object value is found, if the object contains a toJSON
3094
- method, its toJSON method will be called and the result will be
3095
- stringified. A toJSON method does not serialize: it returns the
3096
- value represented by the name/value pair that should be serialized,
3097
- or undefined if nothing should be serialized. The toJSON method
3098
- will be passed the key associated with the value, and this will be
3099
- bound to the value
3100
-
3101
- For example, this would serialize Dates as ISO strings.
3102
-
3103
- Date.prototype.toJSON = function (key) {
3104
- function f(n) {
3105
- // Format integers to have at least two digits.
3106
- return n < 10 ? '0' + n : n;
3107
- }
3108
-
3109
- return this.getUTCFullYear() + '-' +
3110
- f(this.getUTCMonth() + 1) + '-' +
3111
- f(this.getUTCDate()) + 'T' +
3112
- f(this.getUTCHours()) + ':' +
3113
- f(this.getUTCMinutes()) + ':' +
3114
- f(this.getUTCSeconds()) + 'Z';
3115
- };
3116
-
3117
- You can provide an optional replacer method. It will be passed the
3118
- key and value of each member, with this bound to the containing
3119
- object. The value that is returned from your method will be
3120
- serialized. If your method returns undefined, then the member will
3121
- be excluded from the serialization.
3122
-
3123
- If the replacer parameter is an array of strings, then it will be
3124
- used to select the members to be serialized. It filters the results
3125
- such that only members with keys listed in the replacer array are
3126
- stringified.
3127
-
3128
- Values that do not have JSON representations, such as undefined or
3129
- functions, will not be serialized. Such values in objects will be
3130
- dropped; in arrays they will be replaced with null. You can use
3131
- a replacer function to replace those with JSON values.
3132
- JSON.stringify(undefined) returns undefined.
3133
-
3134
- The optional space parameter produces a stringification of the
3135
- value that is filled with line breaks and indentation to make it
3136
- easier to read.
3137
-
3138
- If the space parameter is a non-empty string, then that string will
3139
- be used for indentation. If the space parameter is a number, then
3140
- the indentation will be that many spaces.
3141
-
3142
- Example:
3143
-
3144
- text = JSON.stringify(['e', {pluribus: 'unum'}]);
3145
- // text is '["e",{"pluribus":"unum"}]'
3146
-
3147
-
3148
- text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
3149
- // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
3150
-
3151
- text = JSON.stringify([new Date()], function (key, value) {
3152
- return this[key] instanceof Date ?
3153
- 'Date(' + this[key] + ')' : value;
3154
- });
3155
- // text is '["Date(---current time---)"]'
3156
-
3157
-
3158
- JSON.parse(text, reviver)
3159
- This method parses a JSON text to produce an object or array.
3160
- It can throw a SyntaxError exception.
3161
-
3162
- The optional reviver parameter is a function that can filter and
3163
- transform the results. It receives each of the keys and values,
3164
- and its return value is used instead of the original value.
3165
- If it returns what it received, then the structure is not modified.
3166
- If it returns undefined then the member is deleted.
3167
-
3168
- Example:
3169
-
3170
- // Parse the text. Values that look like ISO date strings will
3171
- // be converted to Date objects.
3172
-
3173
- myData = JSON.parse(text, function (key, value) {
3174
- var a;
3175
- if (typeof value === 'string') {
3176
- a =
3177
- /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
3178
- if (a) {
3179
- return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
3180
- +a[5], +a[6]));
3181
- }
3182
- }
3183
- return value;
3184
- });
3185
-
3186
- myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
3187
- var d;
3188
- if (typeof value === 'string' &&
3189
- value.slice(0, 5) === 'Date(' &&
3190
- value.slice(-1) === ')') {
3191
- d = new Date(value.slice(5, -1));
3192
- if (d) {
3193
- return d;
3194
- }
3195
- }
3196
- return value;
3197
- });
3198
-
3199
-
3200
- This is a reference implementation. You are free to copy, modify, or
3201
- redistribute.
3202
- */
3203
-
3204
- /*jslint evil: true, regexp: true */
3205
-
3206
- /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
3207
- call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
3208
- getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
3209
- lastIndex, length, parse, prototype, push, replace, slice, stringify,
3210
- test, toJSON, toString, valueOf
3211
- */
3212
-
3213
-
3214
- // Create a JSON object only if one does not already exist. We create the
3215
- // methods in a closure to avoid creating global variables.
3216
-
3217
- if (typeof JSON !== 'object') {
3218
- JSON = {};
3219
- }
3220
-
3221
- (function () {
3222
- 'use strict';
3223
-
3224
- function f(n) {
3225
- // Format integers to have at least two digits.
3226
- return n < 10 ? '0' + n : n;
3227
- }
3228
-
3229
- if (typeof Date.prototype.toJSON !== 'function') {
3230
-
3231
- Date.prototype.toJSON = function (key) {
3232
-
3233
- return isFinite(this.valueOf())
3234
- ? this.getUTCFullYear() + '-' +
3235
- f(this.getUTCMonth() + 1) + '-' +
3236
- f(this.getUTCDate()) + 'T' +
3237
- f(this.getUTCHours()) + ':' +
3238
- f(this.getUTCMinutes()) + ':' +
3239
- f(this.getUTCSeconds()) + 'Z'
3240
- : null;
3241
- };
3242
-
3243
- String.prototype.toJSON =
3244
- Number.prototype.toJSON =
3245
- Boolean.prototype.toJSON = function (key) {
3246
- return this.valueOf();
3247
- };
3248
- }
3249
-
3250
- var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
3251
- escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
3252
- gap,
3253
- indent,
3254
- meta = { // table of character substitutions
3255
- '\b': '\\b',
3256
- '\t': '\\t',
3257
- '\n': '\\n',
3258
- '\f': '\\f',
3259
- '\r': '\\r',
3260
- '"' : '\\"',
3261
- '\\': '\\\\'
3262
- },
3263
- rep;
3264
-
3265
-
3266
- function quote(string) {
3267
-
3268
- // If the string contains no control characters, no quote characters, and no
3269
- // backslash characters, then we can safely slap some quotes around it.
3270
- // Otherwise we must also replace the offending characters with safe escape
3271
- // sequences.
3272
-
3273
- escapable.lastIndex = 0;
3274
- return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
3275
- var c = meta[a];
3276
- return typeof c === 'string'
3277
- ? c
3278
- : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
3279
- }) + '"' : '"' + string + '"';
3280
- }
3281
-
3282
-
3283
- function str(key, holder) {
3284
-
3285
- // Produce a string from holder[key].
3286
-
3287
- var i, // The loop counter.
3288
- k, // The member key.
3289
- v, // The member value.
3290
- length,
3291
- mind = gap,
3292
- partial,
3293
- value = holder[key];
3294
-
3295
- // If the value has a toJSON method, call it to obtain a replacement value.
3296
-
3297
- if (value && typeof value === 'object' &&
3298
- typeof value.toJSON === 'function') {
3299
- value = value.toJSON(key);
3300
- }
3301
-
3302
- // If we were called with a replacer function, then call the replacer to
3303
- // obtain a replacement value.
3304
-
3305
- if (typeof rep === 'function') {
3306
- value = rep.call(holder, key, value);
3307
- }
3308
-
3309
- // What happens next depends on the value's type.
3310
-
3311
- switch (typeof value) {
3312
- case 'string':
3313
- return quote(value);
3314
-
3315
- case 'number':
3316
-
3317
- // JSON numbers must be finite. Encode non-finite numbers as null.
3318
-
3319
- return isFinite(value) ? String(value) : 'null';
3320
-
3321
- case 'boolean':
3322
- case 'null':
3323
-
3324
- // If the value is a boolean or null, convert it to a string. Note:
3325
- // typeof null does not produce 'null'. The case is included here in
3326
- // the remote chance that this gets fixed someday.
3327
-
3328
- return String(value);
3329
-
3330
- // If the type is 'object', we might be dealing with an object or an array or
3331
- // null.
3332
-
3333
- case 'object':
3334
-
3335
- // Due to a specification blunder in ECMAScript, typeof null is 'object',
3336
- // so watch out for that case.
3337
-
3338
- if (!value) {
3339
- return 'null';
3340
- }
3341
-
3342
- // Make an array to hold the partial results of stringifying this object value.
3343
-
3344
- gap += indent;
3345
- partial = [];
3346
-
3347
- // Is the value an array?
3348
-
3349
- if (Object.prototype.toString.apply(value) === '[object Array]') {
3350
-
3351
- // The value is an array. Stringify every element. Use null as a placeholder
3352
- // for non-JSON values.
3353
-
3354
- length = value.length;
3355
- for (i = 0; i < length; i += 1) {
3356
- partial[i] = str(i, value) || 'null';
3357
- }
3358
-
3359
- // Join all of the elements together, separated with commas, and wrap them in
3360
- // brackets.
3361
-
3362
- v = partial.length === 0
3363
- ? '[]'
3364
- : gap
3365
- ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
3366
- : '[' + partial.join(',') + ']';
3367
- gap = mind;
3368
- return v;
3369
- }
3370
-
3371
- // If the replacer is an array, use it to select the members to be stringified.
3372
-
3373
- if (rep && typeof rep === 'object') {
3374
- length = rep.length;
3375
- for (i = 0; i < length; i += 1) {
3376
- if (typeof rep[i] === 'string') {
3377
- k = rep[i];
3378
- v = str(k, value);
3379
- if (v) {
3380
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
3381
- }
3382
- }
3383
- }
3384
- } else {
3385
-
3386
- // Otherwise, iterate through all of the keys in the object.
3387
-
3388
- for (k in value) {
3389
- if (Object.prototype.hasOwnProperty.call(value, k)) {
3390
- v = str(k, value);
3391
- if (v) {
3392
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
3393
- }
3394
- }
3395
- }
3396
- }
3397
-
3398
- // Join all of the member texts together, separated with commas,
3399
- // and wrap them in braces.
3400
-
3401
- v = partial.length === 0
3402
- ? '{}'
3403
- : gap
3404
- ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
3405
- : '{' + partial.join(',') + '}';
3406
- gap = mind;
3407
- return v;
3408
- }
3409
- }
3410
-
3411
- // If the JSON object does not yet have a stringify method, give it one.
3412
-
3413
- if (typeof JSON.stringify !== 'function') {
3414
- JSON.stringify = function (value, replacer, space) {
3415
-
3416
- // The stringify method takes a value and an optional replacer, and an optional
3417
- // space parameter, and returns a JSON text. The replacer can be a function
3418
- // that can replace values, or an array of strings that will select the keys.
3419
- // A default replacer method can be provided. Use of the space parameter can
3420
- // produce text that is more easily readable.
3421
-
3422
- var i;
3423
- gap = '';
3424
- indent = '';
3425
-
3426
- // If the space parameter is a number, make an indent string containing that
3427
- // many spaces.
3428
-
3429
- if (typeof space === 'number') {
3430
- for (i = 0; i < space; i += 1) {
3431
- indent += ' ';
3432
- }
3433
-
3434
- // If the space parameter is a string, it will be used as the indent string.
3435
-
3436
- } else if (typeof space === 'string') {
3437
- indent = space;
3438
- }
3439
-
3440
- // If there is a replacer, it must be a function or an array.
3441
- // Otherwise, throw an error.
3442
-
3443
- rep = replacer;
3444
- if (replacer && typeof replacer !== 'function' &&
3445
- (typeof replacer !== 'object' ||
3446
- typeof replacer.length !== 'number')) {
3447
- throw new Error('JSON.stringify');
3448
- }
3449
-
3450
- // Make a fake root object containing our value under the key of ''.
3451
- // Return the result of stringifying the value.
3452
-
3453
- return str('', {'': value});
3454
- };
3455
- }
3456
-
3457
-
3458
- // If the JSON object does not yet have a parse method, give it one.
3459
-
3460
- if (typeof JSON.parse !== 'function') {
3461
- JSON.parse = function (text, reviver) {
3462
-
3463
- // The parse method takes a text and an optional reviver function, and returns
3464
- // a JavaScript value if the text is a valid JSON text.
3465
-
3466
- var j;
3467
-
3468
- function walk(holder, key) {
3469
-
3470
- // The walk method is used to recursively walk the resulting structure so
3471
- // that modifications can be made.
3472
-
3473
- var k, v, value = holder[key];
3474
- if (value && typeof value === 'object') {
3475
- for (k in value) {
3476
- if (Object.prototype.hasOwnProperty.call(value, k)) {
3477
- v = walk(value, k);
3478
- if (v !== undefined) {
3479
- value[k] = v;
3480
- } else {
3481
- delete value[k];
3482
- }
3483
- }
3484
- }
3485
- }
3486
- return reviver.call(holder, key, value);
3487
- }
3488
-
3489
-
3490
- // Parsing happens in four stages. In the first stage, we replace certain
3491
- // Unicode characters with escape sequences. JavaScript handles many characters
3492
- // incorrectly, either silently deleting them, or treating them as line endings.
3493
-
3494
- text = String(text);
3495
- cx.lastIndex = 0;
3496
- if (cx.test(text)) {
3497
- text = text.replace(cx, function (a) {
3498
- return '\\u' +
3499
- ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
3500
- });
3501
- }
3502
-
3503
- // In the second stage, we run the text against regular expressions that look
3504
- // for non-JSON patterns. We are especially concerned with '()' and 'new'
3505
- // because they can cause invocation, and '=' because it can cause mutation.
3506
- // But just to be safe, we want to reject all unexpected forms.
3507
-
3508
- // We split the second stage into 4 regexp operations in order to work around
3509
- // crippling inefficiencies in IE's and Safari's regexp engines. First we
3510
- // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
3511
- // replace all simple value tokens with ']' characters. Third, we delete all
3512
- // open brackets that follow a colon or comma or that begin the text. Finally,
3513
- // we look to see that the remaining characters are only whitespace or ']' or
3514
- // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
3515
-
3516
- if (/^[\],:{}\s]*$/
3517
- .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
3518
- .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
3519
- .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
3520
-
3521
- // In the third stage we use the eval function to compile the text into a
3522
- // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
3523
- // in JavaScript: it can begin a block or an object literal. We wrap the text
3524
- // in parens to eliminate the ambiguity.
3525
-
3526
- j = eval('(' + text + ')');
3527
-
3528
- // In the optional fourth stage, we recursively walk the new structure, passing
3529
- // each name/value pair to a reviver function for possible transformation.
3530
-
3531
- return typeof reviver === 'function'
3532
- ? walk({'': j}, '')
3533
- : j;
3534
- }
3535
-
3536
- // If the text is not JSON parseable, then a SyntaxError is thrown.
3537
-
3538
- throw new SyntaxError('JSON.parse');
3539
- };
3540
- }
3541
- }());