# sequra/3.2.0/assets/js/src/page/widget-facade.js

seQura, version 3.2.0. 353 lines.

- Page: https://pluginprobe.com/plugins/sequra/3.2.0/code/assets/js/src/page/widget-facade.js
- Raw: https://pluginprobe.com/plugins/sequra/3.2.0/raw/assets/js/src/page/widget-facade.js
- Modified: 2025-01-29T10:47:34+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/sequra/3.2.0/code/assets/js/src/page/widget-facade.js#L10-L20`.

```javascript
(function () {
    document.addEventListener('DOMContentLoaded', () => {
        if (!SequraConfigParams || !SequraWidgetFacade) {
            return;
        }

        SequraWidgetFacade = {
            ...{
                widgets: [],
                miniWidgets: [],
            },
            ...SequraConfigParams,
            ...SequraWidgetFacade,
            ...{
                mutationObserver: null,
                forcePriceSelector: true,
                presets: {
                    L: '{"alignment":"left"}',
                    R: '{"alignment":"right"}',
                    legacy: '{"type":"legacy"}',
                    legacyL: '{"type":"legacy","alignment":"left"}',
                    legacyR: '{"type":"legacy","alignment":"right"}',
                    minimal: '{"type":"text","branding":"none","size":"S","starting-text":"as-low-as"}',
                    minimalL: '{"type":"text","branding":"none","size":"S","starting-text":"as-low-as","alignment":"left"}',
                    minimalR: '{"type":"text","branding":"none","size":"S","starting-text":"as-low-as","alignment":"right"}'
                },

                init: function () {
                    // Remove duplicated objects from this.widgets.
                    const uniqueWidgets = [];
                    this.widgets.forEach(widget => {
                        Object.keys(widget).forEach(key => {
                            if (typeof widget[key] === 'string') {
                                widget[key] = this.decodeEntities(widget[key]);
                            }
                        });

                        if (!uniqueWidgets.some(w => w.price_src === widget.price_src && w.dest === widget.dest && w.product === widget.product && w.theme === widget.theme && w.reverse === widget.reverse && w.campaign === widget.campaign)) {
                            uniqueWidgets.push(widget);
                        }
                    });
                    this.widgets = uniqueWidgets;
                },

                getText: function (selector) {
                    return selector && document.querySelector(selector) ? document.querySelector(selector).textContent : "0";
                },

                nodeToCents: function (node) {
                    return this.textToCents(node ? node.textContent : "0");
                },

                selectorToCents: function (selector) {
                    return this.textToCents(this.getText(selector));
                },

                decodeEntities: function (encodedString) {
                    if (!encodedString.match(/&(nbsp|amp|quot|lt|gt|#\d+|#x[0-9A-Fa-f]+);/g)) {
                        return encodedString;
                    }
                    const elem = document.createElement('div');
                    elem.innerHTML = encodedString;
                    return elem.textContent;
                },

                textToCents: function (text) {
                    const thousandSeparator = this.decodeEntities(this.thousandSeparator);
                    const decimalSeparator = this.decodeEntities(this.decimalSeparator);

                    text = text.replace(/^\D*/, '').replace(/\D*$/, '');
                    if (text.indexOf(decimalSeparator) < 0) {
                        text += decimalSeparator + '00';
                    }
                    return this.floatToCents(
                        parseFloat(
                            text
                                .replace(thousandSeparator, '')
                                .replace(decimalSeparator, '.')
                        )
                    );
                },

                floatToCents: function (value) {
                    return parseInt(value.toFixed(2).replace('.', ''), 10);
                },

                refreshComponents: function () {
                    Sequra.onLoad(
                        function () {
                            Sequra.refreshComponents();
                        }
                    );
                },

                isVariableProduct: function (selector) {
                    return document.querySelector(selector) ? true : false;
                },

                getPriceSelector: function (widget) {
                    return !this.forcePriceSelector && this.isVariableProduct(widget.isVariableSel) ? widget.variationPriceSel : widget.priceSel;
                },

                /**
                 * Search for child elements in the parentElem that are targets of the widget
                 * @param {object} parentElem DOM element that may contains the widget's targets
                 * @param {object} widget  Widget object
                 * @param {string} observedAt Unique identifier to avoid fetch the same element multiple times
                 * @returns {array} Array of objects containing the target elements and a reference to the widget
                 */
                getWidgetTargets: function (parentElem, widget, observedAt) {
                    const targets = [];
                    if (widget.dest) {
                        const children = parentElem.querySelectorAll(widget.dest);
                        const productObservedAttr = 'data-sequra-observed-' + widget.product;
                        for (const child of children) {
                            if (child.getAttribute(productObservedAttr) == observedAt) {
                                continue;// skip elements that are already observed in this mutation.
                            }
                            child.setAttribute(productObservedAttr, observedAt);
                            targets.push({ elem: child, widget });
                        }
                    }
                    return targets;
                },

                /**
                 * Search for child elements in the parentElem that are targets of the widget
                 * @param {object} widget  Widget object
                 * @returns {array} Array of objects containing the target elements and a reference to the widget
                 */
                getMiniWidgetTargets: function (widget) {
                    const targets = [];
                    if (widget.dest) {
                        const children = document.querySelectorAll(widget.dest);
                        const prices = document.querySelectorAll(widget.priceSel);
                        const priceObservedAttr = 'data-sequra-observed-price-' + widget.product;

                        for (let i = 0; i < children.length; i++) {
                            const child = children[i];

                            const priceElem = 'undefined' !== typeof prices[i] ? prices[i] : null;
                            const priceValue = priceElem ? this.nodeToCents(priceElem) : null;

                            if (null === priceValue || child.getAttribute(priceObservedAttr) == priceValue) {
                                continue;
                            }
                            child.setAttribute(priceObservedAttr, priceValue);
                            targets.push({ elem: child, priceElem, widget });
                        }
                    }
                    return targets;
                },

                /**
                 * Get an unique identifier to avoid fetch the same element multiple times
                 * @returns {number} The current timestamp
                 */
                getObservedAt: () => Date.now(),

                removeWidgetsOnPage: function () {
                    if (this.mutationObserver) {
                        this.mutationObserver.disconnect();
                    }
                    document.querySelectorAll('.sequra-promotion-widget').forEach(widget => widget.remove());
                    if (this.mutationObserver) {
                        this.mutationObserver.observe(document, { childList: true, subtree: true });
                    }
                },

                /**
                 * Draw the missing or outdated widgets in the page.
                 */
                refreshWidgets: function () {

                    const targets = [];
                    for (const widget of this.widgets) {
                        const widgetTargets = this.getWidgetTargets(document, widget, this.getObservedAt());
                        targets.push(...widgetTargets);
                    }
                    for (const miniWidget of this.miniWidgets) {
                        const widgetTargets = this.getMiniWidgetTargets(miniWidget);
                        targets.push(...widgetTargets);
                    }

                    targets.forEach(target => {
                        const { elem, widget } = target;
                        this.isMiniWidget(widget) ? this.drawMiniWidgetOnElement(widget, elem, target.priceElem) : this.drawWidgetOnElement(widget, elem);
                    });
                },

                /**
                 * Paint the widgets in the page and observe the DOM to refresh the widgets when the page changes.
                 * @param {boolean} forcePriceSelector If true, the price selector will be forced to the simple product price selector.
                 */
                drawWidgetsOnPage: function (forcePriceSelector = true) {
                    if (!this.widgets.length && !this.miniWidgets.length) {
                        return;
                    }

                    if (this.mutationObserver) {
                        this.mutationObserver.disconnect();
                    }

                    this.forcePriceSelector = forcePriceSelector;

                    this.refreshWidgets();

                    // Then, observe the DOM to refresh the widgets when the page changes.
                    this.mutationObserver = new MutationObserver((mutations) => {
                        this.mutationObserver.disconnect();// disable the observer to avoid multiple calls to the same function.
                        for (const mutation of mutations) {
                            if (['childList', 'subtree', 'characterData'].includes(mutation.type)) {
                                this.refreshWidgets();
                                break;
                            }
                        }
                        this.mutationObserver.observe(document, { childList: true, subtree: true, characterData: true }); // enable the observer again.
                    });

                    this.mutationObserver.observe(document, { childList: true, subtree: true, characterData: true });
                },

                isMiniWidget: function (widget) {
                    return this.miniWidgets.indexOf(widget) !== -1;
                },

                drawMiniWidgetOnElement: function (widget, element, priceElem) {
                    if (!priceElem) {
                        const priceSrc = this.getPriceSelector(widget);
                        priceElem = document.querySelector(priceSrc);
                        if (!priceElem) {
                            console.error(priceSrc + ' is not a valid css selector to read the price from, for seQura mini-widget.');
                            return;
                        }
                    }
                    const cents = this.nodeToCents(priceElem);



                    const className = 'sequra-educational-popup';
                    const modifierClassName = className + '--' + widget.product;

                    const oldWidget = element.parentNode.querySelector('.' + className + '.' + modifierClassName);
                    if (oldWidget) {
                        if (cents == oldWidget.getAttribute('data-amount')) {
                            return; // no need to update the widget, the price is the same.
                        }

                        oldWidget.remove();// remove the old widget to draw a new one.
                    }

                    if (widget.maxAmount && widget.maxAmount < cents) {
                        return;
                    }

                    const widgetNode = document.createElement('small');
                    widgetNode.className = className + ' ' + modifierClassName;
                    widgetNode.setAttribute('data-amount', cents);
                    widgetNode.setAttribute('data-product', widget.product);

                    const creditAgreements = Sequra.computeCreditAgreements({ amount: cents, product: widget.product })[widget.product];
                    let creditAgreement = null
                    do {
                        creditAgreement = creditAgreements.pop();
                    } while (cents < creditAgreement.min_amount.value && creditAgreements.length > 1);
                    if (cents < creditAgreement.min_amount.value && !widget.messageBelowLimit) {
                        return;
                    }

                    if (cents >= creditAgreement.min_amount.value) {
                        widgetNode.innerText = widget.message.replace('%s', creditAgreement.instalment_total.string);
                    } else {
                        if (!widget.messageBelowLimit) {
                            return;
                        }
                        widgetNode.innerText = widget.messageBelowLimit.replace('%s', creditAgreement.min_amount.string);
                    }

                    if (element.nextSibling) {//Insert after
                        element.parentNode.insertBefore(widgetNode, element.nextSibling);
                        this.refreshComponents();
                    } else {
                        element.parentNode.appendChild(widgetNode);
                    }

                },

                drawWidgetOnElement: function (widget, element) {
                    const priceSrc = this.getPriceSelector(widget);
                    const priceElem = document.querySelector(priceSrc);
                    if (!priceElem) {
                        console.error(priceSrc + ' is not a valid css selector to read the price from, for seQura widget.');
                        return;
                    }
                    const cents = this.nodeToCents(priceElem);

                    const className = 'sequra-promotion-widget';
                    const modifierClassName = className + '--' + widget.product;

                    const oldWidget = element.parentNode.querySelector('.' + className + '.' + modifierClassName);
                    if (oldWidget) {
                        if (cents == oldWidget.getAttribute('data-amount')) {
                            return; // no need to update the widget, the price is the same.
                        }

                        oldWidget.remove();// remove the old widget to draw a new one.
                    }

                    const promoWidgetNode = document.createElement('div');
                    promoWidgetNode.className = className + ' ' + modifierClassName;
                    promoWidgetNode.setAttribute('data-amount', cents);
                    promoWidgetNode.setAttribute('data-product', widget.product);

                    const theme = this.presets[widget.theme] ? this.presets[widget.theme] : widget.theme;
                    try {
                        const attributes = JSON.parse(theme);
                        for (let key in attributes) {
                            promoWidgetNode.setAttribute('data-' + key, "" + attributes[key]);
                        }
                    } catch (e) {
                        promoWidgetNode.setAttribute('data-type', 'text');
                    }

                    if (widget.campaign) {
                        promoWidgetNode.setAttribute('data-campaign', widget.campaign);
                    }
                    if (widget.registrationAmount) {
                        promoWidgetNode.setAttribute('data-registration-amount', widget.registrationAmount);
                    }

                    if (element.nextSibling) {//Insert after
                        element.parentNode.insertBefore(promoWidgetNode, element.nextSibling);
                        this.refreshComponents();
                    } else {
                        element.parentNode.appendChild(promoWidgetNode);
                    }
                }
            }
        };

        SequraWidgetFacade.init()
        Sequra.onLoad(() => {
            SequraWidgetFacade.drawWidgetsOnPage();
            if ('undefined' !== typeof jQuery) {
                const variationForm = jQuery('.variations_form');
                if (variationForm.length) {
                    variationForm.on('show_variation', () => SequraWidgetFacade.drawWidgetsOnPage(false));
                    variationForm.on('hide_variation', () => SequraWidgetFacade.drawWidgetsOnPage());
                }
            }
        });
    });
})();
```
