feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
This commit is contained in:
130
modules/backend/assets/ui/js/ajax/Handler.js
Normal file
130
modules/backend/assets/ui/js/ajax/Handler.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import { delegate } from 'jquery-events-to-dom-events';
|
||||
|
||||
/**
|
||||
* Backend AJAX handler.
|
||||
*
|
||||
* This is a utility script that resolves some backwards-compatibility issues with the functionality
|
||||
* that relies on the old framework, and ensures that Snowboard works well within the Backend
|
||||
* environment.
|
||||
*
|
||||
* Functions:
|
||||
* - Adds the "render" jQuery event to Snowboard requests that widgets use to initialise.
|
||||
* - Ensures the CSRF token is included in requests.
|
||||
*
|
||||
* @copyright 2021 Winter.
|
||||
* @author Ben Thomson <git@alfreido.com>
|
||||
*/
|
||||
export default class Handler extends Snowboard.Singleton {
|
||||
/**
|
||||
* Event listeners.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
listens() {
|
||||
return {
|
||||
ready: 'ready',
|
||||
ajaxFetchOptions: 'ajaxFetchOptions',
|
||||
ajaxUpdateComplete: 'ajaxUpdateComplete',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ready handler.
|
||||
*
|
||||
* Fires off a "render" event.
|
||||
*/
|
||||
ready() {
|
||||
if (!window.jQuery) {
|
||||
return;
|
||||
}
|
||||
delegate('render');
|
||||
|
||||
// Add global event for rendering in Snowboard
|
||||
delegate('render');
|
||||
document.addEventListener('$render', () => {
|
||||
this.snowboard.globalEvent('render');
|
||||
});
|
||||
|
||||
// Add "render" event for backwards compatibility
|
||||
window.jQuery(document).trigger('render');
|
||||
|
||||
// Add global event for rendering in Snowboard
|
||||
document.addEventListener('$render', () => {
|
||||
this.snowboard.globalEvent('render');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the jQuery AJAX prefilter that the old framework uses to inject the CSRF token in AJAX
|
||||
* calls.
|
||||
*/
|
||||
addPrefilter() {
|
||||
if (!window.jQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.jQuery.ajaxPrefilter((options) => {
|
||||
if (this.hasToken()) {
|
||||
if (!options.headers) {
|
||||
options.headers = {};
|
||||
}
|
||||
options.headers['X-CSRF-TOKEN'] = this.getToken();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch options handler.
|
||||
*
|
||||
* Ensures that the CSRF token is included in Snowboard requests.
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
ajaxFetchOptions(options) {
|
||||
if (this.hasToken()) {
|
||||
options.headers['X-CSRF-TOKEN'] = this.getToken();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update complete handler.
|
||||
*
|
||||
* Fires off a "render" event when partials are updated so that any widgets included in
|
||||
* responses are correctly initialised.
|
||||
*/
|
||||
ajaxUpdateComplete() {
|
||||
if (!window.jQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add "render" event for backwards compatibility
|
||||
window.jQuery(document).trigger('render');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a CSRF token is available.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasToken() {
|
||||
const tokenElement = document.querySelector('meta[name="csrf-token"]');
|
||||
|
||||
if (!tokenElement) {
|
||||
return false;
|
||||
}
|
||||
if (!tokenElement.hasAttribute('content')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CSRF token.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
getToken() {
|
||||
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
}
|
||||
}
|
||||
1
modules/backend/assets/ui/js/build/backend.js
Normal file
1
modules/backend/assets/ui/js/build/backend.js
Normal file
@@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunk_wintercms_wn_backend_module=self.webpackChunk_wintercms_wn_backend_module||[]).push([[476],{286:function(e,t,n){var i=n(35),r=n(171);class s extends Snowboard.Singleton{listens(){return{ready:"ready",ajaxFetchOptions:"ajaxFetchOptions",ajaxUpdateComplete:"ajaxUpdateComplete"}}ready(){window.jQuery&&((0,r.M)("render"),(0,r.M)("render"),document.addEventListener("$render",()=>{this.snowboard.globalEvent("render")}),window.jQuery(document).trigger("render"),document.addEventListener("$render",()=>{this.snowboard.globalEvent("render")}))}addPrefilter(){window.jQuery&&window.jQuery.ajaxPrefilter(e=>{this.hasToken()&&(e.headers||(e.headers={}),e.headers["X-CSRF-TOKEN"]=this.getToken())})}ajaxFetchOptions(e){this.hasToken()&&(e.headers["X-CSRF-TOKEN"]=this.getToken())}ajaxUpdateComplete(){window.jQuery&&window.jQuery(document).trigger("render")}hasToken(){const e=document.querySelector('meta[name="csrf-token"]');return!!e&&!!e.hasAttribute("content")}getToken(){return document.querySelector('meta[name="csrf-token"]').getAttribute("content")}}class a extends Snowboard.PluginBase{construct(e,t){if(e instanceof Snowboard.PluginBase==!1)throw new Error("Event handling can only be applied to Snowboard classes.");if(!t)throw new Error("Event prefix is required.");this.instance=e,this.eventPrefix=t,this.events=[]}on(e,t){this.events.push({event:e,callback:t})}off(e,t){this.events=this.events.filter(n=>n.event!==e||n.callback!==t)}once(e,t){const n=this.events.push({event:e,callback:(...e)=>{t(...e),this.events.splice(n-1,1)}})}fire(e,...t){const n=this.events.filter(t=>t.event===e);let i=!1;n.forEach(e=>{i||!1===e.callback(...t)&&(i=!0)}),i||this.snowboard.globalEvent(`${this.eventPrefix}.${e}`,...t)}firePromise(e,...t){const n=this.events.filter(t=>t.event===e),i=n.filter(e=>null!==e,n.map(e=>e.callback(...t)));Promise.all(i).then(()=>{this.snowboard.globalPromiseEvent(`${this.eventPrefix}.${e}`,...t)})}}class o extends Snowboard.Singleton{construct(){this.registeredWidgets=[],this.elements=[],this.events={mutate:e=>this.onMutation(e)},this.observer=null}listens(){return{ready:"onReady",render:"onRender",ajaxUpdate:"onAjaxUpdate"}}register(e,t,n){this.registeredWidgets.push({control:e,widget:t,callback:n})}unregister(e){this.registeredWidgets=this.registeredWidgets.filter(t=>t.control!==e)}onReady(){this.initializeWidgets(document.body),this.observer||(this.observer=new MutationObserver(this.events.mutate),this.observer.observe(document.body,{childList:!0,subtree:!0}))}onRender(){this.initializeWidgets(document.body)}onAjaxUpdate(e){this.initializeWidgets(e)}initializeWidgets(e){this.registeredWidgets.forEach(t=>{const n=e.querySelectorAll(`[data-control="${t.control}"]:not([data-widget-initialized])`);n.length&&n.forEach(e=>{if(e.dataset.widgetInitialized)return;const n=this.snowboard[t.widget](e);this.elements.push({element:e,instance:n}),e.dataset.widgetInitialized=!0,this.snowboard.globalEvent("backend.widget.initialized",e,n),"function"==typeof t.callback&&t.callback(n,e)})})}getWidget(e){const t=this.elements.find(t=>t.element===e);return t?t.instance:null}onMutation(e){const t=e.filter(e=>e.removedNodes.length).map(e=>Array.from(e.removedNodes)).flat();t.length&&t.forEach(e=>{const t=this.elements.filter(t=>e.contains(t.element));t.length&&t.forEach(e=>{e.instance.destruct(),this.elements=this.elements.filter(t=>t!==e)})})}}if(void 0===window.Snowboard)throw new Error("Snowboard must be loaded in order to use the Backend UI.");(e=>{e.addPlugin("backend.ajax.handler",s),e.addPlugin("backend.ui.eventHandler",a),e.addPlugin("backend.ui.widgetHandler",o),e["backend.ajax.handler"]().addPrefilter(),window.AssetManager={load:(t,n)=>{e.assetLoader().load(t).then(()=>{n&&"function"==typeof n&&n()})}},window.assetManager=window.AssetManager})(window.Snowboard),window.Vue=i}},function(e){e.O(0,[810],function(){return t=286,e(e.s=t);var t});e.O()}]);
|
||||
1
modules/backend/assets/ui/js/build/manifest.js
Normal file
1
modules/backend/assets/ui/js/build/manifest.js
Normal file
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var n,e={},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={id:n,exports:{}};return e[n](i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every(function(n){return t.O[n](r[c])})?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},function(){var n={624:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some(function(e){return 0!==n[e]})){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunk_wintercms_wn_backend_module=self.webpackChunk_wintercms_wn_backend_module||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}(),t.nc=void 0}();
|
||||
24
modules/backend/assets/ui/js/build/vendor.js
Normal file
24
modules/backend/assets/ui/js/build/vendor.js
Normal file
File diff suppressed because one or more lines are too long
34
modules/backend/assets/ui/js/index.js
Normal file
34
modules/backend/assets/ui/js/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as Vue from 'vue';
|
||||
import BackendAjaxHandler from './ajax/Handler';
|
||||
import BackendUiEventHandler from './ui/EventHandler';
|
||||
import BackendUiWidgetHandler from './ui/WidgetHandler';
|
||||
|
||||
if (window.Snowboard === undefined) {
|
||||
throw new Error('Snowboard must be loaded in order to use the Backend UI.');
|
||||
}
|
||||
|
||||
((Snowboard) => {
|
||||
Snowboard.addPlugin('backend.ajax.handler', BackendAjaxHandler);
|
||||
Snowboard.addPlugin('backend.ui.eventHandler', BackendUiEventHandler);
|
||||
Snowboard.addPlugin('backend.ui.widgetHandler', BackendUiWidgetHandler);
|
||||
|
||||
// Add the pre-filter immediately
|
||||
Snowboard['backend.ajax.handler']().addPrefilter();
|
||||
|
||||
// Add polyfill for AssetManager
|
||||
window.AssetManager = {
|
||||
load: (assets, callback) => {
|
||||
Snowboard.assetLoader().load(assets).then(
|
||||
() => {
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
window.assetManager = window.AssetManager;
|
||||
})(window.Snowboard);
|
||||
|
||||
// Add Vue to global scope
|
||||
window.Vue = Vue;
|
||||
100
modules/backend/assets/ui/js/pages/Preferences.js
Normal file
100
modules/backend/assets/ui/js/pages/Preferences.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { delegate } from 'jquery-events-to-dom-events';
|
||||
|
||||
((Snowboard) => {
|
||||
class Preferences extends Snowboard.Singleton {
|
||||
construct() {
|
||||
this.widget = null;
|
||||
}
|
||||
|
||||
listens() {
|
||||
return {
|
||||
'backend.widget.initialized': 'onWidgetInitialized',
|
||||
};
|
||||
}
|
||||
|
||||
onWidgetInitialized(element, widget) {
|
||||
if (element === document.getElementById('CodeEditor-formEditorPreview-_editor_preview')) {
|
||||
this.widget = widget;
|
||||
this.enablePreferences();
|
||||
}
|
||||
}
|
||||
|
||||
enablePreferences() {
|
||||
delegate('change');
|
||||
|
||||
const checkboxes = {
|
||||
show_gutter: 'showGutter',
|
||||
highlight_active_line: 'highlightActiveLine',
|
||||
use_hard_tabs: '!useSoftTabs',
|
||||
display_indent_guides: 'displayIndentGuides',
|
||||
show_invisibles: 'showInvisibles',
|
||||
show_print_margin: 'showPrintMargin',
|
||||
show_minimap: 'showMinimap',
|
||||
enable_folding: 'codeFolding',
|
||||
bracket_colors: 'bracketColors',
|
||||
show_colors: 'showColors',
|
||||
};
|
||||
|
||||
Object.entries(checkboxes).forEach(([key, value]) => {
|
||||
this.element(key).addEventListener('change', (event) => {
|
||||
this.widget.setConfig(
|
||||
value.replace(/^!/, ''),
|
||||
/^!/.test(value) ? !event.target.checked : event.target.checked,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
this.element('theme').addEventListener('$change', (event) => {
|
||||
this.widget.loadTheme(event.target.value);
|
||||
});
|
||||
|
||||
this.element('font_size').addEventListener('$change', (event) => {
|
||||
this.widget.setConfig('fontSize', event.target.value);
|
||||
});
|
||||
|
||||
this.element('tab_size').addEventListener('$change', (event) => {
|
||||
this.widget.setConfig('tabSize', event.target.value);
|
||||
});
|
||||
|
||||
this.element('word_wrap').addEventListener('$change', (event) => {
|
||||
const { value } = event.target;
|
||||
switch (value) {
|
||||
case 'off':
|
||||
this.widget.setConfig('wordWrap', false);
|
||||
break;
|
||||
case 'fluid':
|
||||
this.widget.setConfig('wordWrap', 'fluid');
|
||||
break;
|
||||
default:
|
||||
this.widget.setConfig('wordWrap', parseInt(value, 10));
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-switch-lang]').forEach((element) => {
|
||||
element.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
const language = element.dataset.switchLang;
|
||||
const template = document.querySelector(`[data-lang-snippet="${language}"]`);
|
||||
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.widget.setValue(template.textContent.trim());
|
||||
this.widget.setLanguage(language);
|
||||
});
|
||||
});
|
||||
|
||||
this.widget.events.once('create', () => {
|
||||
const event = new MouseEvent('click');
|
||||
document.querySelector('[data-switch-lang="css"]').dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
element(key) {
|
||||
return document.getElementById(`Form-field-Preference-editor_${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
Snowboard.addPlugin('backend.preferences', Preferences);
|
||||
})(window.Snowboard);
|
||||
116
modules/backend/assets/ui/js/ui/EventHandler.js
Normal file
116
modules/backend/assets/ui/js/ui/EventHandler.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Widget event handler.
|
||||
*
|
||||
* Extends a widget with event handling functionality, allowing for the quick definition of events
|
||||
* and listening for events on a specific instance of a widget.
|
||||
*
|
||||
* This is a complement to Snowboard's global events - these events will still fire in order to
|
||||
* allow external code to listen and handle events. Local events can cancel the global event (and
|
||||
* further local events) by returning `false` from the callback.
|
||||
*
|
||||
* @copyright 2022 Winter.
|
||||
* @author Ben Thomson <git@alfreido.com>
|
||||
*/
|
||||
export default class EventHandler extends Snowboard.PluginBase {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param {PluginBase} instance
|
||||
* @param {String} eventPrefix
|
||||
*/
|
||||
construct(instance, eventPrefix) {
|
||||
if (instance instanceof Snowboard.PluginBase === false) {
|
||||
throw new Error('Event handling can only be applied to Snowboard classes.');
|
||||
}
|
||||
if (!eventPrefix) {
|
||||
throw new Error('Event prefix is required.');
|
||||
}
|
||||
this.instance = instance;
|
||||
this.eventPrefix = eventPrefix;
|
||||
this.events = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener for a widget's event.
|
||||
*
|
||||
* @param {String} event
|
||||
* @param {Function} callback
|
||||
*/
|
||||
on(event, callback) {
|
||||
this.events.push({
|
||||
event,
|
||||
callback,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deregisters a listener for a widget's event.
|
||||
*
|
||||
* @param {String} event
|
||||
* @param {Function} callback
|
||||
*/
|
||||
off(event, callback) {
|
||||
this.events = this.events.filter((registeredEvent) => registeredEvent.event !== event || registeredEvent.callback !== callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener for a widget's event that will only fire once.
|
||||
*
|
||||
* @param {String} event
|
||||
* @param {Function} callback
|
||||
*/
|
||||
once(event, callback) {
|
||||
const length = this.events.push({
|
||||
event,
|
||||
callback: (...parameters) => {
|
||||
callback(...parameters);
|
||||
this.events.splice(length - 1, 1);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires an event on the widget.
|
||||
*
|
||||
* Local events are fired first, then a global event is fired afterwards.
|
||||
*
|
||||
* @param {String} eventName
|
||||
* @param {...any} parameters
|
||||
*/
|
||||
fire(eventName, ...parameters) {
|
||||
// Fire local events first
|
||||
const events = this.events.filter((registeredEvent) => registeredEvent.event === eventName);
|
||||
let cancelled = false;
|
||||
events.forEach((event) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
if (event.callback(...parameters) === false) {
|
||||
cancelled = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!cancelled) {
|
||||
this.snowboard.globalEvent(`${this.eventPrefix}.${eventName}`, ...parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a promise event on the widget.
|
||||
*
|
||||
* Local events are fired first, then a global event is fired afterwards.
|
||||
*
|
||||
* @param {String} eventName
|
||||
* @param {...any} parameters
|
||||
*/
|
||||
firePromise(eventName, ...parameters) {
|
||||
const events = this.events.filter((registeredEvent) => registeredEvent.event === eventName);
|
||||
const promises = events.filter((event) => event !== null, events.map((event) => event.callback(...parameters)));
|
||||
|
||||
Promise.all(promises).then(
|
||||
() => {
|
||||
this.snowboard.globalPromiseEvent(`${this.eventPrefix}.${eventName}`, ...parameters);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
181
modules/backend/assets/ui/js/ui/WidgetHandler.js
Normal file
181
modules/backend/assets/ui/js/ui/WidgetHandler.js
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Backend widget handler.
|
||||
*
|
||||
* Handles the creation and disposal of widgets in the Backend. Widgets should include this as
|
||||
* a dependency in order to be loaded and initialised after the handler, in order to correctly
|
||||
* register.
|
||||
*
|
||||
* @copyright 2022 Winter.
|
||||
* @author Ben Thomson <git@alfreido.com>
|
||||
*/
|
||||
export default class WidgetHandler extends Snowboard.Singleton {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
construct() {
|
||||
this.registeredWidgets = [];
|
||||
this.elements = [];
|
||||
this.events = {
|
||||
mutate: (mutations) => this.onMutation(mutations),
|
||||
};
|
||||
this.observer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listeners.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
listens() {
|
||||
return {
|
||||
ready: 'onReady',
|
||||
render: 'onRender',
|
||||
ajaxUpdate: 'onAjaxUpdate',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a widget as a given data control.
|
||||
*
|
||||
* Registering a widget will allow any element that contains a "data-control" attribute matching
|
||||
* the control name to be initialized with the given widget.
|
||||
*
|
||||
* You may optionally provide a callback that will be fired when an instance of the widget is
|
||||
* initialized - the callback will be provided the element and the widget instance as parameters.
|
||||
*
|
||||
* @param {String} control
|
||||
* @param {Snowboard.PluginBase} widget
|
||||
* @param {Function} callback
|
||||
*/
|
||||
register(control, widget, callback) {
|
||||
this.registeredWidgets.push({
|
||||
control,
|
||||
widget,
|
||||
callback,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a data control.
|
||||
*
|
||||
* @param {String} control
|
||||
*/
|
||||
unregister(control) {
|
||||
this.registeredWidgets = this.registeredWidgets.filter((widget) => widget.control !== control);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ready handler.
|
||||
*
|
||||
* Initializes widgets within the entire document.
|
||||
*/
|
||||
onReady() {
|
||||
this.initializeWidgets(document.body);
|
||||
|
||||
// Register a DOM observer and watch for any removed nodes
|
||||
if (!this.observer) {
|
||||
this.observer = new MutationObserver(this.events.mutate);
|
||||
this.observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render handler.
|
||||
*
|
||||
* Initializes widgets within the entire document.
|
||||
*/
|
||||
onRender() {
|
||||
this.initializeWidgets(document.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX update handler.
|
||||
*
|
||||
* Initializes widgets inside an update element from an AJAX response.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
onAjaxUpdate(element) {
|
||||
this.initializeWidgets(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all widgets within an element.
|
||||
*
|
||||
* If an element contains a "data-control" attribute matching a registered widget, the widget
|
||||
* is initialized and attached to the element as a "widget" property.
|
||||
*
|
||||
* Only one widget may be initialized to a particular element.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
initializeWidgets(element) {
|
||||
this.registeredWidgets.forEach((widget) => {
|
||||
const instances = element.querySelectorAll(`[data-control="${widget.control}"]:not([data-widget-initialized])`);
|
||||
|
||||
if (instances.length) {
|
||||
instances.forEach((instance) => {
|
||||
// Prevent double-widget initialization
|
||||
if (instance.dataset.widgetInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const widgetInstance = this.snowboard[widget.widget](instance);
|
||||
this.elements.push({
|
||||
element: instance,
|
||||
instance: widgetInstance,
|
||||
});
|
||||
instance.dataset.widgetInitialized = true;
|
||||
this.snowboard.globalEvent('backend.widget.initialized', instance, widgetInstance);
|
||||
|
||||
if (typeof widget.callback === 'function') {
|
||||
widget.callback(widgetInstance, instance);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a widget that is attached to the given element, if any.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @returns {Snowboard.PluginBase|null}
|
||||
*/
|
||||
getWidget(element) {
|
||||
const found = this.elements.find((widget) => widget.element === element);
|
||||
|
||||
if (found) {
|
||||
return found.instance;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for mutation events.
|
||||
*
|
||||
* We're only tracking removed nodes, to ensure that those widgets are disposed of.
|
||||
*
|
||||
* @param {MutationRecord[]} mutations
|
||||
*/
|
||||
onMutation(mutations) {
|
||||
const removedNodes = mutations.filter((mutation) => mutation.removedNodes.length).map((mutation) => Array.from(mutation.removedNodes)).flat();
|
||||
if (!removedNodes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
removedNodes.forEach((node) => {
|
||||
const widgets = this.elements.filter((widget) => node.contains(widget.element));
|
||||
if (widgets.length) {
|
||||
widgets.forEach((widget) => {
|
||||
widget.instance.destruct();
|
||||
this.elements = this.elements.filter((element) => element !== widget);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user