1 | ////////////////////////////////////////////////////////////////////////////// |
2 | // |
3 | // spa.ja |
4 | // (c) Gareth Bult 2017 |
5 | // MIT License |
6 | // |
7 | // Depends on: jQuery |
8 | // |
9 | // Turn any website into a Single Page Application by including this code. |
10 | // |
11 | ////////////////////////////////////////////////////////////////////////////// |
12 | // |
13 | // we need a global reference so we can add 3rd parth callbacks |
14 | // |
15 | var iflex_spa_callbacks = iflex_spa_callbacks || {}; |
16 | // |
17 | // on-ready handler, jQuery 3 style |
18 | // |
19 | jQuery(function() { |
20 | |
21 | ////////////////////////////////////////////////////////////////////////// |
22 | // |
23 | // Configuration - this section will change and adapt as we build up a |
24 | // list of edge-cases. |
25 | // |
26 | ////////////////////////////////////////////////////////////////////////// |
27 | var href = window.location.href, |
28 | has_protocol = new RegExp('^(http|https):','i'), |
29 | is_local = new RegExp('^(http:|https:|)//' + window.location.host,'i'), |
30 | is_download = new RegExp('\.(iso|torrent|sig|zip)$'), |
31 | wp_admin = new RegExp('(/wp-admin/|/wp-login.php)'), |
32 | |
33 | ////////////////////////////////////////////////////////////////////////// |
34 | // |
35 | // jQuery_monkeypatch - add error trapping to the globalEval call |
36 | // |
37 | ////////////////////////////////////////////////////////////////////////// |
38 | jQuery_monkeypatch = function() { |
39 | jQuery.globalEval = function(b) { |
40 | var a = window; |
41 | b && jQuery.trim(b) && (a.execScript || function(b) { |
42 | try { |
43 | a.eval.call(a, b) |
44 | } catch(error) { |
45 | console.log(error, b); |
46 | } |
47 | })(b) |
48 | } |
49 | }, |
50 | |
51 | ////////////////////////////////////////////////////////////////////////// |
52 | // |
53 | // scroll_top - reposition the page on a given #tag |
54 | // |
55 | ////////////////////////////////////////////////////////////////////////// |
56 | scroll_top = function(tag) { |
57 | if(jQuery('#'+tag)) { |
58 | node = jQuery('#'+tag).offset(); |
59 | jQuery('html, body').animate({scrollTop: node.top + 'px'}, 'fast'); |
60 | } |
61 | }, |
62 | |
63 | ////////////////////////////////////////////////////////////////////////// |
64 | // |
65 | // set_position - reposition the page based on the current #tag and link |
66 | // |
67 | ////////////////////////////////////////////////////////////////////////// |
68 | set_position = function(tag,htag) { |
69 | if(htag&&(jQuery('#'+htag).length)) return scroll_top(htag); |
70 | if(tag=='href') jQuery('html,body').scrollTop(0); |
71 | }, |
72 | |
73 | ////////////////////////////////////////////////////////////////////////// |
74 | // |
75 | // move_scripts - move a bunch of scripts to a new target location |
76 | // |
77 | ////////////////////////////////////////////////////////////////////////// |
78 | move_scripts = function(scripts, target) { |
79 | jQuery.each(scripts, function() { |
80 | var script = document.createElement('script'), node = target.lastChild; |
81 | if(this.src) { |
82 | script.type = this.type; |
83 | script.src = this.src; |
84 | } else { |
85 | script.innerHTML = this.innerHTML; |
86 | } |
87 | node.parentNode.insertBefore(script, node.nextSibling); |
88 | }); |
89 | }, |
90 | |
91 | ////////////////////////////////////////////////////////////////////////// |
92 | // |
93 | // reload_head - add new entries to page head and remove obsoletes |
94 | // |
95 | ////////////////////////////////////////////////////////////////////////// |
96 | reload_head = function(head) { |
97 | var live = jQuery('head').find('*'); |
98 | jQuery.each(jQuery(head).find('*'), function() { |
99 | var raw = jQuery(this).get()[0], i=live.length; |
100 | while(--i >= 0) { |
101 | if(raw.isEqualNode(jQuery(live[i]).get()[0])) { |
102 | live.splice(i,1); |
103 | break; |
104 | } |
105 | } |
106 | if(i<0) jQuery('head').append(this); |
107 | }); |
108 | jQuery.each(jQuery('head').find('*'), function() { |
109 | var raw = jQuery(this).get()[0], old = this; |
110 | jQuery.each(live, function(k,v) { |
111 | if(raw.isEqualNode(jQuery(v).get()[0])) jQuery(old).remove(); |
112 | }); |
113 | }); |
114 | }, |
115 | |
116 | ////////////////////////////////////////////////////////////////////////// |
117 | // |
118 | // load_page - bulk of the work, replace the current page with a new one |
119 | // |
120 | ////////////////////////////////////////////////////////////////////////// |
121 | load_page = function(tag, href, data, navigate) { |
122 | var target, base, url, htag, |
123 | replace_page = function(data,status,xhr) { |
124 | var mtype,stype,content_type = xhr.getResponseHeader('Content-Type').split(";")[0]; |
125 | [mtype,stype] = content_type.split("/"); |
126 | switch(mtype) { |
127 | case 'text': |
128 | var url, htag, head, body, hscripts, bscripts, |
129 | node = document.createElement("html"); |
130 | window.onload = null; // make sure this is clear for things like SMF |
131 | node.innerHTML = data; |
132 | head = node.getElementsByTagName("head")[0]; |
133 | document.title = jQuery(head).find('title').text(); |
134 | body = node.getElementsByTagName("body")[0]; |
135 | hscripts = jQuery(head).find('script').remove().get(); |
136 | bscripts = jQuery(body).find('script').remove().get(); |
137 | reload_head(head); |
138 | move_scripts(hscripts, body); |
139 | move_scripts(bscripts, body); |
140 | jQuery_monkeypatch(); |
141 | jQuery("body").html(body.innerHTML); |
142 | [url,htag] = href.split('#'); |
143 | set_position(tag,htag); |
144 | setTimeout(function(){jQuery(window).trigger('load');},100); |
145 | break; |
146 | case 'application': |
147 | case 'image': |
148 | var win = window.open(href, '_blank'); |
149 | win.focus(); |
150 | return false; |
151 | default: |
152 | alert('Not configured handle file type: ', content_type); |
153 | return false; |
154 | } |
155 | if(navigate) history.pushState({href: href}, null, href); |
156 | jQuery.each(iflex_spa_callbacks, function(name,routine){ |
157 | routine(); |
158 | }); |
159 | }; |
160 | switch(tag) { |
161 | case 'action': |
162 | target = href; |
163 | href = window.location.href; |
164 | jQuery.ajax({ |
165 | url: target, |
166 | data: data, |
167 | type: 'POST', |
168 | success: replace_page, |
169 | processData: false, |
170 | contentType: false, |
171 | cache: false |
172 | }); |
173 | break; |
174 | case 'href': |
175 | base = window.location.href.split('#')[0]; |
176 | [url,htag] = href.split('#'); |
177 | if(htag&&(!url||url==base)) return scroll_top(htag); |
178 | if(!htag||(url&&(url!=base))) jQuery.get(href, replace_page).fail(function(){ |
179 | alert('Unable to locate url '+href); |
180 | }); |
181 | } |
182 | }, |
183 | |
184 | ////////////////////////////////////////////////////////////////////////// |
185 | // |
186 | // outside - work out if a given URL is going to cause us to leave site |
187 | // |
188 | ////////////////////////////////////////////////////////////////////////// |
189 | outside = function(href) { |
190 | if(has_protocol.test(href) && !is_local.test(href)) return true; |
191 | if(is_download.test(href)||wp_admin.test(href)) return true; |
192 | return false; |
193 | }, |
194 | |
195 | ////////////////////////////////////////////////////////////////////////// |
196 | // |
197 | // wrap - assign a new handler to an element (if appropriate) |
198 | // |
199 | ////////////////////////////////////////////////////////////////////////// |
200 | wrap = function(self, tag, handler) { |
201 | // |
202 | // if we have no url, or the url is outside the site ... |
203 | // |
204 | var href = jQuery(self).attr(tag); |
205 | if(!href||(href=='#')||outside(href)) return false; |
206 | // |
207 | // add a handler to override the default process |
208 | // |
209 | jQuery(self).on(handler, function(event){ |
210 | var not_supported = false, |
211 | data = null, |
212 | button = null; |
213 | // |
214 | // make sure we've not been beaten to the punch |
215 | // |
216 | if(event.isDefaultPrevented()) return false; |
217 | switch(tag) { |
218 | case 'action': |
219 | // |
220 | // for POST requests we're going to use the results from |
221 | // FormData, and add the name/value of the submit button |
222 | // |
223 | data = new FormData(this); |
224 | button = document.activeElement; |
225 | if(button.name) data.append(button.name, button.value) |
226 | break; |
227 | case 'href': |
228 | break; |
229 | default: |
230 | not_supported = true; |
231 | break; |
232 | } |
233 | if(not_supported) return; |
234 | event.preventDefault(); |
235 | load_page(tag,href,data,true); |
236 | }); |
237 | }, |
238 | ////////////////////////////////////////////////////////////////////////// |
239 | // |
240 | // back_butto - retask the back button to do a soft back if within site |
241 | // |
242 | ////////////////////////////////////////////////////////////////////////// |
243 | back_button = function() { |
244 | // |
245 | // If we're run out of history, exit the site |
246 | // |
247 | if(!history.state) return window.history.back(); |
248 | // |
249 | // Otherwise, implement our own version of bac |
250 | // |
251 | load_page('href', history.state.href, null, false); |
252 | }; |
253 | ////////////////////////////////////////////////////////////////////////// |
254 | // |
255 | // ~~~ MAIN ~~~ |
256 | // |
257 | ////////////////////////////////////////////////////////////////////////// |
258 | // |
259 | // wrap all A links for FORMs |
260 | // |
261 | jQuery('a').each(function(){wrap(this,'href','click')}); |
262 | jQuery('form').each(function(){wrap(this,'action','submit')}); |
263 | // |
264 | // wrap the browser's 'BACK' button |
265 | // |
266 | jQuery(window).off('popstate').on('popstate', back_button); |
267 | if(history.state) return; |
268 | // |
269 | // record our first page of the session |
270 | // |
271 | history.pushState({href:href},null,href); |
272 | }); |
Travelled to 3 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx
No comments. add comment
Snippet ID: | #1031909 |
Snippet name: | iflex_spa.js - turn website into single page app |
Eternal ID of this version: | #1031909/1 |
Text MD5: | 86b634ecae08ea3f51d33b22f7807428 |
Author: | stefan |
Category: | javascript |
Type: | Document |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-07-25 05:57:19 |
Source code size: | 10789 bytes / 272 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 151 / 67 |
Referenced in: | [show references] |