Skip to content

Commit

Permalink
IBX-8436: Added modal helper (#1320)
Browse files Browse the repository at this point in the history
  • Loading branch information
dew326 authored Dec 6, 2024
1 parent 60ce234 commit d005d0e
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/bundle/Resources/encore/ibexa.js.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const layout = [
path.resolve(__dirname, '../public/js/scripts/core/toggle.button.js'),
path.resolve(__dirname, '../public/js/scripts/core/slug.value.input.autogenerator.js'),
path.resolve(__dirname, '../public/js/scripts/core/date.time.picker.js'),
path.resolve(__dirname, '../public/js/scripts/core/taggify.js'),
path.resolve(__dirname, '../public/js/scripts/core/suggestion.taggify.js'),
path.resolve(__dirname, '../public/js/scripts/adaptive.filters.js'),
path.resolve(__dirname, '../public/js/scripts/admin.notifications.js'),
path.resolve(__dirname, '../public/js/scripts/button.trigger.js'),
Expand Down
130 changes: 130 additions & 0 deletions src/bundle/Resources/public/js/scripts/core/suggestion.taggify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { getRestInfo } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper';

(function (global, doc, ibexa) {
const MIN_QUERY_LENGTH = 3;

class SuggestionTaggify extends ibexa.core.Taggify {
constructor(config) {
super(config);

const { siteaccess, token } = getRestInfo();

this.suggestionsListNode = config.suggestionsListNode ?? this.container.querySelector('.ibexa-taggify__suggestions');
this.token = config.token ?? token;
this.siteaccess = config.siteaccess ?? siteaccess;

this.renderSuggestionsList = this.renderSuggestionsList.bind(this);
this.getItemsFromResponse = this.getItemsFromResponse.bind(this);
}

hideSuggestionsList() {
this.suggestionsListNode.classList.add('ibexa-taggify__suggestions--hidden');
}

showSuggestionsList() {
this.suggestionsListNode.classList.remove('ibexa-taggify__suggestions--hidden');
}

createSuggestionsRequestBody(query) {
return JSON.stringify({
ViewInput: {
identifier: `find-suggestions-${query}`,
public: false,
ContentQuery: {
FacetBuilders: {},
SortClauses: {},
Query: {
FullTextCriterion: `${query}*`,
ContentTypeIdentifierCriterion: ibexa.adminUiConfig.userContentTypes,
},
limit: 10,
offset: 0,
},
},
});
}

createSuggestionsRequest(body) {
return new Request('/api/ibexa/v2/views', {
method: 'POST',
headers: {
Accept: 'application/vnd.ibexa.api.View+json; version=1.1',
'Content-Type': 'application/vnd.ibexa.api.ViewInput+json; version=1.1',
'X-Siteaccess': this.siteaccess,
'X-CSRF-Token': this.token,
},
body,
mode: 'same-origin',
credentials: 'same-origin',
});
}

getSuggestions(query) {
const body = this.createSuggestionsRequestBody(query);
const request = this.createSuggestionsRequest(body);

fetch(request)
.then(ibexa.helpers.request.getJsonFromResponse)
.then(this.getItemsFromResponse)
.then(this.renderSuggestionsList)
.catch(ibexa.helpers.notification.showErrorNotification);
}

getItemsFromResponse(response) {
return response.View.Result.searchHits.searchHit.map((hit) => hit.value.Content);
}

renderSuggestionsList(items) {
const fragment = doc.createDocumentFragment();

items.forEach((item) => {
const listItemNode = this.renderSuggestionItem(item);

listItemNode.addEventListener(
'click',
({ currentTarget }) => {
this.addTag(currentTarget.innerHTML, item);
this.hideSuggestionsList();

this.inputNode.value = '';
},
false,
);

fragment.append(listItemNode);
});

this.suggestionsListNode.innerHTML = '';
this.suggestionsListNode.append(fragment);

this.showSuggestionsList();
}

renderSuggestionItem(item) {
const itemTemplate = this.suggestionsListNode.dataset.template;
const renderedTemplate = itemTemplate.replace('{{ name }}', item.TranslatedName);
const container = doc.createElement('div');

container.innerHTML = '';
container.insertAdjacentHTML('beforeend', renderedTemplate);

return container.querySelector('div');
}

handleInputKeyUp(event) {
super.handleInputKeyUp(event);

if (this.isAcceptKeyPressed(event.key)) {
this.hideSuggestionsList();

return;
}

if (this.inputNode.value.length > MIN_QUERY_LENGTH) {
this.getSuggestions(this.inputNode.value);
}
}
}

ibexa.addConfig('core.SuggestionTaggify', SuggestionTaggify);
})(window, window.document, window.ibexa);
67 changes: 67 additions & 0 deletions src/bundle/Resources/public/js/scripts/core/taggify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
(function (global, doc, ibexa) {
class Taggify {
constructor(config) {
this.container = config.container;
this.acceptKeys = config.acceptKeys ?? ['Enter'];
this.inputNode = config.inputNode ?? this.container.querySelector('.ibexa-taggify__input');
this.listNode = config.listNode ?? this.container.querySelector('.ibexa-taggify__list');
this.tagsPattern = config.tagsPattern ?? null;
this.tags = config.tags ?? new Set();

this.attachEventsToTag = this.attachEventsToTag.bind(this);
this.handleInputKeyUp = this.handleInputKeyUp.bind(this);
}

afterTagsUpdate() {}

isAcceptKeyPressed(key) {
return this.acceptKeys.includes(key);
}

addTag(name, value) {
const tagTemplate = this.listNode.dataset.template;
const renderedTemplate = tagTemplate.replace('{{ name }}', name).replace('{{ value }}', value);
const div = doc.createElement('div');

div.insertAdjacentHTML('beforeend', renderedTemplate);

const tag = div.querySelector('.ibexa-taggify__list-tag');

this.attachEventsToTag(tag, value);
this.listNode.insertBefore(tag, this.inputNode);
this.tags.add(value);
this.afterTagsUpdate();
}

removeTag(tag, value) {
this.tags.delete(value);
tag.remove();

this.afterTagsUpdate();
}

attachEventsToTag(tag, value) {
const removeBtn = tag.querySelector('.ibexa-taggify__btn--remove');

removeBtn.addEventListener('click', () => this.removeTag(tag, value), false);
}

handleInputKeyUp(event) {
if (this.tagsPattern && !this.tagsPattern.test(this.inputNode.value)) {
return;
}

if (this.isAcceptKeyPressed(event.key)) {
this.addTag(this.inputNode.value, this.inputNode.value);

this.inputNode.value = '';
}
}

init() {
this.inputNode.addEventListener('keyup', this.handleInputKeyUp, false);
}
}

ibexa.addConfig('core.Taggify', Taggify);
})(window, window.document, window.ibexa);
6 changes: 4 additions & 2 deletions src/bundle/Resources/public/js/scripts/edit.header.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(function (global, doc) {
(function (global, doc, ibexa) {
const SCROLL_POSITION_TO_FIT = 50;
const HEADER_RIGHT_MARGIN = 50;
const MIN_HEIGHT_DIFF_FOR_FITTING_HEADER = 150;
Expand All @@ -11,6 +11,7 @@

const { height: expandedHeaderHeight } = headerNode.getBoundingClientRect();
const scrolledContent = doc.querySelector('.ibexa-edit-content > :first-child');
const { controlZIndex } = ibexa.helpers.modal;
const fitEllipsizedTitle = () => {
const headerBottomRowNode = headerNode.querySelector('.ibexa-edit-header__row--bottom');
const titleNode = headerBottomRowNode.querySelector('.ibexa-edit-header__name--ellipsized');
Expand Down Expand Up @@ -46,4 +47,5 @@
};

contentNode.addEventListener('scroll', fitHeader, false);
})(window, window.document);
controlZIndex(headerNode);
})(window, window.document, window.ibexa);
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as icon from './icon.helper';
import * as content from './content.helper';
import * as location from './location.helper';
import * as middleEllipsis from './middle.ellipsis';
import * as modal from './modal.helper';
import * as notification from './notification.helper';
import * as objectInstances from './object.instances';
import * as pagination from './pagination.helper';
Expand All @@ -31,6 +32,7 @@ import * as user from './user.helper';
ibexa.addConfig('helpers.content', content);
ibexa.addConfig('helpers.location', location);
ibexa.addConfig('helpers.ellipsis.middle', middleEllipsis);
ibexa.addConfig('helpers.modal', modal);
ibexa.addConfig('helpers.notification', notification);
ibexa.addConfig('helpers.objectInstances', objectInstances);
ibexa.addConfig('helpers.pagination', pagination);
Expand Down
12 changes: 12 additions & 0 deletions src/bundle/Resources/public/js/scripts/helpers/modal.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const controlZIndex = (container) => {
const initialZIndex = container.style.zIndex;

container.addEventListener('show.bs.modal', () => {
container.style.zIndex = 'initial';
});
container.addEventListener('hide.bs.modal', () => {
container.style.zIndex = initialZIndex;
});
};

export { controlZIndex };
61 changes: 61 additions & 0 deletions src/bundle/Resources/public/scss/_taggify.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
.ibexa-taggify {
position: relative;

&__list {
display: flex;
align-items: center;
flex-wrap: wrap;
min-height: calculateRem(48px);
border: calculateRem(1px) solid $ibexa-color-light;
}

&__list-tag {
padding-left: calculateRem(8px);
border: calculateRem(1px) solid $ibexa-color-light;
border-radius: $ibexa-border-radius;
margin-right: calculateRem(8px);
display: flex;
align-items: center;

.ibexa-btn {
height: auto;
margin-left: calculateRem(8px);
}

& + .ibexa-taggify__input {
&::placeholder {
opacity: 0;
}
}
}

&__input {
border: none;
flex-grow: 1;

&:focus-visible {
outline: none;
}
}

&__suggestions {
position: absolute;
width: 100%;
bottom: 0;
transform: translate(0, calc(100% + calculateRem(4px)));
border: calculateRem(1px) solid $ibexa-color-light;
border-radius: $ibexa-border-radius;
background-color: $ibexa-color-white;
padding: calculateRem(4px);
box-shadow: $ibexa-edit-content-box-shadow;

&--hidden {
display: none;
}
}

&__suggestion-item {
padding: calculateRem(8px) calculateRem(12px);
cursor: pointer;
}
}
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 @@ -130,3 +130,4 @@
@import 'user-profile';
@import 'additional-actions';
@import 'user-mode-badge';
@import 'taggify';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends '@ibexadesign/ui/component/taggify/taggify.html.twig' %}

{% block main_class %}ibexa-taggify--suggestion{% endblock %}

{% block additional_tools_wrapper %}
<div
class="ibexa-taggify__suggestions ibexa-taggify__suggestions--hidden"
data-template="{{ include("@ibexadesign/ui/component/suggestion_taggify/suggestion_taggify_item.html.twig", {
name: '{{ name }}',
})|e('html_attr') }}"
></div>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="ibexa-taggify__suggestion-item">{{ name }}</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="ibexa-taggify {% block main_class %}{% endblock %}">
{% block list_wrapper %}
<div
class="ibexa-taggify__list form-control"
data-template="{{ include("@ibexadesign/ui/component/taggify/taggify_tag.html.twig", {
name: '{{ name }}',
})|e('html_attr') }}"
>
<input type="text" class="ibexa-taggify__input" placeholder="{{ placeholder|default('') }}">
</div>
{% endblock %}
{% block additional_tools_wrapper %}{% endblock %}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div class="ibexa-taggify__list-tag">
<span class="ibexa-taggify__list-tag-name">{{ name }}</span>
<button type="button" class="btn ibexa-btn ibexa-btn--ghost ibexa-btn--small ibexa-btn--no-text ibexa-taggify__btn--remove">
<svg class="ibexa-icon ibexa-icon--tiny">
<use xlink:href="{{ ibexa_icon_path('discard') }}"></use>
</svg>
</button>
</div>

0 comments on commit d005d0e

Please sign in to comment.