PluginProbe
seQura / 3.2.2
seQura v3.2.2
4.3.4 4.3.3 4.3.2 4.3.1 trunk 2.0.0 2.0.10 2.0.11 2.0.12 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0.0 3.0.2 3.0.5 3.0.6 3.0.7 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 4.0.0 All 30 releases
sequra / assets / js / src / core / ElementGenerator.js

ElementGenerator.js in seQura 3.2.2, at assets/js/src/core/ElementGenerator.js

698 lines 22.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 if (!window.SequraFE) {
2 window.SequraFE = {};
3 }
4
5 (function () {
6 /**
7 * @typedef Option
8 * @property {string?} label
9 * @property {any} value
10 */
11
12 /**
13 * @typedef {Object.<string, *>} ElementProps
14 * @property {string?} name
15 * @property {any?} value
16 * @property {string?} className
17 * @property {string?} placeholder
18 * @property {(value: any) => any?} onChange
19 * @property {string?} label
20 * @property {string?} description
21 * @property {string?} error
22 */
23
24 /**
25 * Creates a generic HTML node element and assigns provided class and inner text.
26 *
27 * @param {keyof HTMLElementTagNameMap} type Represents the name of the tag
28 * @param {string?} className CSS class
29 * @param {string?} innerHTMLKey Inner text translation key.
30 * @param {Record<string, any>?} properties An object of additional properties.
31 * @param {HTMLElement[]?} children
32 * @returns {HTMLElement}
33 */
34 const createElement = (type, className, innerHTMLKey, properties, children) => {
35 const child = document.createElement(type);
36 className && child.classList.add(...className.trim().split(' '));
37 if (innerHTMLKey) {
38 let params = innerHTMLKey.split('|');
39 child.innerHTML = SequraFE.translationService.translate(params[0], params.slice(1));
40 }
41
42 if (properties) {
43 if (properties.dataset) {
44 Object.assign(child.dataset, properties.dataset);
45 delete properties.dataset;
46 }
47
48 Object.assign(child, properties);
49 if (properties.onChange) {
50 child.addEventListener('change', properties.onChange, false);
51 }
52
53 if (properties.onClick) {
54 child.addEventListener('click', properties.onClick, false);
55 }
56 }
57
58 if (children) {
59 child.append(...children);
60 }
61
62 return child;
63 };
64
65 /**
66 * Creates an element out of provided HTML markup.
67 *
68 * @param {string} html
69 * @returns {HTMLElement}
70 */
71 const createElementFromHTML = (html) => {
72 const element = document.createElement('div');
73 element.innerHTML = html;
74
75 return element.firstElementChild;
76 };
77
78 /**
79 * Creates a button.
80 *
81 * @param {{ label?: string, type?: 'primary' | 'secondary' | 'cancel' | 'danger', size?: 'small' | 'medium',
82 * className?: string, [key: string]: any, onClick?: () => void}} props
83 * @return {HTMLButtonElement}
84 */
85 const createButton = ({type, size, className, onClick, label, ...properties}) => {
86 const cssClass = ['sq-button'];
87 type && cssClass.push('sqt--' + type);
88 size && cssClass.push('sqm--' + size);
89 className && cssClass.push(className);
90
91 const button = createElement('button', cssClass.join(' '), '', {type: 'button', ...properties}, [
92 createElement('span', '', label)
93 ]);
94
95 onClick &&
96 button.addEventListener(
97 'click',
98 (event) => {
99 event.stopPropagation();
100 event.preventDefault();
101 onClick();
102 },
103 false
104 );
105
106 return button;
107 };
108
109 /**
110 * Creates a version badge.
111 *
112 * @param {string?} version
113 * @return {HTMLElement}
114 */
115 const createVersionBadge = (version) => {
116 return createElement('span', 'sq-version-badge', version);
117 };
118
119 /**
120 * Creates a Loader.
121 *
122 * @param {{ type?: 'small' | 'large', variation?: 'dark', className?:
123 * string, [key: string]: any }} props
124 * @return {HTMLElement}
125 */
126 const createLoader = ({type, variation}) => {
127 const cssClass = ['sq-loader'];
128 type && cssClass.push('sqt--' + type);
129 variation && cssClass.push('sqm--' + variation);
130
131 return createElement('div', cssClass.join(' '), '', null, [createElement('span', 'sqp-spinner', null)]);
132 };
133
134 /**
135 * Creates a link that looks like a button.
136 *
137 * @param {{text?: string, className?: string, href: string, downloadFile?: string, openInNewTab?: boolean}} props
138 * @return {HTMLLinkElement}
139 */
140 const createButtonLink = ({text, className = '', href, downloadFile, openInNewTab}) => {
141 const link = createElement('a', className, '', {
142 href: href,
143 target: openInNewTab ? "_blank" : ""
144 }, [createElement('span', '', text)]);
145 if (downloadFile) {
146 link.setAttribute('download', downloadFile);
147 }
148
149 return link;
150 };
151
152 /**
153 * Creates an input field wrapper around the provided input element.
154 *
155 * @param {HTMLElement} input The input element.
156 * @param {string?} label Label translation key.
157 * @param {string?} description Description translation key.
158 * @param {string?} variation Variation of the input element.
159 * @param {string?} error Error translation key.
160 * @param {string?} className Class name.
161 * @return {HTMLDivElement}
162 */
163 const createFieldWrapper = (input, label, description, variation, error, className) => {
164 const field = createElement('div', 'sq-field-wrapper ' + className + (variation ? 'sqm--' + variation : ''));
165 const labelWrapper = createElement('div', 'sq-label-wrapper');
166 if (label) {
167 labelWrapper.appendChild(createElement('span', 'sqp-field-title', label));
168 }
169
170 if (description) {
171 labelWrapper.appendChild(createElement('span', 'sqp-field-subtitle', description));
172 }
173
174 const inputWrapper = createElement('div', '', '', null, [
175 input,
176 error ? field.appendChild(createElement('span', 'sqp-input-error', error)) : ''
177 ]);
178
179 field.append(labelWrapper, inputWrapper);
180
181 return field;
182 };
183
184 /**
185 * Creates store switcher.
186 *
187 * @param {{options: Option[], value: string, label?: string, onChange: (value: string) => void?}} props
188 * @return {HTMLDivElement}
189 */
190 const createStoreSwitcher = (props) => {
191 const wrapper = createElement('div', 'sq-store-switcher');
192
193 wrapper.append(
194 createDropdownField({
195 className: 'sqp-store-switcher-dropdown ',
196 placeholder: 'general.selectStorePlaceholder',
197 variation: 'label-left',
198 ...props
199 })
200 );
201
202 return wrapper;
203 };
204
205 /**
206 * Creates dropdown wrapper around the provided dropdown element.
207 *
208 * @param {ElementProps & DropdownComponentModel} props The properties.
209 * @return {HTMLDivElement}
210 */
211 const createDropdownField = ({className = '', label, description, variation, error, ...dropdownProps}) => {
212 return createFieldWrapper(
213 SequraFE.components.Dropdown.create(dropdownProps),
214 label,
215 description,
216 variation,
217 error,
218 className
219 );
220 };
221
222 /**
223 * Creates a password input field.
224 *
225 * @param {ElementProps} props The properties.
226 * @return {HTMLElement}
227 */
228 const createPasswordField = ({className = '', label, description, variation, error, onChange, ...rest}) => {
229 const wrapper = createElement('div', `sq-password ${className}`);
230 const input = createElement('input', 'sqp-field-component', '', {type: 'password', ...rest});
231 const span = createElement('span');
232 span.addEventListener('click', () => {
233 if (input.type === 'password') {
234 input.type = 'text';
235 } else {
236 input.type = 'password';
237 }
238 });
239 onChange && input.addEventListener('change', (event) => onChange(event.currentTarget?.value));
240
241 wrapper.append(input, span);
242
243 return createFieldWrapper(wrapper, label, description, variation, error, '');
244 };
245
246 /**
247 * Creates a text input field.
248 *
249 * @param {ElementProps & { type?: 'text' | 'number', variation?: 'label-left' }} props The properties.
250 * @return {HTMLElement}
251 */
252 const createTextField = ({className = '', label, description, variation, error, onChange, ...rest}) => {
253 /** @type HTMLInputElement */
254 const input = createElement('input', `sqp-field-component ${className}`, '', {type: 'text', ...rest});
255 onChange && input.addEventListener('change', (event) => onChange(event.currentTarget?.value));
256
257 return createFieldWrapper(input, label, description, variation, error, '');
258 };
259
260 /**
261 * Creates a text area field.
262 *
263 * @param {ElementProps & { type?: 'text' | 'number', variation?: 'label-left' }} props The properties.
264 * @return {HTMLElement}
265 */
266 const createTextArea = ({className = '', label, description, variation, error, onChange, ...rest}) => {
267 /** @type HTMLInputElement */
268 const textArea = createElement('textarea', `sqp-field-component ${className}`, '', {...rest});
269 onChange && textArea.addEventListener('change', (event) => onChange(event.currentTarget?.value));
270
271 return createFieldWrapper(textArea, label, description, variation, error, 'sqp-textarea-field');
272 };
273
274 /**
275 * Creates a country input field.
276 * @param {string?} countryCode
277 * @param {string?} merchantId
278 * @param {(value: string) => void?} onChange
279 * @return {HTMLElement}
280 */
281 const createCountryField = ({countryCode, merchantId, onChange}) => {
282 const code = countryCode.toUpperCase();
283 return createElement('div', 'sq-country-field-wrapper', '', null, [
284 createElementFromHTML(SequraFE.imagesProvider.flags[code] || ''),
285 createTextField({
286 className: 'sq-text-input',
287 name: `country_${code}`,
288 label: `countries.${code}.label`,
289 description: `countries.${code}.description`,
290 value: merchantId,
291 onChange
292 })
293 ]);
294 };
295
296 /**
297 * Creates a number input field.
298 *
299 * @param {ElementProps & { type?: 'text' | 'number' }} props The properties.
300 * @return {HTMLElement}
301 */
302 const createNumberField = (props) => {
303 return createTextField({type: 'number', step: '0.01', ...props});
304 };
305
306 /**
307 * Creates a radio group field.
308 *
309 * @param {ElementProps} props The properties.
310 * @return {HTMLElement}
311 */
312 const createRadioGroupField = ({name, value, className, options, label, description, error, onChange}) => {
313 const wrapper = createElement('div', 'sq-radio-input-group');
314 options.forEach((option) => {
315 const label = createElement('label', 'sq-radio-input');
316 const props = {type: 'radio', value: option.value, name};
317 if (value === option.value) {
318 props.checked = 'checked';
319 }
320
321 label.append(createElement('input', className, '', props), createElement('span', '', option.label));
322 wrapper.append(label);
323 onChange && label.addEventListener('click', () => onChange(option.value));
324 });
325
326 return createFieldWrapper(wrapper, label, description, error, '');
327 };
328
329 /**
330 * Creates a toggle field.
331 *
332 * @param {ElementProps} props The properties.
333 * @return {HTMLElement}
334 */
335 const createToggleField = ({className = '', label, description, error, onChange, value, ...rest}) => {
336 /** @type HTMLInputElement */
337 const checkbox = createElement('input', 'sqp-toggle-input', '', {type: 'checkbox', checked: value, ...rest});
338 onChange && checkbox.addEventListener('change', () => onChange(checkbox.checked));
339
340 const field = createElement('div', className + ' sq-field-wrapper sqt--toggle', '', null, [
341 createElement('h3', 'sqp-field-title', label, null, [
342 createElement('label', 'sq-toggle', '', null, [checkbox, createElement('span', 'sqp-toggle-round')])
343 ])
344 ]);
345
346 if (description) {
347 field.appendChild(createElement('span', 'sqp-field-subtitle', description));
348 }
349
350 if (error) {
351 field.appendChild(createElement('span', 'sqp-input-error', error));
352 }
353
354 return field;
355 };
356
357 /**
358 * Creates a checkbox field.
359 *
360 * @param {ElementProps} props The properties.
361 * @return {HTMLElement}
362 */
363 const createCheckboxField = ({className = '', label, description, error, onChange, value, ...rest}) => {
364 /** @type HTMLInputElement */
365 const checkbox = createElement('input', 'sqp-checkbox-input', '', {type: 'checkbox', checked: value, ...rest});
366 onChange && checkbox.addEventListener('change', () => onChange(checkbox.checked));
367
368 const field = createElement('div', className + ' sq-field-wrapper sqt--checkbox');
369
370 if (label) {
371 field.appendChild(createElement('h3', 'sqp-field-title', label));
372 }
373
374 field.appendChild(
375 createElement('div', 'sqp-description-wrapper', '', null, [
376 createElement('label', 'sq-checkbox', '', null, [checkbox, createElement('span', 'sqp-checkmark')]),
377 description && createElement('span', 'sqp-field-subtitle', description)
378 ])
379 );
380
381 if (error) {
382 field.appendChild(createElement('span', 'sqp-input-error', error));
383 }
384
385 return field;
386 };
387
388 /**
389 * Creates a button field.
390 *
391 * @param {ElementProps & { onClick?: () => void , buttonType?: string, buttonSize?: string,
392 * buttonLabel?: string, className?: string}} props The properties.
393 * @return {HTMLElement}
394 */
395 const createButtonField = (
396 {
397 label,
398 description,
399 className,
400 buttonType,
401 buttonSize,
402 buttonLabel,
403 onClick,
404 error
405 }
406 ) => {
407 const button = createButton({
408 type: buttonType,
409 size: buttonSize,
410 className: '',
411 label: SequraFE.translationService.translate(buttonLabel),
412 onClick: onClick
413 });
414
415 return createFieldWrapper(button, label, description, '', error, className);
416 };
417
418 /**
419 * Creates a field with a link that looks like a button.
420 *
421 * @param {ElementProps & {text: string, href: string}} props
422 */
423 const createButtonLinkField = ({label, text, description, href, error}) => {
424 const buttonLink = createButtonLink({
425 text: SequraFE.translationService.translate(text),
426 className: '',
427 href: href
428 });
429
430 return createFieldWrapper(buttonLink, label, description, '', error, '');
431 };
432
433 /**
434 * Creates multi item selector wrapper around the provided multi item selector element.
435 *
436 * @param {ElementProps & MultiItemSelectorComponentModel} props The properties.
437 * @return {HTMLDivElement}
438 */
439 const createMultiItemSelectorField = ({label, description, variation, error, ...config}) => {
440 return createFieldWrapper(
441 SequraFE.components.MultiItemSelector.create(config),
442 label,
443 description,
444 variation,
445 error,
446 ''
447 );
448 };
449
450 /**
451 * Creates a flash message.
452 *
453 * @param {string|string[]} messageKey
454 * @param {'error' | 'warning' | 'success'} status
455 * @param {number?} clearAfter Time in ms to remove alert message.
456 * @return {HTMLElement}
457 */
458 const createFlashMessage = (messageKey, status, clearAfter) => {
459 const hideHandler = () => {
460 wrapper.remove();
461 };
462 const wrapper = createElement('div', `sq-alert sqt--${status}`);
463 let messageBlock;
464 if (Array.isArray(messageKey)) {
465 const [titleKey, descriptionKey] = messageKey;
466 messageBlock = createElement('div', 'sqp-alert-title', '', null, [
467 createElement('span', 'sqp-message', '', null, [
468 createElement('span', 'sqp-message-title', titleKey),
469 createElement('span', 'sqp-message-description', descriptionKey)
470 ])
471 ]);
472 } else {
473 messageBlock = createElement('span', 'sqp-alert-title', messageKey);
474 }
475
476 const button = createButton({onClick: hideHandler});
477
478 if (clearAfter) {
479 setTimeout(hideHandler, clearAfter);
480 }
481
482 wrapper.append(messageBlock, button);
483
484 return wrapper;
485 };
486
487 /**
488 * Creates a toaster message.
489 *
490 * @param {string} label
491 * @param {number} timeout Clear timeout in ms.
492 * @returns {HTMLElement}
493 */
494 const createToaster = (label, timeout = 5000) => {
495 const toaster = createElement('div', 'sq-toaster', '', null, [
496 createElement('span', 'sqp-toaster-title', label),
497 createElement('button', 'sq-button', '', null, [createElement('span')])
498 ]);
499
500 toaster.children[1].addEventListener('click', () => toaster.remove());
501
502 setTimeout(() => toaster.remove(), timeout);
503
504 return toaster;
505 };
506
507 /**
508 * Adds a page footer with save and cancel buttons.
509 *
510 * @param {() => void} onSave
511 * @param {() => void} onCancel
512 * @returns HTMLElement
513 */
514 const createPageFooter = ({onSave, onCancel}) => {
515 return createElement('div', 'sq-page-footer', '', null, [
516 createElement('div', 'sqp-actions', '', null, [
517 createButton({
518 className: 'sqp-cancel',
519 type: 'form-secondary',
520 size: 'medium',
521 label: 'general.cancel',
522 onClick: onCancel
523 }),
524 createButton({
525 className: 'sqp-save',
526 type: 'form-primary',
527 size: 'medium',
528 label: 'general.saveChanges',
529 onClick: onSave
530 })
531 ])
532 ]);
533 };
534
535 /**
536 * Creates form fields based on the fields configurations.
537 *
538 * @param {FormField[]} fields
539 */
540 const createFormFields = (fields) => {
541 /** @type HTMLElement[] */
542 const result = [];
543 fields.forEach(({type, ...rest}) => {
544 switch (type) {
545 case 'text':
546 result.push(createTextField({...rest, className: 'sq-text-input'}));
547 break;
548 case 'number':
549 result.push(createNumberField({...rest, className: 'sq-text-input'}));
550 break;
551 case 'dropdown':
552 result.push(createDropdownField(rest));
553 break;
554 case 'radio':
555 result.push(createRadioGroupField(rest));
556 break;
557 case 'checkbox':
558 result.push(createToggleField(rest));
559 break;
560 case 'button':
561 result.push(createButtonField(rest));
562 break;
563 case 'buttonLink':
564 result.push(createButtonLinkField(rest));
565 break;
566 }
567
568 rest.className && result[result.length - 1].classList.add(...rest.className.trim().split(' '));
569 });
570
571 return result;
572 };
573
574 /**
575 * Creates a main header item.
576 *
577 * @param {{title?: string, text?: string}} params
578 * @returns {HTMLElement}
579 */
580 const createPageHeading = ({title, text}) => {
581 return createElement('div', 'sqp-page-heading', '', null, [
582 createElement('h3', 'sqp-page-title', title),
583 createElement('span', 'sqp-description', text)
584 ]);
585 };
586
587 /**
588 * Creates a wizard sidebar.
589 *
590 * @param {{label: string, description?: string, href: string, isActive?: boolean, isCompleted?: boolean}[]} steps
591 * @returns {HTMLElement}
592 */
593 const createWizardSidebar = ({steps}) => {
594 const wrapper = createElement('div', 'sq-wizard-sidebar');
595
596 wrapper.append(
597 ...steps.map((item) => {
598 return createElement(
599 'a',
600 'sqp-step' + (item.isActive ? ' sqs--active' : item.isCompleted ? ' sqs--completed' : ''),
601 '',
602 {
603 href: item.href
604 },
605 [
606 createElement('span', 'sq-link-label', item.label),
607 item.description ? createElement('span', 'sq-link-description', item.description) : ''
608 ]
609 );
610 })
611 );
612
613 return wrapper;
614 }
615
616 /**
617 * Creates a support link FAB.
618 *
619 * @returns {HTMLElement}
620 */
621 const createSupportLink = () => {
622 return createElement(
623 'a',
624 'sq-support-link',
625 '',
626 {
627 href: SequraFE.translationService.translate('supportLink.link'),
628 target: '_blank'
629 },
630 [
631 createElement(
632 'span',
633 'sq-support-link-label',
634 'supportLink.label'
635 )
636 ]
637 );
638 }
639
640 /**
641 * Creates a settings sidebar.
642 *
643 * @param {{label: string, icon: string, href: string, isActive?: boolean}[]} links
644 * @returns {HTMLElement}
645 */
646 const createSettingsSidebar = ({links}) => {
647 const wrapper = createElement('ul', 'sq-settings-sidebar');
648
649 wrapper.append(
650 ...links.map((item) => {
651 return item ? createElement('li', 'sq-sidebar-item' + (item.isActive ? ' sqs--active' : ''), '', null, [
652 createElement(
653 'a',
654 'sq-sidebar-link' + ' sqm--' + item.icon,
655 '',
656 {
657 href: item.href
658 },
659 [createElement('span', '', item.label)]
660 )
661 ]) : '';
662 })
663 );
664
665 return wrapper;
666 };
667
668 SequraFE.elementGenerator = {
669 createElement,
670 createElementFromHTML,
671 createButton,
672 createLoader,
673 createDropdownField,
674 createPasswordField,
675 createTextField,
676 createTextArea,
677 createNumberField,
678 createToggleField,
679 createCheckboxField,
680 createRadioGroupField,
681 createFlashMessage,
682 createStoreSwitcher,
683 createButtonField,
684 createButtonLink,
685 createButtonLinkField,
686 createMultiItemSelectorField,
687 createCountryField,
688 createFormFields,
689 createPageFooter,
690 createToaster,
691 createPageHeading,
692 createVersionBadge,
693 createWizardSidebar,
694 createSupportLink,
695 createSettingsSidebar
696 };
697 })();
698