/* * Ajaxify.js * Ajaxify your site out of the box, instantly. * http://4nf.org/ * * Copyright 2014 Arvind Gupta; MIT Licensed */ //Intuitively better understandable shorthand for String.indexOf() - String.iO() String.prototype.iO = function(s) { return this.toString().indexOf(s) + 1; }; //Module global variables var l=0, pass=0, api=window.history && window.history.pushState && window.history.replaceState; //Regexes for escaping fetched HTML of a whole page - best of Balupton's "Ajaxify" //Makes it possible to pre-fetch an entire page var docType = /<\!DOCTYPE[^>]*>/i; var tagso = /<(html|head|body|title|meta|script|link)([\s\>])/gi; var tagsc = /<\/(html|head|body|title|meta|script|link)\>/gi; //Helper strings var div12 = '
); anywhere in Ajaxify where you would like to peek into (function ($) { var Log = function (options) { var con = window.console; var settings = $.extend({ verbosity: 0 }, options); var verbosity = settings.verbosity; this.a = function (m) { if(l < verbosity && con) con.log(m); }; }; $.log = function (m, options) { if (!$.log.o) $.log.o = new Log(options); return $.log.o.a(m); }; })(jQuery); // The stateful Cache plugin // Usage: // 1) $.cache() - returns currently cached page // 2) $.cache() - returns page with specified URL // 3) $.cache() - saves the page in cache (function ($) { var Cache = function () { var d = false; this.a = function (o) { if (!o) { return d; } if (typeof o === "string") { if(o === "f") { $.pages("f"); d = undefined; } else d = $.pages($.memory(o)); return d; } if (typeof o === "object") { d = o; return d; } }; }; $.cache = function (o) { if (!$.cache.o) $.cache.o = new Cache(); return $.cache.o.a(o); }; })(jQuery); // The stateful Memory plugin // Usage: $.memory() - returns the same URL if not turned off internally (function ($) { var Memory = function (options) { var d = false; var settings = $.extend({ memoryoff: false }, options); var memoryoff = settings.memoryoff; this.a = function (h) { d = memoryoff; if (!h || d === true) return false; if (d === false) return h; if (d.iO(", ")) { d = d.split(", "); if (d.iO(h)) return false; else return h; } return h == d ? false : h; }; }; $.memory = function (h, options) { if (!$.memory.o) $.memory.o = new Memory(options); return $.memory.o.a(h); }; })(jQuery); // The stateful Pages plugin // Usage: // 1) $.pages() - returns page with specified URL from internal array // 2) $.pages() - saves the passed page in internal array // 3) $.pages(false) - returns false (function ($) { var Pages = function () { var d = []; this.a = function (h) { if (typeof h === "string") { if(h === "f") d = []; else for (var i = 0; i < d.length; i++) if (d[i][0] == h) return d[i][1]; } if (typeof h === "object") { d.push(h); } if (typeof h === "boolean") { return false; } }; }; $.pages = function (h) { if (!$.pages.o) $.pages.o = new Pages(); return $.pages.o.a(h); }; })(jQuery); // The GetPage plugin // First parameter is a switch: // empty - returns cache // - loads HTML via Ajax // "+" - pre-fetches page // "-" - loads page into DOM and handle scripts // otherwise - returns selection of current page to client (function ($) { var GetPage = function () { this.a = function (o, p, p2) { if (!o) { return $.cache(); } if (o.iO("/")) { return _lPage(o, p, p2); } if (o === "+") { return _lPage(p, p2, false, true); } if (o === "-") { return _lSel(p, p2); } if($.cache()) return $.cache().find("[data-ajax-class='ajy-" + o + "']"); }; function _lSel(p, $t) { //load page into DOM and handle scripts pass++; _lDivs($t); $.scripts(p); $.scripts("s"); $.scripts("a"); return $.scripts("c"); } function _lPage(h, p, post, pre) { //fire Ajax load, check for hash first if (h.iO("#")) h = h.split("#")[0]; if (post || !$.cache(h)) return _lAjax(h, p, post, pre); if(p) p(); } function _ld($t, $h) { $h.find("[data-ajax-class='ajy-script']").each(function(){ if(!($(this).attr("src"))) $(this).replaceWith(''); else $(this).replaceWith(scri.replace('*', $(this).attr("src"))); }); $t.html($h.html()); } function _lDivs($t) { //load target divs into DOM if ($.cache()) $t.each(function () { _ld($(this), $.cache().find("#" + $(this).attr("id"))); }); } function _lAjax(hin, p, post, pre) { //execute Ajax load var xhr = $.ajax({ url: hin, type: post ? "POST" : "GET", data: post ? post.data : null, success: function (h) { if (!h || !_isHtml(xhr)) { if (!pre) location.href = hin; return; } $.cache($(_parseHTML(h))); $.pages([hin, $.cache()]); if(p) p(); }, error: function(e){ if(e.status == '404') { window.location = nectarLove.rooturl + '/404'; } } }); } function _isHtml(x) { //restrict interesting MIME types - only HTML / XML var d; return (d = x.getResponseHeader("Content-Type")), d && (d.iO("text/html") || d.iO("text/xml")); } function _parseHTML(h) { //process fetched HTML return $.trim(_replD(h)); } function _replD(h) { //pre-process HTML so it can be loaded by jQuery return String(h).replace(docType, "").replace(tagso, div12).replace(tagsc, "
"); } }; $.getPage = function (o, p, p2) { if (!$.getPage.o) $.getPage.o = new GetPage(); return $.getPage.o.a(o, p, p2); }; })(jQuery); // The main plugin - Ajaxify // Is passed the global options // Checks for necessary pre-conditions - otherwise gracefully degrades // Initialises sub-plugins // Calls Pronto (function ($) { var Ajaxify = function (options) { var settings = $.extend({ pluginon: true, fn: $.getPage }, options); var pluginon = settings.pluginon; //404 check if(window.location.href.indexOf("/404") > -1) pluginon = false; this.a = function ($this) { $(function () { if (_init(settings)) { $this.pronto(settings); $.getPage(location.href, $.scripts); } }); }; function _init(s) { if (!api || !pluginon) return false; $.scripts("i", s); $.cache(0, s); $.memory(0, s); return true; } }; $.fn.ajaxify = function (options) { var $this = $(this); if (!$.fn.ajaxify.o) $.fn.ajaxify.o = new Ajaxify(options); return $.fn.ajaxify.o.a($this); }; })(jQuery); // The stateful Scripts plugin // First parameter is switch: // "i" - initailise options // "a" - handle inline scripts // "c" - fetch canonical URL // otherwise - delta loading (function ($) { var Scripts = function (options) { var $s = $(); var settings = $.extend({ canonical: true, inline: true, inlinehints: false, style: true }, options); var canonical = settings.canonical, inline = settings.inline, inlinehints = settings.inlinehints, style = settings.style; this.a = function (o) { if (o === "i") { return true; } if (o === "s") { return _allstyle($s.y); } if (o === "a") { return _alltxts($s.t); } if (o === "c") { if (canonical && $s.can) return $s.can.attr("href"); else return false; } $.detScripts($s); //fetch all scripts _addScripts(o, $s, settings); //delta-loading }; function _allstyle($s) { if (!style) return; $("head").find("style").remove(); $s.each(function () { var d = $(this).text(); _addstyle(d); }); } function _alltxts($s) { $s.each(function () { var d = $(this).text(); if (!d.iO(").ajaxify(") && (inline || $(this).hasClass("ajaxy") || _inline(d))) _addtext(d); }); } function _addtext(t) { try { $.globalEval(t); } catch (e) { //alert(e); } } function _addstyle(t) { $("head").append(''); } function _inline(txt) { var d = inlinehints; if (d) { d = d.split(", "); for (var i = 0; i < d.length; i++) if (txt.iO(d[i])) return true; } } function _addScripts(same, $s, st) { $s.c.addAll(same, "href", st); $s.s.addAll(same, "src", st); } }; $.scripts = function (o, options) { if (!$.scripts.o) $.scripts.o = new Scripts(options); return $.scripts.o.a(o); }; })(jQuery); // The DetScripts plugin - stands for "detach scripts" // Works on "$s" jQuery object that is passed in and fills it // Fetches all stylesheets in the head // Fetches the canonical URL // Fetches all external scripts on the page // Fetches all inline scripts on the page (function ($) { var DetScripts = function () { var head, lk, j; this.a = function ($s) { head = $.getPage("head"); lk = head.find("[data-ajax-class='ajy-link']"); j = $.getPage("script"); $s.c = _rel(lk, "stylesheet"); $s.y = head.find("style"); $s.can = _rel(lk, "canonical"); $s.s = j.filter(function () { return $(this).attr("src"); }); $s.t = j.filter(function () { return !($(this).attr("src")); }); }; function _rel(lk, v) { return $(lk).filter(function () { return $(this).attr("rel").iO(v); }); } }; $.detScripts = function ($s) { if (!$.detScripts.o) $.detScripts.o = new DetScripts(); return $.detScripts.o.a($s); }; })(jQuery); (function ($) { var AddAll = function (options) { var $scriptsO, $scriptsN, $sCssO = [], $sCssN = [], $sO = [], $sN = []; var settings = $.extend({ deltas: true }, options); var deltas = settings.deltas; this.a = function ($this, same, PK) { if (PK == "href") { $scriptsO = $sCssO; $scriptsN = $sCssN; } else { $scriptsO = $sO; $scriptsN = $sN; } if (_allScripts($this, PK)) return true; if (pass) _classAlways($this, PK); if (same) return _sameScripts($scriptsN, PK); $scriptsN = []; _newArray($this, $scriptsN, $scriptsO, PK); if (pass) { _findCommon($scriptsO, $scriptsN); _freeOld($scriptsO, PK); _sameScripts($scriptsN, PK); $scriptsO = $scriptsN.slice(); } if (PK == "href") { $sCssO = $scriptsO; $sCssN = $scriptsN; } else { $sO = $scriptsO; $sN = $scriptsN; } }; function _allScripts($t, PK) { if (deltas) return false; $t.each(function () { _iScript($(this)[0], PK); }); return true; } function _classAlways($t, PK) { $t.each(function () { if ($(this).attr("class") == "always") { _iScript($(this).attr(PK), PK); // $(this).remove(); } }); } function _sameScripts(s, PK) { //var exlucdedNSScript = 'nectar-slider.js'; var exlucdedChartScript = 'vc_chart.js'; var exlucdedProgressScript = 'ProgressCircle.js'; for (var i = 0; i < s.length; i++) if (s[i][1] === 0 && !s[i][0].match(exlucdedChartScript) && !s[i][0].match(exlucdedProgressScript) ) _iScript(s[i][0], PK); } function _iScript($S, PK) { $("head").append((PK == "href" ? linki : scri).replace("*", $S)); } function _newArray($t, sN, sO, PK) { var d; $t.each(function () { d = [$(this).attr(PK), 0]; sN.push(d); if (!pass) sO.push(d); }); } function _findCommon(s, sN) { for (var i = 0; i < s.length; i++) { s[i][1] = 2; if (_findScript(s[i][0], sN)) s[i][1] = 1; } } function _findScript($S, s) { if ($S) for (var i = 0; i < s.length; i++) if (s[i][0] == $S) { s[i][1] = 1; return true; } } function _freeOld(s, PK) { for (var i = 0; i < s.length; i++) if (s[i][1] == 2 && s[i][0]) _removeScript(s[i][0], PK); } function _removeScript($S, PK) { $((PK == "href" ? linkr : scrr).replace("!", $S)).remove(); } }; $.fn.addAll = function (same, PK, options) { var $this = $(this); if (!$.fn.addAll.o) $.fn.addAll.o = new AddAll(options); return $.fn.addAll.o.a($this, same, PK); }; })(jQuery); (function ($) { var Pronto = function (options) { var $window = $(window), currentURL = '', requestTimer = null, post = null, $gthis, fm; // Default Options var settings = $.extend({ selector: "a:not(.no-ajaxy)", requestDelay: 0, forms: true, turbo: true, previewoff: true, fn: false, cb: 0 }, options); //Shorthands var selector = settings.selector, requestDelay = settings.requestDelay, forms = settings.forms, turbo = settings.turbo, previewoff = settings.previewoff, cb = settings.cb, fn = settings.fn; // Main plugin function this.a = function ($this) { $gthis = $this; _init_p(); return $this; }; // Private Methods function _init_p() { settings.$body = $("body"); currentURL = window.location.href; // Capture current url & state _saveState(); // Set initial state $window.on("popstate", _onPop); //Set handler for popState if (turbo) $(selector).hoverIntent(_prefetch, _drain); //If "turbo" option defined then set handler to "_prefetch" on hoverIntent settings.$body.on("click.pronto", selector, _click); //For real clicks set handler to _click() //_ajaxify_forms(); } //Dummy function for hoverIntent function _drain() {} //Prefetch target page on hoverIntent function _prefetch(e) { post = null; // Assume not a POST var link = e.currentTarget; //Validate link internal and not the same URL if (_diffHost(link)) return false; if (currentURL == link.href) return false; var req2 = function () { if (previewoff === true) return false; if (!_isInDivs(link) && (previewoff === false || !$(link).closest(previewoff).length)) _click(e, true); }; fn('+', link.href, req2); } function _isInDivs(link) { var isInDivs = false; $gthis.each(function () { try { if ($(link).parents("#" + $(this).attr("id")).length > 0) isInDivs = true; } catch (e) { //alert(e); } }); return isInDivs; } function _b(m, n) { if (m.indexOf("?") > 0) { m = m.substring(0, m.indexOf("?")); } return m + "?" + n; } function _k() { var o = fm.serialize(); var n = $("input[name][type=submit]", fm); if (n.length === 0) return o; var p = n.attr("name") + "=" + n.val(); if (o.length > 0) { o += "&" + p; } else { o = p; } return o; } function _ajaxify_forms() { if (!forms) return false; $('form').submit(function (q) { fm = $(q.target); if (!fm.is("form")) { fm = fm.filter("input[type=submit]").parents("form:first"); if (fm.length === 0) { return true; } } var p = _k(); var g = "get", m = fm.attr("method"); if (m.length > 0 && m.toLowerCase() == "post") g = "post"; var h, a = fm.attr("action"); if (a !== null && a.length > 0) h = a; else h = currentURL; if (g == "get") h = _b(h, p); else { post = {}; post.data = p; } $window.trigger("pronto.submit", h); _request(h); return false; }); } // Handle link clicks function _click(e, mode) { var link = e.currentTarget; //stop plugin pages from triggering ajax if( $.inArray(link.href, nectarLove.pluginPages) !== -1 ) { document.location.href = link.href; return false; } post = null; if (_exoticKey(e) || _diffHost(link)) return; // Ignore everything but normal click and internal URLs if (_hashChange(link)) { // Only the hash part has changed _saveState(); // Update state on hash change return true; } if(typeof window.stop === 'function' && $('body').hasClass('ajax-loaded')) { window.stop(); } e.preventDefault(); e.stopPropagation(); if (currentURL == link.href) { _saveState(); } else _request(e, mode); } // Request new url function _request(e, mode) { var href = typeof(e) !== "string" ? e.currentTarget.href : e; $window.trigger("pronto.request", e); // Fire request event var reqr = function () { //Callback - continue with _render() _render(e, true, mode); }; fn(href, reqr, post); //Call "fn" - handler of parent, informing whether POST or not } function _render(e, doPush, mode) { if($(window).scrollTop() > 0) { requestDelay = 850; } else { requestDelay = 450; } if (requestTimer !== null) { clearTimeout(requestTimer); requestTimer = null; } requestTimer = setTimeout(function () { _doRender(e, doPush, mode); }, requestDelay); } // Save current state function _saveState() { history.replaceState({ // Update state url: currentURL }, "state-" + currentURL, currentURL); } // Handle back/forward navigation function _onPop(e) { var data = e.originalEvent.state; // Check if data exists if (data !== null && data.url !== currentURL) { $window.trigger("pronto.request", e); // Fire request event var req3 = function () { //Callback - continue with _render() _render(e, false, false); }; fn(data.url, req3); //Call "fn" - handler of parent, passing URL } } // Push new states to the stack on new url function _doPush(url, doPush) { currentURL = url; if (doPush) { history.pushState({ url: currentURL }, "state-" + currentURL, currentURL); } else { _saveState(); } } // Render HTML function _doRender(e, doPush, mode) { var url, canURL; //Canonical URL url = typeof(e) !== "string" ? e.currentTarget.href || e.originalEvent.state.url : e; $window.trigger("pronto.load", e); // Fire load event _gaCaptureView(url); // Trigger analytics page view _saveState(); // Update current state $('title').html(fn('title').html()); // Update title //update meta attrs $('meta[property="og:url"]').attr('content',fn('meta').filter('[property="og:url"]').attr('content')); $('meta[property="og:title"]').attr('content',fn('meta').filter('[property="og:title"]').attr('content')); $('meta[property="og:type"]').attr('content',fn('meta').filter('[property="og:type"]').attr('content')); if(fn('meta').filter('[property="og:description"]').length > 0 && $('meta[property="og:description"]').length == 0){ $('head').append(''); } else if(fn('meta').filter('[property="og:description"]').length == 0 && $('meta[property="og:description"]').length > 0) { $('meta[property="og:description"]').remove(); } else { $('meta[property="og:description"]').attr('content',fn('meta').filter('[property="og:description"]').attr('content')); } if(fn('meta').filter('[property="og:image"]').length > 0 && $('meta[property="og:image"]').length == 0){ $('head').append(''); } else if(fn('meta').filter('[property="og:image"]').length == 0 && $('meta[property="og:image"]').length > 0) { $('meta[property="og:image"]').remove(); } else { $('meta[property="og:image"]').attr('content',fn('meta').filter('[property="og:image"]').attr('content')); } $('body').attr('class',fn('body').attr('class')); // Update class $('body').addClass('ajax-loaded'); $('body').attr('data-bg-header',fn('body').attr('data-bg-header')); //Update body attr if($('#wpadminbar').length > 0) $('#wpadminbar').html(fn('body').find('#wpadminbar').html()); // Update admmin bar var $wooCartHasProducts = ($('.sf-menu').hasClass('product_added')) ? 'product_added' : null; var $wooCart = $('#header-outer .cart-outer').clone(); $('#header-outer').html(fn('body').find('#header-outer').html()); //Update header nav $('#header-outer .cart-outer').replaceWith($wooCart); $('#header-outer').removeClass('directional-nav-effect'); if($wooCartHasProducts == 'product_added') $('#header-outer .sf-menu').addClass($wooCartHasProducts); //Update header attr if(fn('body').find('#header-outer[data-transparent-header]').length > 0) { $('#header-outer').attr('data-transparent-header',fn('body').find('#header-outer').attr('data-transparent-header')); if($(window).scrollTop() == '0') $('#header-outer').addClass('transparent'); } else { $('#header-outer').removeAttr('data-transparent-header'); $('#header-outer').removeClass('transparent'); } if(fn('body').find('#header-outer[data-transparency-option]').length > 0) { $('#header-outer').attr('data-transparency-option',fn('body').find('#header-outer').attr('data-transparency-option')); } //handle mediaElement fn('body').find('video.wp-video-shortcode, audio.wp-audio-shortcode').removeClass().addClass('wp-media-shortcode-ajax'); // Update DOM and fetch canonical URL - important for handling re-directs canURL = fn('-', post, $gthis); //Set current URL to canonical if no hash or parameters in current URl if (canURL && canURL != url && !url.iO('#') && !url.iO('?')) url = canURL; //_ajaxify_forms(); //If hash in URL animate scroll to it /* if (url.iO('#') && !mode) { $('html, body').animate({ scrollTop: $('#' + url.split('#')[1]).offset().top }, 500); }*/ _doPush(url, doPush); // Push new states to the stack on new url $window.trigger("pronto.render", e); // Fire render event if(cb) cb(); } // Google Analytics support function _gaCaptureView(url) { if (typeof window.ga !== 'undefined') window.ga('send', 'pageview', url); } function _diffHost(link) { return (window.location.protocol !== link.protocol || window.location.host !== link.host); } function _exoticKey(e) { return (e.which > 1 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey); } function _hashChange(link) { return (link.hash && link.href.replace(link.hash, '') === window.location.href.replace(location.hash, '') || link.href === window.location.href + '#'); } }; // Define Plugin $.fn.pronto = function (options) { var $this = $(this); if (!$.fn.pronto.o) $.fn.pronto.o = new Pronto(options); return $.fn.pronto.o.a($this); }; })(jQuery);