Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/bundle/Resources/encore/ibexa.js.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const layout = [
path.resolve(__dirname, '../public/js/scripts/core/base.chart.js'),
path.resolve(__dirname, '../public/js/scripts/core/line.chart.js'),
path.resolve(__dirname, '../public/js/scripts/core/multilevel.popup.menu.js'),
path.resolve(__dirname, '../public/js/scripts/core/multistep.selector.js'),
path.resolve(__dirname, '../public/js/scripts/core/split.btn.js'),
path.resolve(__dirname, '../public/js/scripts/core/pie.chart.js'),
path.resolve(__dirname, '../public/js/scripts/core/bar.chart.js'),
Expand Down
159 changes: 159 additions & 0 deletions src/bundle/Resources/public/js/scripts/core/multistep.selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
(function (global, doc, ibexa) {
class StepSelector {
constructor(container, apiUrl) {
this.container = container;
this.apiUrl = apiUrl;
this.dropdownInitialContainer = this.container.querySelector('.ibexa-multistep-selector__dropdown-initial');
this.dropdownContainer = this.container.querySelector('.ibexa-multistep-selector__dropdown');
this.dropdownTemplate = this.container.querySelector('template');
this.filledTemplate = null;

this.createDropdown = this.createDropdown.bind(this);
this.loadData = this.loadData.bind(this);
}

fillSourceOptions(options) {
const { escapeHTML } = ibexa.helpers.text;
const sourceInput = this.filledTemplate.querySelector('.ibexa-dropdown__source .ibexa-input');

options.forEach(({ id, name }) => {
const nameHtmlEscaped = escapeHTML(name);
const idHtmlEscaped = escapeHTML(id);
const optionNode = doc.createElement('option');

optionNode.value = idHtmlEscaped;
optionNode.textContent = nameHtmlEscaped;

sourceInput.appendChild(optionNode);
});
}

fillListOptions(options) {
const { escapeHTML } = ibexa.helpers.text;
const { dangerouslyInsertAdjacentHTML } = ibexa.helpers.dom;
const itemsList = this.filledTemplate.querySelector('.ibexa-dropdown__items-list');
const itemsListFragment = doc.createDocumentFragment();
const { template: itemTemplate } = itemsList.dataset;

options.forEach(({ id, name }) => {
const nameHtmlEscaped = escapeHTML(name);
const idHtmlEscaped = escapeHTML(id);
const itemsContainer = doc.createElement('ul');
const itemRendered = itemTemplate.replace('{{ value }}', idHtmlEscaped).replaceAll('{{ label }}', nameHtmlEscaped);

dangerouslyInsertAdjacentHTML(itemsContainer, 'beforeend', itemRendered);
itemsListFragment.append(itemsContainer.querySelector('li'));
});

itemsList.append(itemsListFragment);
}

createDropdown(options = []) {
this.filledTemplate = this.dropdownTemplate.content.cloneNode(true);

this.fillSourceOptions(options);
this.fillListOptions(options);
this.toggleDropdown(true);

this.dropdownContainer.innerHTML = '';
this.dropdownContainer.appendChild(this.filledTemplate);
this.filledTemplate = null;
this.dropdownInstance = new ibexa.core.Dropdown({
container: this.dropdownContainer.querySelector('.ibexa-dropdown'),
});

this.dropdownInstance.init();
this.bindOnChangeListener();
}

toggleDropdown(showFinal) {
const initialDropdown = this.container.querySelector('.ibexa-multistep-selector__dropdown-initial');
const finalDropdown = this.container.querySelector('.ibexa-multistep-selector__dropdown');

if (showFinal) {
initialDropdown.setAttribute('hidden', true);
finalDropdown.removeAttribute('hidden');
} else {
finalDropdown.setAttribute('hidden', true);
initialDropdown.removeAttribute('hidden');
}
}

toggleLoader(showLoader) {
const placeholder = this.dropdownInitialContainer.querySelector('.ibexa-dropdown__selected-placeholder');
const loader = this.dropdownInitialContainer.querySelector('.ibexa-dropdown__loader-wrapper');

if (showLoader) {
placeholder.setAttribute('hidden', true);
loader.removeAttribute('hidden');
} else {
loader.setAttribute('hidden', true);
placeholder.removeAttribute('hidden');
}
}

loadData(requestPromise) {
this.toggleDropdown(false);
this.toggleLoader(true);

requestPromise().then((response) => {
this.toggleLoader(false);
this.createDropdown(response);
});
}

addOnChangeListener(callback) {
this.bindOnChangeListener = () => {
this.dropdownInstance.sourceInput.addEventListener('change', (event) => {
const selectedValues = [...event.target.selectedOptions].map((option) => option.value);
callback({ selectedValues });
});
};
}

reset() {
this.toggleDropdown(false);
this.toggleLoader(false);
}

init() {}
}

class MultistepSelector {
constructor(container, steps) {
this.container = container;

this.steps = steps.map((step) => {
const stepContainer = this.container.querySelector(`.ibexa-multistep-selector__step[data-step-id="${step.id}"]`);

return {
...step,
instance: new StepSelector(stepContainer),
};
});
}

init() {
this.steps.forEach((step, key) => {
const nextStep = this.steps[key + 1];
const futureSteps = this.steps.slice(key + 2);

step.instance.init();

step.instance.addOnChangeListener((params) => {
if (nextStep) {
nextStep.instance.loadData(() => nextStep.loadData(params));
}

futureSteps.forEach((futureStep) => futureStep.instance.reset());
});
});

if (this.steps[0]) {
this.steps[0].instance.loadData(() => this.steps[0].loadData());
}
}
}

ibexa.addConfig('core.MultistepSelector', MultistepSelector);
})(window, window.document, window.ibexa);
32 changes: 32 additions & 0 deletions src/bundle/Resources/public/scss/_multistep-selector.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.ibexa-multistep-selector {
margin-top: calculateRem(24px);

.ibexa-alert {
margin: calculateRem(24px) 0;
}

.ibexa-dropdown {
&__loader-wrapper {
display: flex;
justify-content: center;
width: 100%;
}

&__loader {
@include spinner(calculateRem(24px), calculateRem(3px), $ibexa-color-primary);
}
}

&__steps {
display: flex;
gap: calculateRem(16px);
}

&__step {
flex-grow: 1;
}

& + & {
margin-top: calculateRem(40px);
}
}
1 change: 1 addition & 0 deletions src/bundle/Resources/public/scss/ibexa.scss
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,4 @@
@import 'additional-actions';
@import 'user-mode-badge';
@import 'taggify';
@import 'multistep-selector';
Original file line number Diff line number Diff line change
Expand Up @@ -66,59 +66,61 @@
})|e('html_attr') }}"
data-placeholder-template="{{ placeholder_list_item|e('html_attr') }}"
>
{% if no_items %}
{% if not is_dynamic %}
{{ placeholder_list_item }}
{% endif %}
{% else %}
{% if value is empty %}
{% if not multiple %}
{% if placeholder is defined and placeholder is not none %}
{% set default_label = 'dropdown.placeholder.all'|trans()|desc('All') %}

{% include selected_item_template_path with {
value: '',
label: _self.get_translated_label(placeholder, translation_domain)|trim|default(default_label),
} %}
{% else %}
{% set first_choice = choices_flat|first %}

{% include selected_item_template_path with {
value: first_choice.value,
label: _self.get_translated_label(first_choice.label, translation_domain),
icon: first_choice.icon is defined ? first_choice.icon,
} %}
{% endif %}
{% block selection_info_content %}
{% if no_items %}
{% if not is_dynamic %}
{{ placeholder_list_item }}
{% endif %}
{% else %}
{% for choice in choices_flat %}
{% if custom_form ? choice.value == value : choice is selectedchoice(value) %}
{% set label = selected_item_label is defined
? selected_item_label
: _self.get_translated_label(choice.label, translation_domain)
%}
{% if value is empty %}
{% if not multiple %}
{% if placeholder is defined and placeholder is not none %}
{% set default_label = 'dropdown.placeholder.all'|trans()|desc('All') %}

{% include selected_item_template_path with {
label,
value: choice.value,
icon: choice.icon is defined ? choice.icon,
} %}
{% endif %}
{% endfor %}
{% endif %}
{% if multiple %}
<li
class="ibexa-dropdown__selected-item ibexa-dropdown__selected-item--predefined ibexa-dropdown__selected-placeholder"
{% if value is empty %}hidden{% endif %}
>
{% if placeholder is defined and placeholder is not none %}
{{ _self.get_translated_label(placeholder, translation_domain )}}
{% else %}
{{ 'dropdown.placeholder'|trans|desc("Choose an option") }}
{% include selected_item_template_path with {
value: '',
label: _self.get_translated_label(placeholder, translation_domain)|trim|default(default_label),
} %}
{% else %}
{% set first_choice = choices_flat|first %}

{% include selected_item_template_path with {
value: first_choice.value,
label: _self.get_translated_label(first_choice.label, translation_domain),
icon: first_choice.icon is defined ? first_choice.icon,
} %}
{% endif %}
{% endif %}
</li>
{% else %}
{% for choice in choices_flat %}
{% if custom_form ? choice.value == value : choice is selectedchoice(value) %}
{% set label = selected_item_label is defined
? selected_item_label
: _self.get_translated_label(choice.label, translation_domain)
%}

{% include selected_item_template_path with {
label,
value: choice.value,
icon: choice.icon is defined ? choice.icon,
} %}
{% endif %}
{% endfor %}
{% endif %}
{% if multiple %}
<li
class="ibexa-dropdown__selected-item ibexa-dropdown__selected-item--predefined ibexa-dropdown__selected-placeholder"
{% if value is empty %}hidden{% endif %}
>
{% if placeholder is defined and placeholder is not none %}
{{ _self.get_translated_label(placeholder, translation_domain )}}
{% else %}
{{ 'dropdown.placeholder'|trans|desc("Choose an option") }}
{% endif %}
</li>
{% endif %}
{% endif %}
{% endif %}
{% endblock selection_info_content %}

<li class="ibexa-dropdown__selected-item ibexa-dropdown__selected-item--predefined ibexa-dropdown__selected-overflow-number" hidden></li>
</ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% set source %}
<select name="selector-{{ id }}" class="ibexa-input ibexa-input--select" multiple>
</select>
{% endset %}
<div class="ibexa-multistep-selector__step ibexa-multistep-selector__step--{{ id }}" data-step-id="{{ id }}">
<template class="ibexa-multistep-selector__dropdown-template">
{% include '@ibexadesign/ui/component/dropdown/dropdown.html.twig' with {
source,
choices: [],
is_dynamic: true,
multiple: true,
class: 'ibexa-dropdown--' ~ id,
} only %}
</template>
<label class="ibexa-label form-label">
{{ label }}
</label>
<div class="ibexa-multistep-selector__dropdown-initial">
{% embed '@ibexadesign/ui/component/dropdown/dropdown.html.twig' with {
source,
choices: [],
is_disabled: true,
class: 'ibexa-dropdown--' ~ id,
} %}
{% block selection_info_content %}
<li class="ibexa-dropdown__selected-item ibexa-dropdown__selected-item--predefined ibexa-dropdown__selected-placeholder">
{{ 'multistep_selector.step.dropdown.placeholder'|trans|desc('Select') }}
</li>
<li class="ibexa-dropdown__selected-item ibexa-dropdown__loader-wrapper" hidden>
<div class="ibexa-dropdown__loader"></div>
</li>
{% endblock %}
{% endembed %}
</div>
<div class="ibexa-multistep-selector__dropdown">
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="ibexa-multistep-selector ibexa-multistep-selector--{{ id }}">
<h3>{{ title }}</h3>

{% include '@ibexadesign/ui/component/alert/alert.html.twig' with {
type: 'info',
title: info,
} only %}

<div class="ibexa-multistep-selector__steps">
{% for step in steps %}
{% include '@ibexadesign/ui/component/multistep_selector/step_selector.html.twig' with {
id: step.id,
label: step.label,
} %}
{% endfor %}
</div>
</div>
Loading