<!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('filter')" @click="$store.asideBlocs.toggleAside('filter')" class="border border-black btn btn-light btn-solid btn-size-lg btn-icons">
<svg class=" shrink-0" width="24" height="24" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M5.41699 11.4584L5.41699 3.33337M5.41699 11.4584C6.10735 11.4584 6.66699 12.018 6.66699 12.7084C6.66699 13.3987 6.10735 13.9584 5.41699 13.9584M5.41699 11.4584C4.72664 11.4584 4.16699 12.018 4.16699 12.7084C4.16699 13.3987 4.72664 13.9584 5.41699 13.9584M5.41699 17.0834L5.41699 13.9584M15.417 11.4584V3.33337M15.417 11.4584C16.1073 11.4584 16.667 12.018 16.667 12.7084C16.667 13.3987 16.1073 13.9584 15.417 13.9584M15.417 11.4584C14.7266 11.4584 14.167 12.018 14.167 12.7084C14.167 13.3987 14.7266 13.9584 15.417 13.9584M15.417 17.0834L15.417 13.9584M10.417 6.45837V3.33337M10.417 6.45837C11.1073 6.45837 11.667 7.01802 11.667 7.70837C11.667 8.39873 11.1073 8.95837 10.417 8.95837M10.417 6.45837C9.72664 6.45837 9.16699 7.01802 9.16699 7.70837C9.16699 8.39873 9.72664 8.95837 10.417 8.95837M10.417 17.0834V8.95837" stroke="currentColor" stroke-width="1.5" fill="none" />
</svg> Filtrer (1)
</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:'Filtrer (1)',
type: 'solid',
icon_type: 'leading-icon',
icon: {
name: 'heroicons--adjustments-outline',
},
button_class: button_class|default('border border-black'),
color: colorType|default('light'),
button_attribute: ('x-data x-init="$store.asideBlocs.addAside(\'filter\')" @click="$store.asideBlocs.toggleAside(\'filter\')"')|replace({"\'": "'"}),
} %}
{{ yield }}
{% endblock %}
/* No context defined. */
No notes defined.