'use strict' import $ from 'jquery' import Events from '../_events' import Spinner from './_ui.spinner' const AjaxUI = (($) => { // Constants const G = window const D = document const $Html = $('html') const $Body = $('body') const NAME = 'jsAjaxUI' const DATA_KEY = NAME class AjaxUI { // Constructor constructor (element) { this._element = element const $element = $(this._element) $element.addClass(`${NAME}-active`) $element.bind('click', function (e) { e.preventDefault() const $this = $(this) $('.ajax').each(function () { const $this = $(this) $this.removeClass('active') $this.parents('.nav-item').removeClass('active') }) $this.addClass('loading') AjaxUI.load($this.attr('href'), () => { $this.removeClass('loading') $this.parents('.nav-item').addClass('active') $this.addClass('active') }) }) } // Public methods static load (url, callback) { // show spinner Spinner.show(() => { $Body.removeClass('loaded') }) // update document location G.MainUI.updateLocation(url) const absoluteLocation = G.URLDetails.base + G.URLDetails.relative.substring(1) if (absoluteLocation !== G.location.href) { G.history.pushState( { ajax: true, page: absoluteLocation, }, document.title, absoluteLocation ) } $.ajax({ sync: false, async: true, url, dataType: 'json', method: 'GET', cache: false, error (jqXHR) { console.warn(`${NAME}: AJAX request failure: ${jqXHR.statusText}`) G.location.href = url // google analytics if (typeof G.ga === 'function') { G.ga('send', 'event', 'error', 'AJAX ERROR', jqXHR.statusText) } }, success (data, status, jqXHR) { AjaxUI.process(data, jqXHR, callback) // google analytics if (typeof G.ga === 'function') { G.ga('set', { page: G.URLDetails.relative + G.URLDetails.hash, title: jqXHR.getResponseHeader('X-Title'), }) G.ga('send', 'pageview') } }, }) } static process (data, jqXHR, callback) { const css = jqXHR.getResponseHeader('X-Include-CSS').split(',') || [] const js = jqXHR.getResponseHeader('X-Include-JS').split(',') || [] // Replace HTML regions if (typeof data.regions === 'object') { for (const key in data.regions) { if (typeof data.regions[key] === 'string') { AjaxUI.replaceRegion(data.regions[key], key) } } } // remove already loaded scripts $('link[type="text/css"]').each(function () { const i = css.indexOf($(this).attr('href')) if (i > -1) { css.splice(i, 1) } else if (!$Body.data('unload-blocked')) { console.log(`${NAME}: Unloading | ${$(this).attr('href')}`) $(this).remove() } }) $('script[type="text/javascript"]').each(function () { const i = js.indexOf($(this).attr('src')) if (i > -1) { js.splice(i, 1) } else if (!$Body.data('unload-blocked')) { console.log(`${NAME}: Unloading | ${$(this).attr('src')}`) $(this).remove() } }) // preload CSS this.preload(css).then(() => { const $head = $('head') css.forEach((el) => { $head.append( `` ) }) // preload JS this.preload(js, 'script').then(() => { js.forEach((el) => { $Body.append( `` ) }) console.log(`${NAME}: New page is loaded!`) // trigger events if (typeof data.events === 'object') { for (const eventName in data.events) { $(D).trigger(eventName, [data.events[eventName]]) } } if (typeof callback !== 'undefined') { callback() } $(G).trigger(Events.AJAX) }) }) } static preload (items, type = 'text', cache = true, itemCallback = false) { if (!items.length) { return $.Deferred().resolve().promise() } const dfds = [] items.forEach((url, i) => { const dfd = $.Deferred() $.ajax({ dataType: type, cache, url, }).always(() => { dfd.resolve() if (itemCallback) { itemCallback(i, url) } }) dfds.push(dfd) }) // return a master promise object which will resolve when all the deferred objects have resolved return $.when(...dfds) } static replaceRegion (html, key) { const $region = $(`[data-ajax-region="${key}"]`) if ($region.length) { $region.empty().append(html) } else { console.warn(`${NAME}: Region returned without class or id!`) } } dispose () { const $element = $(this._element) $element.removeClass(`${NAME}-active`) $.removeData(this._element, DATA_KEY) this._element = null } static _jQueryInterface () { return this.each(function () { // attach functionality to element const $element = $(this) let data = $element.data(DATA_KEY) if (!data) { data = new AjaxUI(this) $element.data(DATA_KEY, data) } }) } } // jQuery interface $.fn[NAME] = AjaxUI._jQueryInterface $.fn[NAME].Constructor = AjaxUI $.fn[NAME].noConflict = function () { $.fn[NAME] = JQUERY_NO_CONFLICT return AjaxUI._jQueryInterface } // auto-apply $('.ajax').ready(() => { $('.ajax').jsAjaxUI() }) // AJAX update browser title $(D).on('layoutRefresh', (e, data) => { D.title = data.Title $Html.attr('class', '') if (data.ClassName) { $Html.addClass(data.ClassName) } // data.Link = (data.Link === '/home/') ? '/' : data.Link; }) // Back/Forward functionality G.onpopstate = function (event) { const $existingLink = $(`a[href^="${D.location}"]`) if (event.state !== null && event.state.ajax) { console.log(`${NAME}: GOBACK (AJAX state)`) AjaxUI.load(event.state.page) } else if ($existingLink.length && $existingLink.hasClass('ajax')) { console.log(`${NAME}: GOBACK (AJAX link)`) $existingLink.trigger('click') } else if (D.location.href !== G.location.href) { console.log(`${NAME}: GOBACK (HTTP)`) G.location.href = D.location } } // manage AJAX requests $.ajaxPrefilter((options, originalOptions, jqXHR) => { jqXHR.opts = options $.xhrPool.requests[jqXHR.opts.url] = jqXHR }) $.xhrPool = { requests: {}, paused: false, pauseAll: () => { $.xhrPool.paused = true /* for (let url in $.xhrPool.requests) { const jqXHR = $.xhrPool.requests[url]; jqXHR.abort(); console.log(`${NAME}: AJAX request is paused (${jqXHR.opts.url})`); } */ }, restoreAll: () => { for (const url in $.xhrPool.requests) { const jqXHR = $.xhrPool.requests[url] $.ajax(jqXHR.opts) console.log(`${NAME}: AJAX request is restored (${jqXHR.opts.url})`) } $.xhrPool.paused = false }, } $.ajaxSetup({ beforeSend: (jqXHR) => {}, // and connection to list complete: (jqXHR) => { if (!$.xhrPool.paused) { // console.log(`${NAME}: AJAX request is done (${jqXHR.opts.url})`); delete $.xhrPool.requests[jqXHR.opts.url] } }, }) // attach events $Body.on(`${Events.OFFLINE}`, () => { $.xhrPool.pauseAll() }) $Body.on(`${Events.BACKONLINE}`, () => { $.xhrPool.restoreAll() }) return AjaxUI })($) export default AjaxUI