"use strict";
/* QueryItem Class */
/**
* This class holds query and contains InputItemWrappers and their InputItems and OptionItems for draggable queries.
*/
class QueryItem {
/**
* Create QueryItem.
* @param {QueryDataBlock} queryData - A QueryDataBlock instance holding all relevant query data.
*/
constructor(queryData) {
if(!queryData) {
throw new QueryItemException("No data passed when creating a new instance of QueryItem");
}
if(!queryData instanceof QueryDataBlock) {
throw new QueryItemException("Data passed when creating new instance of QueryItem were not an instance of a QueryDataBlock");
}
this.queryData = queryData;
this.inputWrappers = [];
this.inputItems = [];
this.optionItems = [];
this.data = {};
this.errorTag = null;
this.optionsData = {};
this.optionsContainer = this.data.optionsContainer || null;
/* Define draggable plugin */
this.draggable = this.queryData.draggable || null;
}
/**
* Check if OptionItem it is in any of the InputItems of this QueryItem.
* @param {OptionItem} optionItem - An OptionItem which position should be found.
* @param {number} [optionalMargin] - Margin to be taken taken in account while matching OptionItem's position on InputItem positions.
* @return {(InputItems|false)} - Return matched InputItem instance or false if none found.
*/
checkIfOptionItemIsInsideInput(optionItem, optionalMargin) {
optionItem.updatePosition();
for(let i = 0; i < this.inputItems.length; i++) {
/* Set default margin to 0 */
optionalMargin = optionalMargin || 0;
if(this.inputItems[i] && optionItem) {
this.inputItems[i].updatePosition();
/* Element inside vertically */
if(optionItem.position.top > this.inputItems[i].position.top &&
optionItem.position.bottom < this.inputItems[i].position.bottom &&
optionItem.position.top < this.inputItems[i].position.bottom &&
optionItem.position.top > this.inputItems[i].position.top) {
/* Element inside horizontally */
if(optionItem.position.right > this.inputItems[i].position.left &&
optionItem.position.right < this.inputItems[i].position.right &&
optionItem.position.left < this.inputItems[i].position.right &&
optionItem.position.left > this.inputItems[i].position.left) {
return this.inputItems[i];
}
}
}
}
return false;
}
/**
* Propagate validation to the nested inputItems and return Object.
* @return {Object} - Object containing an array of each InputItem and a boolean that is true if this query is valid: { allAvalid: bool, inputItems: InputItem[] }.
*/
validate() {
let validationData = {
allValid: true,
inputItems: []
};
for(let i = 0; i < this.inputWrappers.length; i++) {
let validation = this.inputWrappers[i].validate();
if(!validation.valid) {
validationData.allValid = false;
}
validationData.inputItems.push(this.inputWrappers[i].input);
}
return validationData;
};
/**
* Get all nested inputItems values.
* @return {string[]} - An array of all InpuItem values.
*/
get value() {
var values = [];
for(let i = 0; i < this.inputItems.length; i++) {
values.push(this.inputItems[i].inputData);
}
return values;
};
/**
* Generates dragable items. Requires options to be set in QueryItem.queryData.
* @return {(dom|false)} - A newly created optionsContainer dom element, wrapping each OptionItem or false if no options present for this QueryItem.
*/
renderOptions() {
if(this.queryData.options) {
this.optionsContainer = document.createElement("div");
this.optionsContainer.classList.add("dt-section-container-options");
for(let i = 0; i < this.queryData.options.length; i++) {
let optionItem = new OptionItem(this.queryData, i).createOption();
this.optionsContainer.appendChild(optionItem.dom);
this.optionItems[i] = optionItem;
/* Make option item draggable */
this.makeOptionDraggable(optionItem);
/* Reset option item if moved out of assigned input item upon window size chage */
this.optionItemResetPageResizeListener(optionItem);
}
let optionsClearer = document.createElement("div");
optionsClearer.classList.add("dt-options-clearer");
this.optionsContainer.appendChild(optionsClearer);
return this.optionsContainer;
}
return false;
}
/**
* Resets OptionItem if moved out of assigned input item upon window size chage.
* @param {OptionItem} optionItem - Use the passed optionItem to add an action to upon event listener call.
* @param {function} callback - Potential callback function.
* @return {QueryItem} - Returns instance of this QueryItem.
*/
optionItemResetPageResizeListener(optionItem, callback) {
let inlineCSSbackup = optionItem.dom.getAttribute("style");
window.addEventListener("resize", () => {
if(optionItem.input) {
if(!this.checkIfOptionItemIsInsideInput(optionItem)) {
/* Remove all data attributes on position reset */
let attributesToRemove = [];
for(let i = 0; i < optionItem.dom.attributes.length; i++) {
if(optionItem.dom.attributes[i].name.indexOf("data-") === 0) {
attributesToRemove.push(optionItem.dom.attributes[i].name);
}
}
for(let i = 0; i < attributesToRemove.length; i++) {
optionItem.dom.removeAttribute(attributesToRemove[i]);
}
/* Position reset */
optionItem.dom.style = inlineCSSbackup;
optionItem.positionChanged();
optionItem.dom.classList.add("dt-option-reset");
}
}
});
return this;
}
/**
* Sets draggable plugin for this QueryItem.
* @param {(string|function)} [draggablePlugin] - If string passed one of the pre-set ones. If function passed thant this will be used for draggable. If ommited the default will be set.
*/
set draggable(draggablePlugin) {
draggablePlugin = draggablePlugin || "default";
if(typeof draggablePlugin === "function") {
this.draggablePlugin = draggablePlugin;
return;
}
switch(draggablePlugin) {
case "jQuery":
this.draggablePlugin = CommonFormFunctions.draggablePlugin_jQuery;
break;
case "interact":
this.draggablePlugin = CommonFormFunctions.draggablePlugin_interact;
break;
default:
this.draggablePlugin = CommonFormFunctions.draggablePlugin_interact;
}
}
/**
* Get pre-set draggable plugin or default if none set.
* @return {function} - A function that takes care of draggable.
*/
get draggable() {
return this.draggablePlugin || CommonFormFunctions.draggablePlugin_interact;
}
/**
* Makes passed OptionItem graggable.
* Requires interact.js module!
* @param {OptionItem} optionItem - Use the passed optionItem to make it draggable.
* @return {QueryItem} - Returns instance of this QueryItem.
*/
makeOptionDraggable(optionItem) {
/* Adds "option-draggable" class name to an OptionItem's dom that is about to become draggable */
optionItem.dom.classList.add("option-draggable");
optionItem.dom.setAttribute('draggable', true);
CommonFormFunctions.draggableSnippet({
item: optionItem.dom,
container: this.queryData.sectionContainer,
onPosChange: () => {
let inputItem = this.checkIfOptionItemIsInsideInput(optionItem);
optionItem.positionChanged(inputItem);
},
onEnd: () => {
let inputItem = this.checkIfOptionItemIsInsideInput(optionItem);
optionItem.positionChanged(inputItem);
},
plugin: this.draggable
});
/* Draggable mobile fix: Prevent page scroll on drag event */
optionItem.dom.addEventListener('touchmove', function(event) {
event.preventDefault();
}, false);
return this;
}
/**
* Creates InputItemWrapper and assigns it to the internal list of such items.
* @return {QueryItem} - Returns instance of this QueryItem.
*/
createInput() {
var newInput = new InputItemWrapper(this.queryData, this.inputItems.length).createInput();
this.queryData.sectionContainer.appendChild(newInput.dom);
this.inputWrappers.push(newInput);
this.inputItems.push(newInput.input);
return this;
}
/**
* Writes the content of this QueryItem to a specified DOM container.
* @param {dom} container - Use the passed dom element to contain this QueryItem.
* @return {QueryItem} - Returns instance of this QueryItem.
*/
write(container) {
this.container = container || this.queryData.sectionContainer;
CommonFormFunctions.empty(this.container); // Clear section container
CommonFormFunctions.appendStyle(this.container, this.queryData.css);
/* Section Title */
if(this.queryData.title) {
var title = document.createElement("span");
title.classList.add("dt-section-container-title");
title.textContent = this.queryData.title;
this.container.appendChild(title);
}
/* Section Subtitle */
if(this.queryData.subtitle) {
var subtitle = document.createElement("span");
subtitle.classList.add("dt-section-container-subtitle");
subtitle.textContent = this.queryData.subtitle;
this.container.appendChild(subtitle);
}
/* Section Input */
if(this.queryData.type) {
var hasOptions = false;
if(this.queryData.before) {
this.container.appendChild(this.queryData.before);
}
var count = 0;
do {
this.createInput();
count++;
}
while(count < this.queryData.items.length);
if(this.queryData.options.length > 0) {
if(this.queryData.optionsOrder != "before") {
this.container.appendChild(this.renderOptions());
}
else {
this.container.insertBefore(this.renderOptions(), this.inputWrappers[0].dom);
}
}
if(this.queryData.after) {
this.container.appendChild(this.queryData.after);
}
}
this.queryData.sectionContainer = this.container;
return this;
};
}
/**
* Exception block for QueryItem
* @see {@link https://developer.mozilla.org/cs/docs/Web/JavaScript/Reference/Statements/throw|MDN Throw Statement}
* @param {Object} message - Exception message Object.
* @param {string} message.message - Message text.
*/
function QueryItemException(message) {
this.message = message;
this.name = "QueryItemException";
}