<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Preview Layout</title>
<link media="all" rel="stylesheet" href="../../css/styles.css">
<script>
window.APP = {
modules: {},
addModule: function(name, config) {
this.modules[name] = this.modules[name] || [];
this.modules[name].push(config);
},
DEBUG: 1,
CONFIG: {},
};
</script>
<style>
body {
margin: 25px !important;
}
</style>
</head>
<body class="antialiased text-body-base font-roboto">
<script>
'use strict';
(function(hyva, undefined) {
function lifetimeToExpires(options, defaults) {
const lifetime = options.lifetime || defaults.lifetime;
if (lifetime) {
const date = new Date;
date.setTime(date.getTime() + lifetime * 1000);
return date;
}
return null;
}
function generateRandomString() {
const allowedCharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
length = 16;
let formKey = '',
charactersLength = allowedCharacters.length;
for (let i = 0; i < length; i++) {
formKey += allowedCharacters[Math.round(Math.random() * (charactersLength - 1))]
}
return formKey;
}
const sessionCookieMarker = {
noLifetime: true
}
const cookieTempStorage = {};
const internalCookie = {
get(name) {
const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
return v ? v[2] : null;
},
set(name, value, days, skipSetDomain) {
let expires,
path,
domain,
secure,
samesite;
const defaultCookieConfig = {
expires: null,
path: '/',
domain: null,
secure: false,
lifetime: null,
samesite: 'lax'
};
const cookieConfig = window.COOKIE_CONFIG || {};
expires = days && days !== sessionCookieMarker ?
lifetimeToExpires({
lifetime: 24 * 60 * 60 * days,
expires: null
}, defaultCookieConfig) :
lifetimeToExpires(window.COOKIE_CONFIG, defaultCookieConfig) || defaultCookieConfig.expires;
path = cookieConfig.path || defaultCookieConfig.path;
domain = !skipSetDomain && (cookieConfig.domain || defaultCookieConfig.domain);
secure = cookieConfig.secure || defaultCookieConfig.secure;
samesite = cookieConfig.samesite || defaultCookieConfig.samesite;
document.cookie = name + "=" + encodeURIComponent(value) +
(expires && days !== sessionCookieMarker ? '; expires=' + expires.toGMTString() : '') +
(path ? '; path=' + path : '') +
(domain ? '; domain=' + domain : '') +
(secure ? '; secure' : '') +
(samesite ? '; samesite=' + samesite : 'lax');
},
isWebsiteAllowedToSaveCookie() {
const allowedCookies = this.get('user_allowed_save_cookie');
if (allowedCookies) {
const allowedWebsites = JSON.parse(unescape(allowedCookies));
return allowedWebsites[CURRENT_WEBSITE_ID] === 1;
}
return false;
},
getGroupByCookieName(name) {
const cookieConsentConfig = window.cookie_consent_config || {};
let group = null;
for (let prop in cookieConsentConfig) {
if (!cookieConsentConfig.hasOwnProperty(prop)) continue;
if (cookieConsentConfig[prop].includes(name)) {
group = prop;
break;
}
}
return group;
},
isCookieAllowed(name) {
const cookieGroup = this.getGroupByCookieName(name);
return cookieGroup ?
window.cookie_consent_groups[cookieGroup] :
this.isWebsiteAllowedToSaveCookie();
},
saveTempStorageCookies() {
for (const [name, data] of Object.entries(cookieTempStorage)) {
if (this.isCookieAllowed(name)) {
this.set(name, data['value'], data['days'], data['skipSetDomain']);
delete cookieTempStorage[name];
}
}
}
};
hyva.getCookie = (name) => {
const cookieConfig = window.COOKIE_CONFIG || {};
if (cookieConfig.cookie_restriction_enabled && !internalCookie.isCookieAllowed(name)) {
return cookieTempStorage[name] ? cookieTempStorage[name]['value'] : null;
}
return internalCookie.get(name);
}
hyva.setCookie = (name, value, days, skipSetDomain) => {
const cookieConfig = window.COOKIE_CONFIG || {};
if (cookieConfig.cookie_restriction_enabled && !internalCookie.isCookieAllowed(name)) {
cookieTempStorage[name] = {
value,
days,
skipSetDomain
};
return;
}
return internalCookie.set(name, value, days, skipSetDomain);
}
hyva.setSessionCookie = (name, value, skipSetDomain) => {
return hyva.setCookie(name, value, sessionCookieMarker, skipSetDomain)
}
hyva.getBrowserStorage = () => {
const browserStorage = window.localStorage || window.sessionStorage;
if (!browserStorage) {
console.warn('Browser Storage is unavailable');
return false;
}
try {
browserStorage.setItem('storage_test', '1');
browserStorage.removeItem('storage_test');
} catch (error) {
console.warn('Browser Storage is not accessible', error);
return false;
}
return browserStorage;
}
hyva.postForm = (postParams) => {
const form = document.createElement("form");
let data = postParams.data;
if (!postParams.skipUenc && !data.uenc) {
data.uenc = btoa(window.location.href);
}
form.method = "POST";
form.action = postParams.action;
Object.keys(postParams.data).map(key => {
const field = document.createElement("input");
field.type = 'hidden'
field.value = postParams.data[key];
field.name = key;
form.appendChild(field);
});
const form_key = document.createElement("input");
form_key.type = 'hidden';
form_key.value = hyva.getFormKey();
form_key.name = "form_key";
form.appendChild(form_key);
document.body.appendChild(form);
form.submit();
}
hyva.getFormKey = function() {
let formKey = hyva.getCookie('form_key');
if (!formKey) {
formKey = generateRandomString();
hyva.setCookie('form_key', formKey);
}
return formKey;
}
hyva.formatPrice = (value, showSign, options = {}) => {
const formatter = new Intl.NumberFormat(
'en-US',
Object.assign({
style: 'currency',
currency: 'EUR',
signDisplay: showSign ? 'always' : 'auto'
}, options)
);
return (typeof Intl.NumberFormat.prototype.formatToParts === 'function') ?
formatter.formatToParts(value).map(({
type,
value
}) => {
switch (type) {
case 'currency':
return '€' || value;
case 'minusSign':
return '- ';
case 'plusSign':
return '+ ';
default:
return value;
}
}).reduce((string, part) => string + part) :
formatter.format(value);
}
/**
* Internal string replacement function implementation, see hyva.str() for usage details.
*
* @param string str Template string with optional placeholders
* @param int nStart Offset for placeholders, 0 means %0 is replaced with args[0], 1 means %1 is replaced with args[0]
* @param array ...args Positional replacement arguments. Rest arguments support isn't at 97% yet, so Array.from(arguments).slice() is used instead.
*/
const formatStr = function(str, nStart) {
const args = Array.from(arguments).slice(2);
return str.replace(/(%+)([0-9]+)/g, (m, p, n) => {
const idx = parseInt(n) - nStart;
if (args[idx] === null || args[idx] === void 0) {
return m;
}
return p.length % 2 ?
p.slice(0, -1).replace('%%', '%') + args[idx] :
p.replace('%%', '%') + n;
})
}
/**
* Replace positional parameters like %1 in string with the rest argument in the matching position.
* The first rest argument replaces %1, the second %2 and so on.
*
* Example: hyva.str('%3 %2 %1', 'a', 'b', 'c') => "c b a"
*
* To insert a literal % symbol followed by a number, duplicate the %, for example %%2 is returned as %2.
*/
hyva.str = function(string) {
const args = Array.from(arguments);
args.splice(1, 0, 1);
return formatStr.apply(undefined, args);
}
/**
* Zero based version of hyva.str(): the first rest argument replaces %0, the second %1 and so on.
*
* Example: hyva.strf('%2 %1 %0', 'a', 'b', 'c') => "c b a"
*
* If in doubt whether to use hyva.str() or hyva.strf(), prefer hyva.str() because it is more similar to __()
* and it might be possible to reuse existing phrase translations with placeholders.
*/
hyva.strf = function() {
const args = Array.from(arguments);
args.splice(1, 0, 0);
return formatStr.apply(undefined, args);
}
/**
* Take a html string as `content` parameter and
* extract an element from the DOM to replace in
* the current page under the same selector,
* defined by `targetSelector`
*/
hyva.replaceDomElement = (targetSelector, content) => {
// Parse the content and extract the DOM node using the `targetSelector`
const parser = new DOMParser();
const doc = parser.parseFromString(content, 'text/html');
const contentNode = doc.querySelector(targetSelector);
// Bail if content can't be found
if (!contentNode) {
return;
}
hyva.activateScripts(contentNode)
// Replace the old DOM node with the new content
document.querySelector(targetSelector).replaceWith(contentNode);
// Reload customerSectionData and display cookie-messages if present
window.dispatchEvent(new CustomEvent("reload-customer-section-data"));
hyva.initMessages();
}
hyva.activateScripts = (contentNode) => {
// Extract all the script tags from the content.
// Script tags won't execute when inserted into a dom-element directly,
// therefore we need to inject them to the head of the document.
const tmpScripts = contentNode.getElementsByTagName('script');
if (tmpScripts.length > 0) {
// Push all script tags into an array
// (to prevent dom manipulation while iterating over dom nodes)
const scripts = [];
for (let i = 0; i < tmpScripts.length; i++) {
scripts.push(tmpScripts[i]);
}
// Iterate over all script tags and duplicate+inject each into the head
for (let i = 0; i < scripts.length; i++) {
let script = document.createElement('script');
script.innerHTML = scripts[i].innerHTML;
document.head.appendChild(script);
// Remove the original (non-executing) node from the content
scripts[i].parentNode.removeChild(scripts[i]);
}
}
return contentNode;
}
/**
* Return base64 encoded current URL that can be used by Magento to redirect the visitor back to the current page.
* The func hyva.getUenc handles additional encoding of +, / and = like \Magento\Framework\Url\Encoder::encode().
*/
const replace = {
['+']: '-',
['/']: '_',
['=']: ','
};
hyva.getUenc = () => btoa(window.location.href).replace(/[+/=]/g, match => replace[match]);
let currentTrap;
const focusableElements = (rootElement) => {
const selector = 'button, [href], input, select, textarea, details, [tabindex]:not([tabindex="-1"]';
return Array.from(rootElement.querySelectorAll(selector))
.filter(el => {
return el.style.display !== 'none' &&
!el.disabled &&
el.tabIndex !== -1 &&
(el.offsetWidth || el.offsetHeight || el.getClientRects().length)
})
}
const focusTrap = (e) => {
const isTabPressed = e.key === 'Tab' || e.keyCode === 9;
if (!isTabPressed) return;
const focusable = focusableElements(currentTrap)
const firstFocusableElement = focusable[0]
const lastFocusableElement = focusable[focusable.length - 1]
e.shiftKey ?
document.activeElement === firstFocusableElement && (lastFocusableElement.focus(), e.preventDefault()) :
document.activeElement === lastFocusableElement && (firstFocusableElement.focus(), e.preventDefault())
};
hyva.releaseFocus = (rootElement) => {
if (currentTrap && (!rootElement || rootElement === currentTrap)) {
currentTrap.removeEventListener('keydown', focusTrap)
currentTrap = null
}
}
hyva.trapFocus = (rootElement) => {
if (!rootElement) return;
hyva.releaseFocus()
currentTrap = rootElement
rootElement.addEventListener('keydown', focusTrap)
const firstElement = focusableElements(rootElement)[0]
firstElement && firstElement.focus()
}
hyva.alpineInitialized = (fn) => window.addEventListener('alpine:initialized', fn, {
once: true
})
window.addEventListener('alpine:initialized', () => {
console.log('Alpine.js initialized')
})
window.addEventListener('user-allowed-save-cookie', () => internalCookie.saveTempStorageCookies())
}(window.hyva = window.hyva || {}));
</script>
<script>
'use strict';
(function(hyva) {
const formValidationRules = {
required(value, options, field, context) {
const el = field.element.type === 'hidden' ? createTextInputFrom(field.element) : field.element,
msg = 'This\u0020is\u0020a\u0020required\u0020field.';
if (el.type === 'radio' || el.type === 'checkbox') {
return (value === undefined || value.length === 0) ? msg : true;
}
el.setAttribute('required', '');
el.checkValidity();
return el.validity.valueMissing ? msg : true;
},
maxlength(value, options, field, context) {
const n = Number(options)
if (value.length > n) {
return n === 1 ?
hyva.strf('Please\u0020enter\u0020no\u0020more\u0020than\u00201\u0020character.') :
hyva.strf('Please\u0020enter\u0020no\u0020more\u0020than\u0020\u00250\u0020characters.', options)
}
return true;
},
minlength(value, options, field, context) {
const n = Number(options)
if (value.length > 0 && value.length < n) {
return n === 1 ?
hyva.strf('Please\u0020enter\u0020at\u0020least\u00201\u0020character.') :
hyva.strf('Please\u0020enter\u0020at\u0020least\u0020\u00250\u0020characters.', options)
}
return true;
},
max(value, options, field, context) {
field.element.setAttribute('max', options);
field.element.checkValidity();
if (field.element.validity.rangeOverflow) {
return hyva.strf('Please\u0020enter\u0020a\u0020value\u0020less\u0020than\u0020or\u0020equal\u0020to\u0020\u0022\u00250\u0022.', options);
}
return true;
},
min(value, options, field, context) {
field.element.setAttribute('min', options);
field.element.checkValidity();
if (field.element.validity.rangeUnderflow) {
return hyva.strf('Please\u0020enter\u0020a\u0020value\u0020greater\u0020than\u0020or\u0020equal\u0020to\u0020\u0022\u00250\u0022.', options);
}
return true;
},
step(value, options, field, context) {
field.element.setAttribute('step', options);
field.element.checkValidity();
if (field.element.validity.stepMismatch) {
const val = Number(value);
const step = Number(options);
const msg = 'Please\u0020enter\u0020a\u0020valid\u0020value.\u0020The\u0020two\u0020nearest\u0020valid\u0020values\u0020are\u0020\u0022\u00250\u0022\u0020and\u0020\u0022\u00251\u0022.';
return hyva.strf(msg, Math.floor(val / step) * step, Math.ceil(val / step) * step);
}
return true;
},
pattern(value, options, field, context) {
field.element.setAttribute('pattern', options);
field.element.checkValidity();
if (field.element.validity.patternMismatch) {
return field.element.title ?
hyva.strf('Please\u0020match\u0020the\u0020requested\u0020format\u003A\u0020\u00250.', field.element.title) :
'Please\u0020match\u0020the\u0020requested\u0020format.'
}
return true;
},
email(value, options, field, context) {
const rule = /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i;
if (value.length > 0 && !rule.test(value)) {
return 'Please\u0020enter\u0020a\u0020valid\u0020email\u0020address.';
}
return true;
},
password(value, options, field, context) {
const rule = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/;
if (value.length > 0 && !rule.test(value)) {
return 'Please\u0020provide\u0020at\u0020least\u0020one\u0020upper\u0020case,\u0020one\u0020lower\u0020case,\u0020one\u0020digit\u0020and\u0020one\u0020special\u0020character\u0020\u0028\u0023\u003F\u0021\u0040\u0024\u0025\u005E\u0026\u002A\u002D\u0029';
}
return true;
},
equalTo(value, options, field, context) {
const dependencyField = context.fields[options].element;
if (value !== dependencyField.value) {
const dependencyFieldName =
dependencyField.label ||
dependencyField.title ||
(dependencyField.labels && dependencyField.labels[0] && dependencyField.labels[0].innerText) ||
dependencyField.name;
return hyva.strf('This\u0020field\u0020value\u0020must\u0020be\u0020the\u0020same\u0020as\u0020\u0022\u00250\u0022.', dependencyFieldName);
}
return true;
}
};
function raceSome(promises, pred) {
return new Promise((resolve, reject) => {
if (promises.length === 0) {
return resolve();
}
let settled = false,
nDone = 0;
const resolveIf = v => {
if (!settled && (pred(v) || ++nDone === promises.length)) {
settled = true;
resolve(v);
}
return v;
}
promises.map(promise => {
promise.then(resolveIf).catch(reason => {
settled = true;
reject(reason)
});
return promise;
});
});
}
const INPUT_ATTRIBUTE_RULES = {
min: 'min',
max: 'max',
required: 'required',
minlength: 'minlength',
maxlength: 'maxlength',
step: 'step',
pattern: 'pattern'
}
const INPUT_TYPE_RULES = {
email: 'email'
}
function getRules(element) {
let rules = {};
Object.keys(INPUT_ATTRIBUTE_RULES).forEach(attrName => {
if (element.hasAttribute(attrName)) {
rules[INPUT_ATTRIBUTE_RULES[attrName]] = element.getAttribute(attrName);
}
})
if (INPUT_TYPE_RULES[element.type]) {
rules[INPUT_TYPE_RULES[element.type]] = true;
}
if (element.dataset.validate) {
try {
Object.assign(rules, JSON.parse(element.dataset.validate));
} catch (error) {
console.error('Validator error. Cannot parse data-validate attribute of element:\n', element);
}
}
return rules;
}
function isInvalidRuleResult(ruleState) {
return typeof ruleState === 'string' || !ruleState || (ruleState.type && ruleState.content);
}
async function runValidateFn(rule, options, value, field) {
return formValidationRules[rule](value, options, field, this);
}
function generateId() {
let id;
do {
id = `${this.idPrefix}-${++this.idSeq}`;
} while (document.getElementById(id));
return id;
}
function isVisible(element) {
const el = element.type !== 'hidden' ? element : (element.parentElement || {});
return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length)
}
function elementWillValidate(element) {
return (element.willValidate || element.type === 'hidden') &&
element.tagName !== 'BUTTON' &&
element.disabled === false &&
!(element.tagName === 'INPUT' && element.type === 'submit') &&
(element.hasAttribute('data-validate-hidden') || isVisible(element))
}
function createMessageContainer(el, fieldWrapperClassName) {
if (!el.parentElement) {
return;
}
const refocus = document.activeElement === el;
const wrapper = document.createElement('div');
wrapper.classList.add.apply(wrapper.classList, fieldWrapperClassName.split(' '));
el.parentElement.insertBefore(wrapper, el);
wrapper.appendChild(el);
refocus && document.activeElement !== el && el.focus();
return wrapper;
}
function containerNotFound(selector, el) {
const msg = `Cannot find message container element ${selector} of ${el.name}`;
console.error(msg, el);
throw msg;
}
function createTextInputFrom(el) {
const text = document.createElement('INPUT');
text.type = 'text';
text.value = el.value;
return text;
}
function classNamesToSelector(classNames) {
return classNames.split(' ')
.filter(className => className.length > 0)
.map(className => `.${className}`)
.join('')
}
function hasMessagesWrapper(field, messagesWrapperClassName) {
return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName));
}
function getMessagesWrapper(field, messagesWrapperClassName) {
if (hasMessagesWrapper.call(this, field, messagesWrapperClassName)) {
return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName));
}
const msgWrapper = document.createElement('ul');
const msgId = generateId.call(this);
msgWrapper.id = msgId;
field.element.setAttribute('aria-errormessage', msgId);
field.element.setAttribute('aria-describedby', msgId);
msgWrapper.classList.add.apply(msgWrapper.classList, messagesWrapperClassName.split(' '));
if (field.validateOnChange) {
msgWrapper.setAttribute('aria-live', 'polite');
}
this.getMessageContainer(field).appendChild(msgWrapper);
return msgWrapper;
}
function getCheckedValues(field) {
const name = field.element.name.replace(/([\\"])/g, '\\$1');
const elements = field.element.form.querySelectorAll('input[name="' + name + '"]:checked');
return Array.from(elements).map(el => el.value);
}
function escapeHtml(s) {
const div = document.createElement('div')
div.innerText = s;
return div.innerHTML;
}
function formValidation(formElement, options) {
// Disable browser default validation
if (formElement.tagName === 'FORM') {
formElement.setAttribute('novalidate', '');
} else {
console.error('formValidation can be initialized only on FORM element', formElement);
return;
}
options = Object.assign({
fieldWrapperClassName: 'field field-reserved',
messagesWrapperClassName: 'messages',
validClassName: 'field-success',
invalidClassName: 'field-error',
pageMessagesWrapperSelector: null,
scrollToFirstError: true,
}, options || {});
return {
state: {
valid: false,
},
fields: {},
idSeq: 0,
idPrefix: formElement.id || 'vld-msg',
setupFields(elements) {
this.fields = {};
Array.from(elements).forEach(element => {
if (elementWillValidate(element)) {
this.setupField(element);
}
});
},
setupField(element) {
if (!element) return;
const onChange = !!element.dataset.onChange;
if (elementWillValidate(element)) {
const rules = getRules(element);
if (Object.keys(rules).length > 0) {
if (this.fields[element.name]) {
Object.assign(this.fields[element.name].rules, rules);
} else {
this.fields[element.name] = {
element,
rules: rules,
validateOnChange: onChange,
state: {
valid: null,
rules: {}
}
}
}
}
} else {
console.error('Element will not validate', element);
}
},
onSubmit(event) {
if (event.target.tagName === 'FORM') {
event.preventDefault();
this.validate()
.then(() => event.target.submit())
.catch(invalidElements => {});
}
},
onChange(event) {
event.target.dataset.onChange = 'true';
if (!Object.keys(this.fields).length) {
this.setupFields(formElement.elements);
}
if (!Object.keys(this.fields).includes(event.target.name)) {
this.setupField(event.target);
}
const field = this.fields[event.target.name];
this.validateField(field);
field && field.element.removeAttribute('data-on-change')
},
validateSafe() {
return new Promise(resolve => this.validate().then(() => resolve(true)).catch(() => {}))
},
validate() {
if (!Object.keys(this.fields).length || !Object.keys(this.fields).length !== formElement.elements.length) {
this.setupFields(formElement.elements);
}
return new Promise(async (resolve, reject) => {
if (formElement.elements) {
await raceSome(this.validateFields(), result => result !== true)
const invalidFields = Object.values(this.fields).filter(field => !field.state.valid);
this.state.valid = invalidFields.length === 0;
if (this.state.valid) {
resolve();
} else {
if (options.scrollToFirstError && invalidFields.length > 0) {
invalidFields[0].element.focus()
invalidFields[0].element.select && invalidFields[0].element.select();
}
reject(invalidFields.map(field => field.element));
}
}
});
},
validateFields() {
const fields = Object.values(this.fields);
fields.forEach(field => {
this.getMessageContainer(field).classList.remove(options.validClassName, options.invalidClassName)
});
return fields.map(field => this.validateField(field))
},
validateField(field) {
if (!field || !elementWillValidate(field.element)) {
return new Promise(resolve => resolve(true))
}
let value;
if (field.element.type === 'checkbox') {
value = getCheckedValues(field);
} else if (field.element.type === 'radio') {
value = getCheckedValues(field)[0] || undefined;
} else if (field.element.tagName === 'SELECT' && field.element.multiple) {
value = Array.from(field.element.selectedOptions).map(opt => opt.value);
} else {
value = field.element.value;
}
const rules = field.rules || {};
field.state.valid = true;
this.showFieldState(field);
const fieldValidations = Object.keys(rules).filter(rule => formValidationRules[rule]).map(async rule => {
return runValidateFn.call(this, rule, rules[rule], value, field).then(result => {
field.state.rules[rule] = result;
return result;
})
});
return new Promise(resolve => {
Promise.all(fieldValidations).then(results => {
field.state.valid = !elementWillValidate(field.element) || rules.length === 0 || !results.some(isInvalidRuleResult)
this.showFieldState(field);
resolve(field.state.valid);
})
});
},
getMessagesByField(field) {
const messages = [];
const invalidRules = Object.keys(field.state.rules).filter(rule => isInvalidRuleResult(field.state.rules[rule]));
field.rules && Object.keys(field.rules).forEach((rule) => {
if (invalidRules.includes(rule)) {
const customMessage = field.element.getAttribute('data-msg-' + rule);
const message = customMessage ? customMessage : field.state.rules[rule];
const ruleOptions = JSON.parse(JSON.stringify(field.rules[rule]));
if (typeof message === 'undefined' || message === null || (typeof message !== 'string' && !message.type)) {
messages.push(hyva.strf('Validation rule "%0" failed.', rule));
} else if (Array.isArray(ruleOptions)) {
ruleOptions.unshift(message.type ? message.content : message);
const content = hyva.strf.apply(null, ruleOptions);
messages.push(message.type ? {
type: message.type,
content
} : content);
} else {
const content = hyva.strf(message.type ? message.content : message, ruleOptions)
messages.push(message.type ? {
type: message.type,
content
} : content);
}
}
});
return messages;
},
/** @deprecated */
getFieldWrapper(field) {
return this.getMessageContainer(field)
},
getMessageContainer(field) {
let container;
const pageSelector = field.element.getAttribute('data-validation-container') || options.pageMessagesContainerSelector;
if (pageSelector) {
container = document.querySelector(pageSelector) ||
containerNotFound(pageSelector, field.element)
} else {
const containerSelector = classNamesToSelector(options.fieldWrapperClassName);
container = field.element.closest(containerSelector) ||
createMessageContainer(field.element, options.fieldWrapperClassName) ||
containerNotFound(containerSelector, field.element);
}
return container;
},
showFieldState(field) {
const container = this.getMessageContainer(field),
hasErrorMessages = hasMessagesWrapper.call(this, field, options.messagesWrapperClassName),
messages = this.getMessagesByField(field).map(m => {
return m.type !== 'html' ? escapeHtml(m.type ? m.content : m) : m.content;
});
container.classList.toggle(options.validClassName, field.state.valid && !hasErrorMessages);
container.classList.toggle(options.invalidClassName, !field.state.valid || hasErrorMessages);
this.createHtmlErrorMessage(field, messages);
if (field.state.valid) {
field.element.removeAttribute('aria-invalid');
} else {
field.element.setAttribute('aria-invalid', 'true');
if (!document.activeElement) {
field.element.focus();
}
}
},
removeMessages(field, messagesClass) {
if (!hasMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName)) {
return;
}
const msgWrapper = getMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName);
const messages = msgWrapper.querySelectorAll(`[data-msg-field='${field.element.name}']`);
Array.from(messages).forEach(msg => msg.remove());
if (msgWrapper && msgWrapper.childElementCount === 0) {
field.element.removeAttribute('aria-errormessage');
field.element.removeAttribute('aria-describedby');
msgWrapper.remove();
}
},
createErrorMessage(field, messages) {
const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml)
this.createHtmlErrorMessage(field, htmlMessages);
},
createHtmlErrorMessage(field, messages) {
this.removeMessages(field, options.messagesWrapperClassName);
field.element.removeAttribute('aria-errormessage');
field.element.removeAttribute('aria-describedby');
if (!field.state.valid) {
const msgWrapper = this.addHtmlMessages(field, options.messagesWrapperClassName, messages);
field.element.setAttribute('aria-errormessage', msgWrapper.id);
field.element.setAttribute('aria-describedby', msgWrapper.id);
}
},
/** @deprecated */
createMessage(field, message) {
return this.addMessages(field, options.messagesWrapperClassName, message);
},
addMessages(field, messagesClass, messages) {
const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml)
return this.addHtmlMessages(field, messagesClass, htmlMessages);
},
addHtmlMessages(field, messagesClass, htmlMessages) {
const msgWrapper = getMessagesWrapper.call(this, field, messagesClass);
(Array.isArray(htmlMessages) ? htmlMessages : [htmlMessages]).forEach((htmlMessage) => {
const li = document.createElement('li');
li.innerHTML = htmlMessage;
li.setAttribute('data-msg-field', field.element.name);
msgWrapper.appendChild(li);
});
return msgWrapper;
},
setField(name, value) {
this.fields[name].element.value = value;
this.fields[name].element.dispatchEvent((new Event('input')));
this.validateField(this.fields[name]);
}
}
}
hyva.formValidation = formValidation;
hyva.formValidation.rules = formValidationRules;
hyva.formValidation.setInputAttributeRuleName = (attrName, ruleName) => INPUT_ATTRIBUTE_RULES[attrName] = ruleName || attrName;
hyva.formValidation.setInputTypeRuleName = (typeName, ruleName) => INPUT_TYPE_RULES[typeName] = ruleName || typeName;
hyva.formValidation.addRule = (name, validator) => formValidationRules[name] = validator;
}(window.hyva = window.hyva || {}));
</script>
<button type="button" x-data x-init="$store.asideBlocs.addAside('')" @click="$store.asideBlocs.toggleAside('')" class=" btn btn-dark btn-solid btn-size-lg">
Open side panel
</button>
<script type="module" src="../../js/anchor.min.js" defer crossorigin></script>
<script type="module" src="../../js/intersect.min.js" defer crossorigin></script>
<script type="module" src="../../js/collapse.min.js" defer crossorigin></script>
<script type="module" src="../../js/alpine3.min.js" defer crossorigin></script>
<script>
(g => {
var h, a, k, p = "The Google Maps JavaScript API",
c = "google",
l = "importLibrary",
q = "__ib__",
m = document,
b = window;
b = b[c] || (b[c] = {});
var d = b.maps || (b.maps = {}),
r = new Set,
e = new URLSearchParams,
u = () => h || (h = new Promise(async (f, n) => {
await (a = m.createElement("script"));
e.set("libraries", [...r] + "");
for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]);
e.set("callback", c + ".maps." + q);
a.src = `https://maps.${c}apis.com/maps/api/js?` + e;
d[q] = f;
a.onerror = () => h = n(Error(p + " could not load."));
a.nonce = m.querySelector("script[nonce]")?.nonce || "";
m.head.append(a)
}));
d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n))
})({
key: "AIzaSyDR6ZEfRdQeGl40JZj1jYumFJ_xZS2z1Gg",
v: "weekly",
// Use the 'v' parameter to indicate the version to use (weekly, beta, alpha, etc.).
// Add other bootstrap parameters as needed, using camel case.
});
</script>
<script src="../../js/markerclusterer.min.js" defer crossorigin></script>
<script>
window.addEventListener('alpine:init', () => {
console.log('Alpine.js has been initialized');
Alpine.store('screen', {
isSmall: window.matchMedia('(max-width: 639px)').matches,
isMobile: window.matchMedia('(max-width: 767px)').matches,
isTablet: window.matchMedia('(max-width: 1023px)').matches,
init() {
const smallMedia = window.matchMedia('(max-width: 639px)');
const mobileMedia = window.matchMedia('(max-width: 767px)');
const tabletMedia = window.matchMedia('(max-width: 1023px)');
this.isSmall = smallMedia.matches;
this.isMobile = mobileMedia.matches;
this.isTablet = tabletMedia.matches;
const updateScreen = (event, type) => {
if (type === 'small') this.isSmall = event.matches;
if (type === 'mobile') this.isMobile = event.matches;
if (type === 'tablet') this.isTablet = event.matches;
};
[{
media: smallMedia,
type: 'small'
},
{
media: mobileMedia,
type: 'mobile'
},
{
media: tabletMedia,
type: 'tablet'
}
].forEach(({
media,
type
}) => {
if (typeof media.onchange !== 'object') {
media.addListener((e) => updateScreen(e, type));
} else {
media.addEventListener('change', (e) => updateScreen(e, type));
}
});
}
});
Alpine.store('asideBlocs', {
asides: [],
// Add new aside block
addAside(name, customProperties = {}) {
if (!this.asides.find(aside => aside.name === name)) {
this.asides.push({
name,
open: false,
...customProperties,
});
}
},
removeAside(name) {
this.asides = this.asides.filter(aside => aside.name !== name);
},
toggleAside(name) {
const aside = this.asides.find(aside => aside.name === name);
if (aside) {
aside.open = !aside.open;
document.body.classList.toggle('overflow-hidden', aside.open);
}
},
closeAside(name) {
const aside = this.asides.find(aside => aside.name === name);
if (aside) {
aside.open = false;
document.body.classList.remove('overflow-hidden');
}
},
openAside(name) {
const aside = this.asides.find(aside => aside.name === name);
if (aside) {
aside.open = true;
document.body.classList.add('overflow-hidden');
}
}
});
Alpine.store('locator', {
days: [{
key: 'monday',
label: 'Lundi'
},
{
key: 'tuesday',
label: 'Mardi'
},
{
key: 'wednesday',
label: 'Mercredi'
},
{
key: 'thursday',
label: 'Jeudi'
},
{
key: 'friday',
label: 'Vendredi'
},
{
key: 'saturday',
label: 'Samedi'
},
{
key: 'sunday',
label: 'Dimanche'
},
],
allStores: [],
countStore: "",
filteredDistanceStores: null,
selectedStore: null,
loading: true,
mapCenter: null,
mapInstance: null,
async init() {
try {
await this.loadLibraries();
await this.loadStores();
} finally {
this.loading = false;
}
},
async loadLibraries() {
const [geometry] = await Promise.all([
google.maps.importLibrary("geometry"),
]);
},
async loadStores() {
try {
const response = await fetch(`${window.location.origin}/js/json/getList.json`);
const data = await response.json();
if (data) {
this.countStore = data.length;
this.allStores = data;
}
} catch (error) {
console.error('Erreur lors du chargement des magasins:', error);
this.allStores = [];
}
},
selectStore(store) {
this.selectedStore = store;
if (typeof this.onSelectStore === 'function') {
this.onSelectStore(store);
}
},
updateDistances(minZoom = 12) {
if (!this.mapInstance) return;
const zoom = this.mapInstance.getZoom();
if (zoom < minZoom) {
return this.filteredDistanceStores = []
}
const center = this.mapInstance.getCenter();
if (!center) return;
this.filteredDistanceStores = this.allStores
.filter(store => this.validateCoordinates(store.lat, store.lng).isValid)
.map(store => {
const position = new google.maps.LatLng(parseFloat(store.lat), parseFloat(store.lng));
const distanceInMeters = google.maps.geometry.spherical.computeDistanceBetween(center, position);
const distanceInKm = distanceInMeters / 1000;
return {
...store,
distanceInKm,
distance: `${distanceInKm.toFixed(1)} km`
};
})
.sort((a, b) => a.distanceInKm - b.distanceInKm);
},
validateCoordinates(lat, lng) {
const isEmpty = (v) => v === '' || v === null || v === undefined;
if (isEmpty(lat) || isEmpty(lng)) {
return {
isValid: false,
error: 'Les coordonnées ne peuvent pas être null, undefined ou vides'
};
}
const latitude = parseFloat(lat);
const longitude = parseFloat(lng);
if (isNaN(latitude) || isNaN(longitude)) {
return {
isValid: false,
error: 'Les coordonnées doivent être des nombres valides'
};
}
if (latitude < -90 || latitude > 90) {
return {
isValid: false,
error: 'La latitude doit être comprise entre -90 et 90'
};
}
if (longitude < -180 || longitude > 180) {
return {
isValid: false,
error: 'La longitude doit être comprise entre -180 et 180'
};
}
return {
isValid: true,
coordinates: {
lat: latitude,
lng: longitude
}
};
},
setMapCenter(lat, lng, zoom) {
const parsedLat = parseFloat(lat);
const parsedLng = parseFloat(lng);
if (!isNaN(parsedLat) && !isNaN(parsedLng)) {
this.mapInstance.panTo({
lat: parsedLat,
lng: parsedLng
})
}
if (zoom) {
this.mapInstance.setZoom(zoom)
}
},
goToStore(store) {
if (!this.mapInstance || !store) return;
const lat = parseFloat(store.lat);
const lng = parseFloat(store.lng);
if (isNaN(lat) || isNaN(lng)) return;
this.setMapCenter(lat, lng, 15)
this.selectStore(store);
},
getOpeningStatus(store) {
const now = new Date();
const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
const currentDayKey = days[now.getDay()];
const schedule = store[currentDayKey];
if (!schedule || schedule.toLowerCase().includes('fermé')) {
return {
status: 'Fermé',
end: null
};
}
const [start, end] = schedule.split(' - ');
if (!start || !end) {
return {
status: 'Fermé',
end: null
};
}
const [startHour, startMin] = start.split(':').map(Number);
const [endHour, endMin] = end.split(':').map(Number);
const startTime = new Date();
startTime.setHours(startHour, startMin, 0);
const endTime = new Date();
endTime.setHours(endHour, endMin, 0);
if (now >= startTime && now <= endTime) {
return {
status: 'Ouvert',
end
};
}
return {
status: 'Fermé',
end: null
};
}
});
});
</script>
</body>
</html>
{% extends '@layout' %}
{% block yield %}
{% render "@template-button" with {
label:'Open side panel',
color: colorType|default('dark'),
button_attribute: ('x-data x-init="$store.asideBlocs.addAside(\'' ~ _target.meta.name ~ '\')" @click="$store.asideBlocs.toggleAside(\'' ~ _target.meta.name ~ '\')"')|replace({"\'": "'"}),
} %}
{{ yield }}
{% endblock %}
/* No context defined. */
No notes defined.