File: D:/HostingSpaces/SBogers10/carrot.komma.pro/resources/js/kms/attributes/multiSelect.js
class MultiSelect {
constructor(attributeWrapper) {
this.attributeKey = attributeWrapper.dataset.key;
this.value = attributeWrapper.querySelector('[name="'+this.attributeKey+'"]').value;
/**Select all elements for easy access later on**/
this.uiWidget = attributeWrapper.querySelector('.ui-widget');
this.input = document.getElementById(this.attributeKey);
this.openbutton = document.getElementById(this.attributeKey + "-open");
this.autoCompleteSelectField = document.getElementById(this.attributeKey + "-fake");
this.itemsWrapper = document.getElementById(this.attributeKey+"_items");
this.autoSaveUrl = attributeWrapper.dataset.autosaveUrl;
this.maxItemsToSelect = attributeWrapper.dataset.maxItemsToSelect;
this.selectOptions = MultiSelect._buildSelectOptionsFromObject(JSON.parse(attributeWrapper.dataset.items));
this.dataSet = MultiSelect._buildDataSetFromSelectOptions(this.selectOptions);
this.sortable = attributeWrapper.dataset.sortable;
if(this.sortable) {
let self = this;
this.dragElement = null;
// Sorting starts
this.itemsWrapper.addEventListener('dragstart', function (evt){
self.dragElement = evt.target; // Remembering an element that will be moved
// Limiting the movement type
evt.dataTransfer.effectAllowed = 'move';
evt.dataTransfer.setData('Text', self.dragElement.textContent);
// Subscribing to the events at dnd
self.itemsWrapper.addEventListener('dragover', self._onDragOver.bind(self), false);
self.itemsWrapper.addEventListener('dragend', self._onDragEnd.bind(self), false);
setTimeout(function () {
// If this action is performed without setTimeout, then
// the moved object will be of this class.
self.dragElement.classList.add('ghost');
}, 0)
}, false);
}
this._makeMultiSelect();
this._addItems();
this._updateRealInput();
this._enableListeners(true);
this._enableOpenButton();
}
static _buildSelectOptionsFromObject(data) {
let selectOptions = [];
let itemsCount = data.length;
for(let index = 0; index < itemsCount; index++) {
let selectOption = SelectOption.fromObject(data[index]);
if(selectOption) selectOptions.push(selectOption);
}
return selectOptions;
}
/**
* @param {SelectOption[]}selectOptions
* @private
*/
static _buildDataSetFromSelectOptions(selectOptions) {
let dataset = [];
let selectoptionsCount = selectOptions.length;
for(let index = 0; index < selectoptionsCount; index++) {
/** @var {SelectOption} selectOption **/
dataset.push({
id: selectOptions[index].value,
value: selectOptions[index].htmlContent.replace(/ /gi, '.')
});
}
return dataset;
}
_enableListeners(enable) {
this.listenersEnabled = enable;
let length = this.itemsWrapper.children.length;
//Add / remove listeners to items
for (let i = 0; i < length; i++) {
let item = this.itemsWrapper.children[i];
let id = item.dataset.id;
item.classList.remove('readonly');
this.autoCompleteSelectField.classList.remove('hidden');
if(!enable) {
item.classList.add('readonly');
this.autoCompleteSelectField.classList.add('hidden');
this.openbutton.classList.add('hidden');
}
}
}
_addItems()
{
let valuesFromAttribute = this.value.split(',');
let datasetLength = this.dataSet.length;
for (let valuesIndex = 0; valuesIndex < valuesFromAttribute.length; valuesIndex++) {
let idFromAttribute = parseInt(valuesFromAttribute[valuesIndex]);
for (let itemIndex = 0; itemIndex < datasetLength; itemIndex++) {
let item = this.dataSet[itemIndex];
if (item.id === idFromAttribute) {
this._addItem(item.id, item.value);
break;
}
}
}
};
/**
* Makes the fake input field and autocomplete input field.
* Focus method is triggered when you hover over items in the menu
* select method is triggered when you click an item in the list.
*/
_makeMultiSelect() {
let self = this;
let element = document.getElementById(this.attributeKey+"-fake");
$(element).autocomplete({
source: this.dataSet,
minLength: 0,
focus: function (event, ui) {
return false;
},
select: function (event, ui) {
$(this.autoCompleteSelectField).val("");
self._addItem(ui.item.id, ui.item.value);
self._updateRealInput();
self._autosaveIfNeeded();
self._enableListeners(true);
return false;
},
});
};
/**
* Adds a span tag containing the value given and the data-id property set to id given to
* the p tag with class items.
*/
_addItem(id, value) {
let self = this;
if(this._itemsCount() >= self.maxItemsToSelect) return;
let itemElement = document.createElement('p');
itemElement.classList.add('item');
if(this.sortable) {
itemElement.draggable = true;
value = '<span class="drag-icon"></span><span class="text">'+value+'</span>';
}
itemElement.innerHTML = value;
let removeButton = document.createElement('span');
removeButton.classList.add('remove');
itemElement.dataset.id = id;
itemElement.dataset.sort_order = this._itemsCount()+1;
removeButton.addEventListener('click', (function (itemToRemove) {
return function() {
if(!self.listenersEnabled) return;
self._removeItem(itemToRemove);
}
})(id));
this.itemsWrapper.appendChild(itemElement);
itemElement.appendChild(removeButton);
if(this._itemsCount() >= self.maxItemsToSelect) {
this.uiWidget.style.display = "none";
}
};
/**
* Make sure we can open the autocomplete field dropdown with the open button
*/
_enableOpenButton() {
let self = this;
this.openbutton.addEventListener('click', function(event) {
$(self.autoCompleteSelectField).autocomplete("search", "");
});
};
_itemsCount()
{
return this.itemsWrapper.childNodes.length-1;
};
/**
* Iterates over the child elements of the p element with class items and removes the element that has
* data-id set to the specified id. Then calls _updateRealInput to update the hidden input
*/
_removeItem(id) {
let items = this.itemsWrapper.children;
for (let index = 0; index < items.length; index++) {
let item = items[index];
if (parseInt(item.dataset.id) === id) {
this.itemsWrapper.removeChild(item);
if(this._itemsCount() <= this.maxItemsToSelect) {
this.uiWidget.style.display = "block";
}
break;
}
}
this._updateRealInput();
this._autosaveIfNeeded();
};
/**
* Iterates over the child elements of the p element with class items and concatenates their data-id
* values into a string. After that it sets the hidden input with the id string
*/
_updateRealInput() {
let ids = [];
let items = this.itemsWrapper.children;
let length = this.itemsWrapper.children.length;
for (let i = 0; i < length; i++) {
let item = items[i];
ids[ids.length] = item.dataset.id;
}
let idString = ids.join(',');
this.input.setAttribute('value', idString);
let changeEvent = document.createEvent("Event");
changeEvent.initEvent("change", false, true);
// args: string type, boolean bubbles, boolean cancelable
this.input.dispatchEvent(changeEvent)
};
_autosaveIfNeeded() {
if (!this.autoSaveUrl) return;
let itemIds = this.input.getAttribute('value');
let self = this;
$.ajax({
type: "POST",
url: this.autoSaveUrl,
data: {
'itemIds': itemIds,
},
success: function (response, textStatus, xhr) {
if (xhr.status === 200) {
console.info('autosave successfull to url: ' + self.autoSaveUrl)
} else {
console.error('autosave failure to url (no code 200): ' + self.autoSaveUrl + '');
console.error(response)
}
},
error: function (message) {
console.error('autosave failure to url: ' + self.autoSaveUrl + '');
console.error(message)
},
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
dataType: 'json'
});
};
_onDragOver(evt) {
evt.preventDefault();
evt.dataTransfer.dropEffect = 'move';
let target = evt.target;
if (target && target !== this.dragElement && target.nodeName === 'P') {
// Sorting
this.itemsWrapper.insertBefore(this.dragElement, target.nextSibling || target);
}
}
_onDragEnd(evt){
evt.preventDefault();
this.dragElement.classList.remove('ghost');
this.itemsWrapper.removeEventListener('dragover', this.onDragOver, false);
this.itemsWrapper.removeEventListener('dragend', this.onDragEnd, false);
this._updateRealInput();
}
}
/**
* Matches it's php twin functionality
*/
class SelectOption {
constructor()
{
this._value = '';
this._content = '';
this._htmlContent = '';
this.toJSON = this._toJson.bind(this);
}
_toJson()
{
return {
value: this._value,
content: this._content,
htmlContent: this._htmlContent,
}
}
static fromObject(data) {
if(!this._objectRepresentsSelectOption(data)) return;
let instance = new this;
instance._value = data.value;
instance._content = data.content;
instance._htmlContent = data.htmlContent;
return instance;
}
static _objectRepresentsSelectOption(data)
{
if (
typeof data !== 'object' ||
!data.hasOwnProperty('value') ||
!data.hasOwnProperty('content') ||
!data.hasOwnProperty('htmlContent')
) {
console.error('SelectOption::_objectRepresentsSelectOption: This does not represent an select option. It must be an object containing properties: value, content and htmlContent', data); return false;
}
return true;
}
get value() {
return this._value;
}
set value(value) {
if(typeof value !== 'string') { console.error('SelectOption::Value must be a string'); return; }
this._value = value;
}
get content() {
return this._content;
}
set content(value) {
if(typeof value !== 'string') { console.error('SelectOption::Content must be a string'); return; }
this._content = value;
}
get htmlContent() {
return this._htmlContent;
}
set htmlContent(value) {
if(typeof value !== 'string') { console.error('SelectOption::htmlContent must be a string'); return }
this._htmlContent = value;
}
}