From 4ffbfa5a0187196addfd724594eae46abfcd8346 Mon Sep 17 00:00:00 2001 From: Tony Air Date: Tue, 30 Mar 2021 12:37:02 +0700 Subject: [PATCH] FIX: Fallback page loading --- src/js/_components/_apollo.js | 142 ++++++------ src/js/_components/_main.links.js | 353 ++++++++++++++++-------------- src/js/_components/_page.jsx | 290 ++++++++++++------------ src/js/_events.js | 75 +++---- 4 files changed, 443 insertions(+), 417 deletions(-) diff --git a/src/js/_components/_apollo.js b/src/js/_components/_apollo.js index 8bc6571..b5839f7 100644 --- a/src/js/_components/_apollo.js +++ b/src/js/_components/_apollo.js @@ -2,101 +2,103 @@ import Events from '../_events'; import { cache } from './_apollo.cache'; import { - from, - ApolloClient, - HttpLink, - ApolloLink, - concat, + from, + ApolloClient, + HttpLink, + ApolloLink, + concat, } from '@apollo/client'; import { onError } from '@apollo/client/link/error'; const NAME = '_appolo'; const API_META = document.querySelector('meta[name="api_url"]'); -const API_URL = API_META - ? API_META.getAttribute('content') - : `${window.location.protocol }//${ window.location.host }/graphql`; +const API_URL = API_META ? + API_META.getAttribute('content') : + `${window.location.protocol }//${ window.location.host }/graphql`; const authMiddleware = new ApolloLink((operation, forward) => { - // add the authorization to the headers - operation.setContext({ - headers: { - apikey: `${GRAPHQL_API_KEY}`, - }, - }); + // add the authorization to the headers + operation.setContext({ + headers: { + apikey: `${GRAPHQL_API_KEY}`, + }, + }); - return forward(operation); + return forward(operation); }); console.info(`%cAPI: ${API_URL}`, 'color:green;font-size:10px'); const link = from([ - authMiddleware, - new ApolloLink((operation, forward) => { - operation.setContext({ start: new Date() }); - return forward(operation); - }), - onError(({ operation, response, graphQLErrors, networkError, forward }) => { - if (operation.operationName === 'IgnoreErrorsQuery') { - response.errors = null; - return; - } + authMiddleware, + new ApolloLink((operation, forward) => { + operation.setContext({ start: new Date() }); + return forward(operation); + }), + onError(({ operation, response, graphQLErrors, networkError, forward }) => { + if (operation.operationName === 'IgnoreErrorsQuery') { + console.error(`${NAME}: IgnoreErrorsQuery`); + response.errors = null; + return; + } - if (graphQLErrors) - graphQLErrors.forEach(({ message, locations, path }) => - console.error( - `${NAME}: [GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, - ), - ); + if (graphQLErrors) { + graphQLErrors.forEach(({ message, locations, path }) => + console.error( + `${NAME}: [GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, + ), + ); + } - if (networkError) { - /*let msg = ''; - switch (networkError.statusCode) { - case 404: - msg = 'Not Found.'; - break; - case 500: - msg = 'Server issue, please try again latter.'; - break; - default: - msg = 'Something went wrong.'; - break; - }*/ + if (networkError) { + /*let msg = ''; + switch (networkError.statusCode) { + case 404: + msg = 'Not Found.'; + break; + case 500: + msg = 'Server issue, please try again latter.'; + break; + default: + msg = 'Something went wrong.'; + break; + }*/ + console.error(`${NAME}: [Network error] ${networkError.statusCode}`); + } - console.error(`${NAME}: [Network error] ${networkError.statusCode}`); + console.error(`${NAME}: [APOLLO_ERROR]`); + window.dispatchEvent(new Event(Events.APOLLO_ERROR)); + }), + new ApolloLink((operation, forward) => { + return forward(operation).map((data) => { + // data from a previous link + const time = new Date() - operation.getContext().start; + console.log( + `${NAME}: operation ${operation.operationName} took ${time} ms to complete`, + ); - window.dispatchEvent(new Event(Events.OFFLINE)); - } - }), - new ApolloLink((operation, forward) => { - return forward(operation).map((data) => { - // data from a previous link - const time = new Date() - operation.getContext().start; - console.log( - `${NAME}: operation ${operation.operationName} took ${time} ms to complete`, - ); + window.dispatchEvent(new Event(Events.ONLINE)); + return data; + }); + }), + new HttpLink({ + uri: API_URL, - window.dispatchEvent(new Event(Events.ONLINE)); - return data; - }); - }), - new HttpLink({ - uri: API_URL, - - // Use explicit `window.fetch` so tha outgoing requests - // are captured and deferred until the Service Worker is ready. - fetch: (...args) => fetch(...args), - credentials: 'same-origin', //'include', - connectToDevTools: process.env.NODE_ENV === 'development' ? true : false, - }), + // Use explicit `window.fetch` so tha outgoing requests + // are captured and deferred until the Service Worker is ready. + fetch: (...args) => fetch(...args), + credentials: 'same-origin', //'include', + connectToDevTools: process.env.NODE_ENV === 'development' ? true : false, + }), ]); // Isolate Apollo client so it could be reused // in both application runtime and tests. const client = new ApolloClient({ - cache, - link, + cache, + link, }); export { client }; diff --git a/src/js/_components/_main.links.js b/src/js/_components/_main.links.js index 61de731..888633f 100644 --- a/src/js/_components/_main.links.js +++ b/src/js/_components/_main.links.js @@ -9,192 +9,213 @@ import { Collapse } from 'bootstrap'; import SpinnerUI from './_main.loading-spinner'; const MainUILinks = ((W) => { - const NAME = '_main.links'; - const D = document; - const BODY = D.body; + const NAME = '_main.links'; + const D = document; + const BODY = D.body; - class MainUILinks { - static init() { - const ui = this; - ui.GraphPage = null; + class MainUILinks { + window + static init() { + const ui = this; + ui.GraphPage = null; - console.log(`${NAME}: init`); + console.log(`${NAME}: init`); - ui.loaded(); + ui.loaded(); - // history state switch - W.addEventListener('popstate', (e) => { - ui.popState(e); - }); - } - - static loaded() { - const ui = this; - - D.querySelectorAll('.graphql-page').forEach((el, i) => { - const el_id = el.getAttribute('href'); - el.setAttribute(`data-${ui.name}-id`, el_id); - - el.addEventListener('click', ui.loadClick); - }); - } - - static setActiveLinks(link) { - const ui = this; - D.querySelectorAll(`[data-${ui.name}-id="${link}"]`).forEach( - (el) => { - el.classList.add('active'); - }, - ); - } - - static reset() { - // reset focus - D.activeElement.blur(); - - // remove active and loading classes - D.querySelectorAll('.graphql-page,.nav-item').forEach((el2) => { - el2.classList.remove('active', 'loading'); - }); - } - - static popState(e) { - const ui = this; - - SpinnerUI.show(); - - if (e.state && e.state.page) { - console.log(`${NAME}: [popstate] load`); - const state = JSON.parse(e.state.page); - - state.current = null; - state.popstate = true; - - ui.reset(); - ui.setActiveLinks(e.state.link); - - if (!ui.GraphPage) { - console.log( - `${NAME}: [popstate] GraphPage is missing. Have to render it first`, - ); - - ui.GraphPage = ReactDOM.render( - , - document.getElementById('MainContent'), - ); + // history state switch + W.addEventListener('popstate', (e) => { + ui.popState(e); + }); } - ui.GraphPage.setState(state); - SpinnerUI.hide(); + static loaded() { + const ui = this; - window.dispatchEvent(new Event(Events.AJAX)); - } else if (e.state && e.state.landing) { - console.log(`${NAME}: [popstate] go to landing`); - W.location.href = e.state.landing; - } else { - console.warn(`${NAME}: [popstate] state is missing`); - console.log(e); - SpinnerUI.hide(); - } - } + D.querySelectorAll('.graphql-page').forEach((el, i) => { + const el_id = el.getAttribute('href'); + el.setAttribute(`data-${ui.name}-id`, el_id); - // link specific event {this} = current event, not MainUILinks - static loadClick(e) { - console.groupCollapsed(`${NAME}: load on click`); - e.preventDefault(); + el.addEventListener('click', ui.loadClick); + }); + } - const ui = MainUILinks; - const el = e.currentTarget; - - SpinnerUI.show(); - - ui.reset(); - el.classList.add('loading'); - el.classList.remove('response-404', 'response-500', 'response-523'); - BODY.classList.add('ajax-loading'); - - // hide parent mobile nav - const navs = getParents(el, '.collapse'); - if (navs.length) { - navs.forEach((nav) => { - const collapseInst = Collapse.getInstance(nav); - if (collapseInst) { - collapseInst.hide(); - } - }); - } - - // hide parent dropdown - /*const dropdowns = getParents(el, '.dropdown-menu'); - if (dropdowns.length) { - const DropdownInst = Dropdown.getInstance(dropdowns[0]); - DropdownInst.hide(); - }*/ - - if (!ui.GraphPage) { - ui.GraphPage = ReactDOM.render( - , - document.getElementById('MainContent'), - ); - } - - const link = el.getAttribute('href') || el.getAttribute('data-href'); - - ui.GraphPage.state.current = el; - - ui.GraphPage.load(link) - .then((response) => { - BODY.classList.remove('ajax-loading'); - el.classList.remove('loading'); - el.classList.add('active'); - - if (ui.GraphPage.state.Link) { - window.history.pushState({ - page: JSON.stringify(ui.GraphPage.state), - link: el.getAttribute(`data-${ui.name}-id`), - }, - ui.GraphPage.state.Title, - ui.GraphPage.state.Link, + static setActiveLinks(link) { + const ui = this; + D.querySelectorAll(`[data-${ui.name}-id="${link}"]`).forEach( + (el) => { + el.classList.add('active'); + }, ); + } - ui.setActiveLinks(ui.GraphPage.state.Link) - } + static reset() { + // reset focus + D.activeElement.blur(); - SpinnerUI.hide(); + // remove active and loading classes + D.querySelectorAll('.graphql-page,.nav-item').forEach((el2) => { + el2.classList.remove('active', 'loading'); + }); + } - window.dispatchEvent(new Event(Events.AJAX)); - console.groupEnd(`${NAME}: load on click`); - }) - .catch((e) => { - console.log(e); + static popState(e) { + const ui = this; - BODY.classList.remove('ajax-loading'); - el.classList.remove('loading'); - el.classList.add('error', `response-${e.status}`); - /*switch (e.status) { - case 404: - el.classList.add('not-found'); - break; - case 523: - el.classList.add('unreachable'); - break; + SpinnerUI.show(); + + if (e.state && e.state.page) { + console.log(`${NAME}: [popstate] load`); + const state = JSON.parse(e.state.page); + + state.current = null; + state.popstate = true; + + ui.reset(); + ui.setActiveLinks(e.state.link); + + if (!ui.GraphPage) { + console.log( + `${NAME}: [popstate] GraphPage is missing. Have to render it first`, + ); + + ui.GraphPage = ReactDOM.render( + , + document.getElementById('MainContent'), + ); + } + + ui.GraphPage.setState(state); + SpinnerUI.hide(); + + window.dispatchEvent(new Event(Events.AJAX)); + } else if (e.state && e.state.landing) { + console.log(`${NAME}: [popstate] go to landing`); + W.location.href = e.state.landing; + } else { + console.warn(`${NAME}: [popstate] state is missing`); + console.log(e); + SpinnerUI.hide(); + } + } + + // link specific event {this} = current event, not MainUILinks + static loadClick(e) { + console.groupCollapsed(`${NAME}: load on click`); + e.preventDefault(); + + const ui = MainUILinks; + const el = e.currentTarget; + + SpinnerUI.show(); + + ui.reset(); + el.classList.add('loading'); + el.classList.remove('response-404', 'response-500', 'response-523'); + BODY.classList.add('ajax-loading'); + + // hide parent mobile nav + const navs = getParents(el, '.collapse'); + if (navs.length) { + navs.forEach((nav) => { + const collapseInst = Collapse.getInstance(nav); + if (collapseInst) { + collapseInst.hide(); + } + }); + } + + // hide parent dropdown + /*const dropdowns = getParents(el, '.dropdown-menu'); + if (dropdowns.length) { + const DropdownInst = Dropdown.getInstance(dropdowns[0]); + DropdownInst.hide(); + }*/ + + if (!ui.GraphPage) { + ui.GraphPage = ReactDOM.render( + , + document.getElementById('MainContent'), + ); + } + + const link = el.getAttribute('href') || el.getAttribute('data-href'); + + + ui.GraphPage.state.current = el; + + ui.GraphPage.load(link) + .then((response) => { + BODY.classList.remove('ajax-loading'); + el.classList.remove('loading'); + el.classList.add('active'); + + D.loading_apollo_link = null; + + if (ui.GraphPage.state.Link) { + window.history.pushState({ + page: JSON.stringify(ui.GraphPage.state), + link: el.getAttribute(`data-${ui.name}-id`), + }, + ui.GraphPage.state.Title, + ui.GraphPage.state.Link, + ); + + ui.setActiveLinks(ui.GraphPage.state.Link) + } + + SpinnerUI.hide(); + + window.dispatchEvent(new Event(Events.AJAX)); + console.groupEnd(`${NAME}: load on click`); + }) + .catch((e) => { + console.error(`${NAME}: loading error`); + console.log(e); + + BODY.classList.remove('ajax-loading'); + el.classList.remove('loading'); + el.classList.add('error', `response-${e.status}`); + /*switch (e.status) { + case 500: + break; + case 404: + el.classList.add('not-found'); + break; + case 523: + el.classList.add('unreachable'); + break; }*/ - SpinnerUI.hide(); + SpinnerUI.hide(); - window.dispatchEvent(new Event(Events.AJAX)); - console.groupEnd(`${NAME}: load on click`); - }); + window.dispatchEvent(new Event(Events.AJAX)); + console.groupEnd(`${NAME}: load on click`); + + console.log(`${NAME}: reloading page ${link}`); + + // fallback loading + W.location.href = link; + }); + } } - } - W.addEventListener(`${Events.LOADED}`, () => { - MainUILinks.init(); - }); + W.addEventListener(`${Events.LOADED}`, () => { + MainUILinks.init(); + }); - W.addEventListener(`${Events.AJAX}`, () => { - MainUILinks.loaded(); - }); + W.addEventListener(`${Events.AJAX}`, () => { + MainUILinks.loaded(); + }); + + // fallback + /*W.addEventListener(`${Events.APOLLO_ERROR}`, (e) => { + console.error(`${NAME}: [APOLLO_ERROR] loading failure, reloading the page`); + //W.dispatchEvent(new Event(Events.OFFLINE)); + if (D.loading_apollo_link) { + W.location.href = D.loading_apollo_link; + } + });*/ })(window); export default MainUILinks; diff --git a/src/js/_components/_page.jsx b/src/js/_components/_page.jsx index c37b36f..2f05cdc 100644 --- a/src/js/_components/_page.jsx +++ b/src/js/_components/_page.jsx @@ -11,53 +11,53 @@ const D = document; const BODY = document.body; class Page extends Component { - state = { - type: [], - shown: false, - Title: 'Loading ...', - loading: true, - error: false, - current: null, - ID: null, - URLSegment: null, - ClassName: 'Page', - CSSClass: null, - Summary: null, - Link: null, - URL: null, - HTML: null, - Elements: [], - page: null, - }; + state = { + type: [], + shown: false, + Title: 'Loading ...', + loading: true, + error: false, + current: null, + ID: null, + URLSegment: null, + ClassName: 'Page', + CSSClass: null, + Summary: null, + Link: null, + URL: null, + HTML: null, + Elements: [], + page: null, + }; - componentDidUpdate() { - const ui = this; + componentDidUpdate() { + const ui = this; - if (ui.state.Title) { - document.title = ui.state.Title; - } - } + if (ui.state.Title) { + document.title = ui.state.Title; + } + } - constructor(props) { - super(props); + constructor(props) { + super(props); - const ui = this; + const ui = this; - ui.name = ui.constructor.name; - ui.empty_state = ui.state; + ui.name = ui.constructor.name; + ui.empty_state = ui.state; - console.log(`${ui.name}: init`); - } + console.log(`${ui.name}: init`); + } - isOnline = () => { - return BODY.classList.contains('is-online'); - }; + isOnline = () => { + return BODY.classList.contains('is-online'); + }; - load = (link) => { - const ui = this; + load = (link) => { + const ui = this; - return new Promise((resolve, reject) => { - const query = gql(`query Pages { + return new Promise((resolve, reject) => { + const query = gql(`query Pages { readPages(Link: "${link}") { edges { node { @@ -76,133 +76,135 @@ class Page extends Component { } }`); - if (!ui.isOnline()) { - const data = client.readQuery({ query }); + if (!ui.isOnline()) { + const data = client.readQuery({ query }); - if (data && ui.processResponse(data)) { - console.log(`${ui.name}: Offline cached response`); - resolve(data); - } else { - console.log(`${ui.name}: No offline response`); + if (data && ui.processResponse(data)) { + console.log(`${ui.name}: Offline cached response`); + resolve(data); + } else { + console.log(`${ui.name}: No offline response`); - ui.setState( - Object.assign(ui.empty_state, { - Title: 'Offline', - CSSClass: 'graphql__status-523', - Summary: - "You're Offline. The page is not available now.", - loading: false, - }), - ); + ui.setState( + Object.assign(ui.empty_state, { + Title: 'Offline', + CSSClass: 'graphql__status-523', + Summary: "You're Offline. The page is not available now.", + loading: false, + }), + ); - reject({ status: 523 }); - } - } else { - if (!ui.state.loading) { - ui.setState(ui.empty_state); - } + reject({ status: 523 }); + } + } else { + if (!ui.state.loading) { + ui.setState(ui.empty_state); + } - client - .query({ - query: query, - fetchPolicy: ui.isOnline() ? 'no-cache' : 'cache-first', - }) - .then((resp) => { - // write to cache - const data = resp.data; - client.writeQuery({ query, data: data }); + client + .query({ + query: query, + fetchPolicy: ui.isOnline() ? 'no-cache' : 'cache-first', + }) + .then((resp) => { + // write to cache + const data = resp.data; + client.writeQuery({ query, data: data }); - if (ui.processResponse(data)) { - console.log(`${ui.name}: got the server response`); - resolve(data); - } else { - console.log(`${ui.name}: not found`); - reject({ status: 404 }); - } - }); - } - }); - }; + if (ui.processResponse(data)) { + console.log(`${ui.name}: got the server response`); + resolve(data); + } else { + console.log(`${ui.name}: not found`); + reject({ status: 404 }); + } + }) + .catch((error) => { + reject({ status: 500, error: error }); + }); + } + }); + }; - processResponse = (data) => { - const ui = this; + processResponse = (data) => { + const ui = this; - if (!data.readPages.edges.length) { - console.log(`${ui.name}: not found`); + if (!data.readPages.edges.length) { + console.log(`${ui.name}: not found`); - ui.setState( - Object.assign(ui.empty_state, { - Title: 'Not Found', - CSSClass: 'graphql__status-404', - Summary: 'The page is not found.', - loading: false, - }), - ); + ui.setState( + Object.assign(ui.empty_state, { + Title: 'Not Found', + CSSClass: 'graphql__status-404', + Summary: 'The page is not found.', + loading: false, + }), + ); - return false; - } + return false; + } - const page = data.readPages.edges[0].node; - ui.setState({ - ID: page.ID, - Title: page.Title, - ClassName: page.ClassName, - URLSegment: page.URLSegment, - CSSClass: page.CSSClass, - Summary: page.Summary, - Link: page.Link, - HTML: page.HTML, - Elements: [],//page.Elements.edges, - loading: false, - }); + const page = data.readPages.edges[0].node; + ui.setState({ + ID: page.ID, + Title: page.Title, + ClassName: page.ClassName, + URLSegment: page.URLSegment, + CSSClass: page.CSSClass, + Summary: page.Summary, + Link: page.Link, + HTML: page.HTML, + Elements: [], //page.Elements.edges, + loading: false, + }); - return true; - }; + return true; + }; - getHtml = (html) => { - const decodeHtmlEntity = (input) => { - var doc = new DOMParser().parseFromString(input, 'text/html'); - return doc.documentElement.textContent; - }; + getHtml = (html) => { + const decodeHtmlEntity = (input) => { + var doc = new DOMParser().parseFromString(input, 'text/html'); + return doc.documentElement.textContent; + }; - return { __html: decodeHtmlEntity(html) }; - }; + return { __html: decodeHtmlEntity(html) }; + }; - render() { - const ui = this; - const name = ui.name; - const className = `elemental-area graphql__page page-${ui.state.CSSClass} url-${ui.state.URLSegment}`; + render() { + const ui = this; + const name = ui.name; + const className = `elemental-area graphql__page page-${ui.state.CSSClass} url-${ui.state.URLSegment}`; - const ElementItem = (props) => ( -
- ); + const ElementItem = (props) => ( +
+ ); - let html = ''; - if(ui.state.HTML) { - console.log(`${ui.name}: HTML only`); - html = ui.state.HTML; - }else if (ui.state.Elements.length) { - console.log(`${ui.name}: render`); - ui.state.Elements.map((el) => { - html += el.node.Render; - }); - } else if (ui.state.Summary && ui.state.Summary.length) { - console.log(`${ui.name}: summary only`); - html = `
${ui.state.Summary}
`; - } + let html = ''; + if (ui.state.HTML) { + console.log(`${ui.name}: HTML only`); + html = ui.state.HTML; + } else if (ui.state.Elements.length) { + console.log(`${ui.name}: render`); + ui.state.Elements.map((el) => { + html += el.node.Render; + }); + } else if (ui.state.Summary && ui.state.Summary.length) { + console.log(`${ui.name}: summary only`); + html = `
${ui.state.Summary}
`; + } - if(ui.state.loading){ - const spinner = D.getElementById('PageLoading'); - html = `
Loading ...
`; - } + if (ui.state.loading) { + const spinner = D.getElementById('PageLoading'); + html = `
Loading ...
`; + } - return ( -
- ); - } + ); + } } export default Page; diff --git a/src/js/_events.js b/src/js/_events.js index 592228c..84f3c72 100755 --- a/src/js/_events.js +++ b/src/js/_events.js @@ -3,41 +3,42 @@ */ export default { - AJAX: 'ajax-load', - AJAXMAIN: 'ajax-main-load', - MAININIT: 'main-init', - TABHIDDEN: 'tab-hidden', - TABFOCUSED: 'tab-focused', - OFFLINE: 'offline', - ONLINE: 'online', - BACKONLINE: 'back-online', - TOUCHENABLE: 'touch-enabled', - TOUCHDISABLED: 'touch-disabled', - LOADED: 'load', - SWIPELEFT: 'swipeleft panleft', - SWIPERIGHT: 'swiperight panright', - ALLERTAPPEARED: 'alert-appeared', - ALERTREMOVED: 'alert-removed', - LODEDANDREADY: 'load-ready', - LAZYIMAGEREADY: 'image-lazy-bg-loaded', - LAZYIMAGESREADY: 'images-lazy-loaded', - MAPLOADED: 'map-loaded', - MAPAPILOADED: 'map-api-loaded', - MAPMARKERCLICK: 'map-marker-click', - MAPPOPUPCLOSE: 'map-popup-close', - SCROLL: 'scroll', - RESIZE: 'resize', - CAROUSEL_READY: 'bs.carousel.ready', - SET_TARGET_UPDATE: 'set-target-update', - RESTORE_FIELD: 'restore-field', - FORM_INIT_BASICS: 'form-basics', - FORM_INIT_STEPPED: 'form-init-stepped', - FORM_INIT_VALIDATE: 'form-init-validate', - FORM_INIT_VALIDATE_FIELD: 'form-init-validate-field', - FORM_INIT_STORAGE: 'form-init-storage', - FORM_VALIDATION_FAILED: 'form-validation-failed', - FORM_STEPPED_NEW_STEP: 'form-new-step', - FORM_STEPPED_FIRST_STEP: 'form-first-step', - FORM_STEPPED_LAST_STEP: 'form-last-step', - FORM_FIELDS: 'input,textarea,select', + APOLLO_ERROR: 'apollo-error', + AJAX: 'ajax-load', + AJAXMAIN: 'ajax-main-load', + MAININIT: 'main-init', + TABHIDDEN: 'tab-hidden', + TABFOCUSED: 'tab-focused', + OFFLINE: 'offline', + ONLINE: 'online', + BACKONLINE: 'back-online', + TOUCHENABLE: 'touch-enabled', + TOUCHDISABLED: 'touch-disabled', + LOADED: 'load', + SWIPELEFT: 'swipeleft panleft', + SWIPERIGHT: 'swiperight panright', + ALLERTAPPEARED: 'alert-appeared', + ALERTREMOVED: 'alert-removed', + LODEDANDREADY: 'load-ready', + LAZYIMAGEREADY: 'image-lazy-bg-loaded', + LAZYIMAGESREADY: 'images-lazy-loaded', + MAPLOADED: 'map-loaded', + MAPAPILOADED: 'map-api-loaded', + MAPMARKERCLICK: 'map-marker-click', + MAPPOPUPCLOSE: 'map-popup-close', + SCROLL: 'scroll', + RESIZE: 'resize', + CAROUSEL_READY: 'bs.carousel.ready', + SET_TARGET_UPDATE: 'set-target-update', + RESTORE_FIELD: 'restore-field', + FORM_INIT_BASICS: 'form-basics', + FORM_INIT_STEPPED: 'form-init-stepped', + FORM_INIT_VALIDATE: 'form-init-validate', + FORM_INIT_VALIDATE_FIELD: 'form-init-validate-field', + FORM_INIT_STORAGE: 'form-init-storage', + FORM_VALIDATION_FAILED: 'form-validation-failed', + FORM_STEPPED_NEW_STEP: 'form-new-step', + FORM_STEPPED_FIRST_STEP: 'form-first-step', + FORM_STEPPED_LAST_STEP: 'form-last-step', + FORM_FIELDS: 'input,textarea,select', };