Source: inputitem.js

"use strict";

/**
 * InputItem holds input DOM and takes care of its validation using QueryValidator.
 */
class InputItem extends CommonFormMethods {
	/**
	 * Create InputItemWrapper. 
	 * @param {QueryDataBlock} queryData - An instance of QueryData.
	 * @param {number} index - Order index of the input item in a query.
	 */
	constructor(queryData, index) {
		if(!queryData) {
			throw new InputItemException("No data passed when creating a new instance of InputItemException");
		}
		if(!queryData instanceof QueryDataBlock) {
			throw new InputItemException("Data passed when creating new instance of InputItemException were not an instance of a QueryDataBlock");
		}
		
		super();
		
		this.queryData = queryData;
		this.index = index;
		
		this.internalValue = {
			assignedOptions: [],
			text: "",
			checked: false
		}
		
		this.valid = true;
		this.associateCheckRule();
		
		return this;
	}
	
	/**
	 * Creates InputItem DOM representation.
	 * @return {InputItem} - This instance.
	 */
	createInput() {
		this.dom = document.createElement("input");
		this.dom.classList.add("dt-section-container-input", "dt-section-container-input-type-" + this.queryData.type);
		if(this.queryData.direction) {
			this.dom.classList.add(this.queryData.direction);
		}
		this.dom.setAttribute("type", this.queryData.inputType);
		this.dom.setAttribute("id", this.htmlId);
		this.dom.setAttribute("name", this.name);
		
		if(this.queryData.type) {
			if(this.queryData.type === "order" || this.queryData.type === "assign") {
				this.restrictWriteAccess = true;
			}
		}
		
		if(!this.standsAlone) {
			if(this.queryData.properInputType) {
				if(this.queryData.properInputType === "radio" || this.queryData.properInputType === "checkbox") {
					this.dom.setAttribute("value", this.currentSubItem.id);
				}
			}
			CommonFormFunctions.appendStyle(this.dom, this.currentSubItem.css);
		}
		
		if(this.queryData.required) {
			this.dom.setAttribute("required", "");
		}
		
		return this;
	}
	
	/**
	 * Associtate this InputItem with QueryValidator for its future validation.
	 * Directly refferences to the QueryDataBlock's QueryValidator for this item.
	 * @return {InputItem} - This instance.
	 */
	associateCheckRule() {
		this.currentSubItem.queryValidator.itemToValidate = this;
		return this;
	}
	
	/**
	 * Set if this InputItem's dom input is user editable.
	 * @param {boolean} readOnly - Of true the input item won't be user editable - usable eg. on value assign queries.
	 */
	set restrictWriteAccess(readOnly) {
		var readOnly = readOnly || false;
		this.dom.readOnly = readOnly;
	}
	
	/**
	 * Validate data in this InputItem's DOM input = using adequate QueryValidator.
	 * @return {InputItem} - This instance.
	 */
	validate() {
		if(this.dom) {
			this.valid = this.currentSubItem.queryValidator.validate();
			this.setValid();
		}
		return this.valid;
	}
	
	/**
	 * Get all input item values, either the one typed in or all the assigned options.
	 * @return {string[]} - An array containing all assigned values or a single inserted text value.
	 */
	get value() {
		if(this.internalValue.assignedOptions && this.internalValue.assignedOptions.length > 0) {
			this.orderAssignedOptions();
						
			var optionIds = [];
			for(var i = 0; i < this.internalValue.assignedOptions.length; i++) {
				optionIds.push(this.internalValue.assignedOptions[i].scope.validatorMatch);
			}
			return optionIds;
		}
		this.internalValue.text = this.dom.value;
		this.internalValue.checked = this.dom.checked;
		return this.internalValue.text ? [this.internalValue.text] : [];
	}

	 /**
	 * Returns obect with detailed information about this InputItem.
	 * @return {Object} - {name:string - input item's name, label:string - input item's label or name if no label defined, values:string[], internalValue:string[] - assigned option's text, valid: boolean}
	 */
	get inputData() {
		var options = [];
		for(var i = 0; i < this.internalValue.assignedOptions.length; i++) {
			options.push(this.internalValue.assignedOptions[i].scope.text);
		}
		return {
			name: this.name,
			label: this.currentSubItem.label || this.name,
			values: this.value,
			internalValue: options,
			valid: this.valid,
			checked: this.dom.checked
		};
	}
	
	/**
	 * Assign OptionItem to this instance of InputItem.
	 * @param {OptionItem} option - The OptionItem instance.
	 */
	set option(option) {
		var optionIndex = this.internalValue.assignedOptions.indexOf(option);
		if(optionIndex === -1) {
			this.internalValue.assignedOptions.push(option);
		}
	}
	
	/**
	 * Remove OptionItem from this instance of InputItem.
	 * @return {InputItem} - This instance.
	 */
	deleteOption(option) {
		var optionIndex = this.internalValue.assignedOptions.indexOf(option);
		if(optionIndex > -1) {
			this.internalValue.assignedOptions.splice(optionIndex, 1);
		}
		return this;
	}
	
	/**
	 * Order OptionItems in InputItem by their position rather than the order they were dragged in (reorder).
	 * Depends on Query direction value - "vertical" or "horizontal" - if no match found fallbacks to "horizontal".
	 * @return {OptionItem[]} - Ordered Option items.
	 */
	orderAssignedOptions() {
		for(let i = 0; i < this.internalValue.assignedOptions.length; i++) {
			this.internalValue.assignedOptions[i].updatePosition();
		}
		
		if(this.internalValue.assignedOptions && this.internalValue.assignedOptions.length > 1 && this.queryData.direction) {
			if(this.queryData.direction === "horizontal") {
				this.internalValue.assignedOptions.sort(function(a, b) {
					return a.position.left - b.position.left;
				});
			}
			else {
				this.internalValue.assignedOptions.sort(function(a, b) {
					return a.position.top - b.position.top;
				});
			}
		}
		
		return this.internalValue.assignedOptions;
	}
	
	/**
	 * Modify InputItem DOM input value based on current this.internalValue.assignedOptions state.
	 * @return {InputItem} - This instance.
	 */
	changeAssignedOptionsValue() {
		var finalValue = "";
		if(this.internalValue.assignedOptions && this.internalValue.assignedOptions.length > 0) {
			let ordered = this.orderAssignedOptions();
			for(var i = 0; i < ordered.length; i++) {
				finalValue += (i > 0 ? "&" : "") + ordered[i].scope.id;
			}
		}
		this.dom.value = finalValue;
		return this;
	}
	
	/**
	 * Adds attribute with a value to InputItem's DOM input, like: <tagname attribute="value">.
	 * @param {string} attribute - The name of the attribute.
	 * @param {string} value - The value of the attribute.
	 * @return {InputItem} - This instance.
	 */
	setAttribute(attribute, value) {
		if(attribute != null && value != null) {
			this.dom.setAttribute(attribute, value);
		}
		return this;
	};
	
	/**
	 * Changes this InputItem DOM input class based on the validity.
	 * @param {bollean} [overwriteGraphicsTo] - If set to true, the InputItem will be force-set to valid.
	 * @return {InputItem} - This instance.
	 */
	setValid(overwriteGraphicsTo) {
		if(this.valid || overwriteGraphicsTo == true) {
			this.dom.classList.remove("invalid");
		}
		else {
			this.dom.classList.add("invalid");
		}
		return this;
	}
	
	/**
	 * InputItem ValueChange event - call function on value change.
	 * @param {function} callback - The function that should be executed on input's value change.
	 * @return {InputItem} - This instance.
	 */
	onInputValueChange(callback) {
		this.dom.addEventListener("change", () => {
			callback();
			this.setValid(true);
		});
		return this;
	}
}

/**
 * Exception block for InputItem
 * @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 InputItemException(message) {
	this.message = message;
	this.name = "InputItem";
}