/**
* This file handles all logic that involves the UI.
*
* @module scripts/page_handler
*/
/**
* Class for handling all UI modifications and updates.
*
* @class PageHandler
*/
class PageHandler {
ERROR_TIMEOUT_DURATION = 3000;
ERROR_ANIMATION_DURATION = 500;
ERROR_TIMEOUT_NAME = "error_timeout";
ERROR_ANIMATION_NAME = "error_animation";
BOOLEAN_TRUE_NAME = "true";
BOOLEAN_FALSE_NAME = "false";
DATA_TAG_QUERY_FORMAT = (name) => `[data-${name}]`;
DATA_TAG_NAME = "data-";
DATA_TAG_ARGS_BLACKLIST = [ "event" ];
DATA_EVENT_TAG_NAME = "event";
DATA_TOGGLED_TAG_NAME = "toggled";
DATA_SETTINGS_TAG_NAME = "settings";
DATA_RUN_TAG_NAME = "run";
DATA_CHANGE_TAG_NAME = "change";
DATA_FRAME_TAG_NAME = "frame";
DATA_SETTINGS_FRAME_NAME = "settings-frame";
DATA_VERSIONS_FRAME_NAME = "versions-frame";
DATA_PROJECTS_FRAME_NAME = "projects-frame";
DATA_LOADED_TAG_NAME = "loaded";
DATA_PROJECT_NAME_TAG_NAME = "project-name";
DATA_VERSION_ID_TAG_NAME = "version-id";
CLASS_NAME_QUERY_FORMAT = (name) => `.${name}`;
ERRORS_CLASS_NAME = "errors";
ERRORS_INFO_CLASS_NAME = "info";
ERRORS_OUT_CLASS_NAME = "out";
DEBUG_TOGGLE_CLASS_NAME = "on";
FRAME_OPEN_CLASS_NAME = "frame-open";
GLYPHICON_CLASS_NAME = "glyphicon";
VERSION_FRAME_TITLE_FORMAT = (name) => `Versions (${name})`;
FRAME_TOP_BAR_CLASS_NAME = "top-bar";
CONSOLE_CONTAINER_CLASS_NAME = "console-wrapper";
SETTINGS_FRAME_CONTENT_CLASS_NAME = "settings-container";
SETTINGS_ELEMENT_MAIN_CLASS_NAME = "settings-main";
CURRENT_PROJECT_CONTAINER_CLASS_NAME = "current-name";
PROJECTS_FRAME_CONTENT_CLASS_NAME = "frame-content-wrapper";
PROJECTS_ELEMENT_MAIN_CLASS_NAME = "project-container";
VERSIONS_CONTAINER_CONTENT_CLASS_NAME = "version-container";
VERSIONS_ELEMENT_MAIN_CLASS_NAME = "version-main";
VERSIONS_ELEMENT_FAVORITED_CLASS_NAME = "glyphicon-star";
VERSIONS_ELEMENT_UNFAVORITED_CLASS_NAME = "glyphicon-star-empty";
CONSOLE_MESSAGE_MAIN_CLASS_NAME = "message";
PROJECT_TOGGLE_CLASS_NAME = "opened";
ANIM_LENGTH_CSS_VARIABLE = "--animLength";
NUM_VERSIONS_CSS_VARIABLE = "--numVersions";
VERSION_LIMIT_NAME = "Set max. number of versions per project: ";
NUMBER_TYPE_INPUT = "number";
VERSION_LIMIT_VALNAME = "version_max";
VERSION_LIMIT_TITLE = "Set Version Limit";
VERSION_LIMIT_INPUT_MIN = 1;
VERSION_LIMIT_INPUT_MAX = 1000;
VERSION_FAVORITE_TITLE = "Favorite Version";
VERSION_UNFAVORITE_TITLE = "Unfavorite Version";
/**
* Constructs a new Page Handler.
*
* @constructor
*/
constructor() {
this.errorQueue = [];
this.timers = {};
this.DATA_FRAME_EVENT_MAP = {};
this.DATA_FRAME_EVENT_MAP[this.DATA_SETTINGS_TAG_NAME] = this.DATA_SETTINGS_FRAME_NAME;
this.DATA_FRAME_EVENT_MAP[this.DATA_RUN_TAG_NAME] = this.DATA_VERSIONS_FRAME_NAME;
this.DATA_FRAME_EVENT_MAP[this.DATA_CHANGE_TAG_NAME] = this.DATA_PROJECTS_FRAME_NAME;
this.settings = [];
this.settings.push(new Setting(this.VERSION_LIMIT_NAME, this.NUMBER_TYPE_INPUT, this.VERSION_LIMIT_VALNAME,
this.VERSION_LIMIT_TITLE, this.VERSION_LIMIT_INPUT_MIN, this.VERSION_LIMIT_INPUT_MAX
));
/**
* Opens the settings frame and fills with fetched data if needed.
*
* @function openSettingsFrame
*
* @param {object} data - Either null or holds the settings data to be rendered.
*/
this.openSettingsFrame = (data) => {
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_SETTINGS_TAG_NAME;
const frame = this.getFrame(frameData);
if (data != null) {
let frameContentContainer = frame.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.SETTINGS_FRAME_CONTENT_CLASS_NAME));
this.clearChildren(frameContentContainer);
for (let i = 0; i < this.settings.length; i ++) {
this.settings[i].val = data[this.settings[i].valName];
let newSettingElement = this.buildSettingsElement(this.settings[i]);
frameContentContainer.appendChild(newSettingElement);
}
}
this.openFrame(frame);
this.setFrameLoaded(frame);
}
/**
* Constructs a new settings HTML element.
*
* @function buildSettingsElement
*
* @param {object} data - Holds the settings name, type, and so on for creating a settings element.
*
* @returns {HTMLElement} The newly created settings element.
*/
this.buildSettingsElement = (data) => {
const mainElement = document.createElement('div');
mainElement.classList.add(this.SETTINGS_ELEMENT_MAIN_CLASS_NAME);
let internalElements =
`<div class="settings-name" title="${data.name}">
<div>${data.name}</div>
</div>
<input class="settings-input" type="${data.type}" name="${data.valName}" title="${data.title}" min="${data.min}" max="${data.max}" value="${data.val}">`;
mainElement.innerHTML = internalElements;
return mainElement;
}
/**
* Opens the projects frame and fills with fetched data if needed.
*
* @function openProjectsFrame
*
* @param {object} data - Either null or holds the project data to be rendered.
*/
this.openProjectsFrame = (data) => {
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_CHANGE_TAG_NAME;
const frame = this.getFrame(frameData);
if (data != null) {
let frameContentContainer = frame.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.PROJECTS_FRAME_CONTENT_CLASS_NAME));
this.clearChildren(frameContentContainer);
for (let proj of data) {
this.addProjectElement(proj, frame);
}
}
this.openFrame(frame);
this.setFrameLoaded(frame);
}
/**
* Constructs a new project HTML element.
*
* @function buildProjectElement
*
* @param {object} data - Holds the project name and number of versions for creating a project element.
*
* @returns {HTMLElement} The newly created project element.
*/
this.buildProjectElement = (data) => {
const mainElement = document.createElement('div');
mainElement.classList.add(this.PROJECTS_ELEMENT_MAIN_CLASS_NAME);
mainElement.setAttribute(this.formatDataTagName(this.DATA_PROJECT_NAME_TAG_NAME), data.name);
let numVersions = 0;
if (data.versions != null) {
numVersions = data.versions.length;
}
let internalElements =
`<div class="project-main">
<div class="project-data">
<div class="data-name" title="${data.name}" data-event="select_project">${data.name}</div>
<div class="icon-container"><div class="glyphicon glyphicon-menu-right" title="Toggle Project" data-event="toggle"></div></div>
</div>
<div class="icon-container"><div class="glyphicon glyphicon-trash" title="Delete Project" data-event="delete_project"></div></div>
</div>
<div style="--numVersions: ${numVersions};" class="version-container"></div>`;
mainElement.innerHTML = internalElements;
return mainElement;
}
/**
* Constructs a new version HTML element for the projects frame.
*
* @function buildProjectVersionElement
*
* @param {object} data - Holds the version id, name, date and so on for creating a version element for the projects frame.
*
* @returns {HTMLElement} The newly created version element.
*/
this.buildProjectVersionElement = (data) => {
const mainElement = document.createElement('div');
mainElement.classList.add(this.VERSIONS_ELEMENT_MAIN_CLASS_NAME);
mainElement.setAttribute(this.formatDataTagName(this.DATA_VERSION_ID_TAG_NAME), data.v_id);
let favoriteClassName = (data.favorite) ? this.VERSIONS_ELEMENT_FAVORITED_CLASS_NAME : this.VERSIONS_ELEMENT_UNFAVORITED_CLASS_NAME;
let favoriteTitle = (!data.favorite) ? this.VERSION_FAVORITE_TITLE : this.VERSION_UNFAVORITE_TITLE;
let internalElements =
`<div class="version-data nobord-t">
<div class="data-name" title="${data.name}">${data.name}</div>
<div class="version-data-right">
<div class="data-date" title="${data.date}">${data.date}</div>
<div class="icon-container"><div class="glyphicon ${favoriteClassName}" title="${favoriteTitle}" data-event="favorite"></div></div>
</div>
</div>
<div class="icon-container"><div class="glyphicon glyphicon-trash" title="Delete Version" data-event="delete_version"></div></div>`;
mainElement.innerHTML = internalElements;
return mainElement;
}
/**
* Opens the versions frame and fills with fetched data if needed.
*
* @function openVersionsFrame
*
* @param {object} data - Either null or holds the version data to be rendered.
*/
this.openVersionsFrame = (versionData, currProjData) => {
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_RUN_TAG_NAME;
const frame = this.getFrame(frameData);
if (versionData != null) {
let frameContentContainer = frame.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.VERSIONS_CONTAINER_CONTENT_CLASS_NAME));
this.clearChildren(frameContentContainer);
for (let ver of versionData) {
let versionElement = this.buildVersionElement(ver);
frameContentContainer.appendChild(versionElement);
}
}
if (currProjData != null) {
let topBarTitle = frame.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.FRAME_TOP_BAR_CLASS_NAME)).querySelector('div');
topBarTitle.innerText = this.VERSION_FRAME_TITLE_FORMAT(currProjData.name);
}
this.openFrame(frame);
this.setFrameLoaded(frame);
}
/**
* Constructs a new version HTML element for the versions frame.
*
* @function buildVersionElement
*
* @param {object} data - Holds the version id, name, date for creating a version element for the versions frame.
*
* @returns {HTMLElement} The newly created version element.
*/
this.buildVersionElement = (data) => {
const mainElement = document.createElement('div');
mainElement.classList.add(this.VERSIONS_ELEMENT_MAIN_CLASS_NAME);
mainElement.setAttribute(this.formatDataTagName(this.DATA_VERSION_ID_TAG_NAME), data.v_id);
let internalElements =
`<div class="version-data nobord-t">
<div class="data-name" title="${data.name}" data-event="select_version">${data.name}</div>
<div class="version-data-right">
<div class="data-date" title="${data.date}">${data.date}</div>
</div>
</div>`;
mainElement.innerHTML = internalElements;
return mainElement;
}
/**
* Closes the settings frame.
*
* @function closeSettingsFrame
*/
this.closeSettingsFrame = () => {
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_SETTINGS_TAG_NAME;
const frame = this.getFrame(frameData);
frame.classList.remove(this.FRAME_OPEN_CLASS_NAME);
}
/**
* Closes the projects frame.
*
* @function closeProjectsFrame
*/
this.closeProjectsFrame = () => {
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_CHANGE_TAG_NAME;
const frame = this.getFrame(frameData);
frame.classList.remove(this.FRAME_OPEN_CLASS_NAME);
}
/**
* Closes the versions frame.
*
* @function closeVersionsFrame
*/
this.closeVersionsFrame = () => {
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_RUN_TAG_NAME;
const frame = this.getFrame(frameData);
frame.classList.remove(this.FRAME_OPEN_CLASS_NAME);
}
/**
* Deletes a certain project from the projects frame.
*
* @function deleteProject
*
* @param {object} data - Holds the data of the project to be deleted, so the name.
* @param {boolean} wasCurrent - Says whether the deleted project was the current project.
*/
this.deleteProject = (data, wasCurrent) => {
let projectContainer = this.queryElementsWithDataAttribute(this.DATA_PROJECT_NAME_TAG_NAME, data.name)[0];
projectContainer.remove();
if (wasCurrent) {
this.selectProject(null);
}
}
/**
* Adds a new version to the projects and versions frames.
*
* @function addVersion
*
* @param {object} versionData - Holds the data of the version to be added, so the id, name, date and so on.
* @param {object} currProjData - Holds the data of the current project, so the name.
*
* @returns {HTMLElement} The newly created version element from the projects frame.
*/
this.addVersion = (versionData, currProjData) => {
let addedVersion = versionData[0];
let removedVersion = versionData[1];
let projectElement = this.queryElementsWithDataAttribute(this.DATA_PROJECT_NAME_TAG_NAME, currProjData.name)[0];
let projectVersionContentContainer = projectElement.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.VERSIONS_CONTAINER_CONTENT_CLASS_NAME));
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_RUN_TAG_NAME;
const frame = this.getFrame(frameData);
let versionContentContainer = frame.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.VERSIONS_CONTAINER_CONTENT_CLASS_NAME));
let newProjectVersionElement = this.buildProjectVersionElement(addedVersion);
let newVersionElement = this.buildVersionElement(addedVersion);
projectVersionContentContainer.prepend(newProjectVersionElement);
versionContentContainer.prepend(newVersionElement);
this.increaseNumVersions(newProjectVersionElement, 1);
if (removedVersion != null) {
this.deleteVersionElements(removedVersion);
this.increaseNumVersions(newProjectVersionElement, -1);
}
return newProjectVersionElement;
}
/**
* Decreases the ammount of versions of a project and deletes the version.
*
* @function deleteVersion
*
* @param {object} data - Holds the data of the version to be deleted, id, name, date and so on.
* @param {HTMLElement} target - Is the targeted UI element that was clicked.
*/
this.deleteVersion = (data, target) => {
this.increaseNumVersions(target, -1);
this.deleteVersionElements(data);
}
/**
* Increases or decreases the number of versions stored in a project element on the page.
*
* @function increaseNumVersions
*
* @param {HTMLElement} target - Is the targeted UI element that was clicked.
* @param {number} num - Is the ammount, the number of versions should be increased or decreased to.
*/
this.increaseNumVersions = (target, num) => {
let versionContainer = this.getParentWithClassName(this.VERSIONS_CONTAINER_CONTENT_CLASS_NAME, target);
let currentNumVersions = getComputedStyle(versionContainer).getPropertyValue(this.NUM_VERSIONS_CSS_VARIABLE);
versionContainer.style.setProperty(this.NUM_VERSIONS_CSS_VARIABLE, parseInt(currentNumVersions) + num);
}
/**
* Deletes a certain version from the projects and versions frames.
*
* @function deleteVersionElements
*
* @param {object} data - Holds the version id, name, date for removing the version element.
*/
this.deleteVersionElements = (data) => {
let versionElements = this.queryElementsWithDataAttribute(this.DATA_VERSION_ID_TAG_NAME, data.v_id);
for (const versionElem of versionElements) {
versionElem.remove();
}
}
/**
* Favorites a certain version on the projects frame.
*
* @function favoriteVersion
*
* @param {object} data - Holds the version id, name, date of the version that is to be favorited.
*/
this.favoriteVersion = (data) => {
let versionElement = this.queryElementsWithDataAttribute(this.DATA_VERSION_ID_TAG_NAME, data.v_id)[0];
let favoriteElement = versionElement.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.GLYPHICON_CLASS_NAME));
if (this.getIsFavorited(favoriteElement)) {
favoriteElement.classList.remove(this.VERSIONS_ELEMENT_FAVORITED_CLASS_NAME);
favoriteElement.classList.add(this.VERSIONS_ELEMENT_UNFAVORITED_CLASS_NAME);
favoriteElement.title = this.VERSION_FAVORITE_TITLE;
} else {
favoriteElement.classList.remove(this.VERSIONS_ELEMENT_UNFAVORITED_CLASS_NAME);
favoriteElement.classList.add(this.VERSIONS_ELEMENT_FAVORITED_CLASS_NAME);
favoriteElement.title = this.VERSION_UNFAVORITE_TITLE;
}
}
/**
* Checks whether a given version is favorited or not.
*
* @function getIsFavorited
*
* @param {HTMLElement} data - Is the version element itself or one of its children.
*
* @returns {boolean} Whether the version element is favorited or not.
*/
this.getIsFavorited = (data) => {
let versionMain = this.getParentWithDataAttribute(this.DATA_VERSION_ID_TAG_NAME, data);
let favoriteElement = versionMain.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.GLYPHICON_CLASS_NAME));
return favoriteElement.classList.contains(this.VERSIONS_ELEMENT_FAVORITED_CLASS_NAME);
}
/**
* Retrieves the id of a version stored in the version element.
*
* @function getVersionId
*
* @param {HTMLElement} target - Is the targeted UI element that was clicked.
*
* @returns {string} The version id stored in the targeted element.
*/
this.getVersionId = (target) => {
let nameTagName = this.DATA_VERSION_ID_TAG_NAME;
let versionMain = this.getParentWithDataAttribute(nameTagName, target);
return this.getDataAttributes(versionMain)[nameTagName];
}
/**
* Retrieves the name of a project stored in the project element.
*
* @function getProjectName
*
* @param {HTMLElement} target - Is the targeted UI element that was clicked.
*
* @returns {object} The data attributes stored in the target element with their names.
*/
this.getProjectName = (target) => {
let nameTagName = this.DATA_PROJECT_NAME_TAG_NAME;
let projectContainer = this.getParentWithDataAttribute(nameTagName, target);
return this.getDataAttributes(projectContainer)[nameTagName];
}
/**
* Selects and adds a new project.
*
* @function addNewProject
*
* @param {object} data - Holds the data of the project to be added, so the name, date and its versions.
*/
this.addNewProject = (data) => {
this.selectProject(data);
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_CHANGE_TAG_NAME;
const frame = this.getFrame(frameData);
this.addProjectElement(data, frame, true);
}
/**
* Adds a new project element to the projects frame.
*
* @function addProjectElement
*
* @param {object} data - Holds the data of the project to be added, so the name, date and its versions.
* @param {HTMLElement} frame - Is the frame element the project element should be added to.
* @param {boolean} addFront - Says whether to add the project to the front or back of the projects.
*/
this.addProjectElement = (data, frame, addFront=false) => {
let frameContentContainer = frame.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.PROJECTS_FRAME_CONTENT_CLASS_NAME));
let newProjectElement = this.buildProjectElement(data);
if (data.versions != null) {
let versionContentContainer = newProjectElement.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.VERSIONS_CONTAINER_CONTENT_CLASS_NAME));
for (let ver of data.versions) {
let newVersionElement = this.buildProjectVersionElement(ver);
versionContentContainer.appendChild(newVersionElement);
}
}
if (addFront) {
frameContentContainer.prepend(newProjectElement);
} else {
frameContentContainer.appendChild(newProjectElement);
}
}
/**
* Selects a certain project to be the new current project.
*
* @function selectProject
*
* @param {object} data - Holds the data of the project to be selected, so the name, date and its versions.
*/
this.selectProject = (data) => {
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_RUN_TAG_NAME;
const frame = this.getFrame(frameData);
this.setFrameUnloaded(frame);
this.updateCurrentProject(data);
}
/**
* Updates the current project.
*
* @function updateCurrentProject
*
* @param {object} data - Holds the data of the project to be set as the new current project, so the name, date and its versions.
*/
this.updateCurrentProject = (data) => {
let currentNameContainer = document.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.CURRENT_PROJECT_CONTAINER_CLASS_NAME));
let newName = "";
let newTitle = '';
if (data != null && data.name != null && data.name.length > 0) {
newName = data.name;
newTitle = data.name;
}
currentNameContainer.innerHTML = `<div>${newName}</div>`;
currentNameContainer.title = newTitle;
}
/**
* Collapses or opens a certain project element to hide or reveal its versions.
*
* @function toggleProject
*
* @param {HTMLElement} data - Is the project element itself or one of its children.
*/
this.toggleProject = (data) => {
let projectContainer = this.getParentWithClassName(this.PROJECTS_ELEMENT_MAIN_CLASS_NAME, data);
if (projectContainer.classList.contains(this.PROJECT_TOGGLE_CLASS_NAME)) {
projectContainer.classList.remove(this.PROJECT_TOGGLE_CLASS_NAME);
} else {
projectContainer.classList.add(this.PROJECT_TOGGLE_CLASS_NAME);
}
}
/**
* Turns the debug mode on or off and updates the debug slider.
*
* @function toggleDebug
*
* @param {object} data - Holds the name of the event that was called and whether the debug was toggled or not.
*/
this.toggleDebug = (data) => {
const debugSlider = this.queryElementsWithDataAttribute(this.DATA_EVENT_TAG_NAME, data[this.DATA_EVENT_TAG_NAME])[0];
let isToggled = this.parseStringToBool(data[this.DATA_TOGGLED_TAG_NAME]);
if (isToggled) {
debugSlider.setAttribute(this.formatDataTagName(this.DATA_TOGGLED_TAG_NAME), this.BOOLEAN_FALSE_NAME);
debugSlider.classList.remove(this.DEBUG_TOGGLE_CLASS_NAME);
} else {
debugSlider.setAttribute(this.formatDataTagName(this.DATA_TOGGLED_TAG_NAME), this.BOOLEAN_TRUE_NAME);
debugSlider.classList.add(this.DEBUG_TOGGLE_CLASS_NAME);
}
}
/**
* Opens a certain frame.
*
* @function openFrame
*
* @param {HTMLElement} data - Is the frame element to open.
*/
this.openFrame = (data) => {
data.classList.add(this.FRAME_OPEN_CLASS_NAME);
}
/**
* Checks whether a certain frame has been loaded yet or needs to be fetched.
*
* @function getIsFrameLoaded
*
* @param {object} data - Is the data of the frame, so its name.
*
* @returns {boolean} Whether the frame is loaded or not.
*/
this.getIsFrameLoaded = (data) => {
const frame = this.getFrame(data);
let argsFrame = this.getDataAttributes(frame);
return this.parseStringToBool(argsFrame[this.DATA_LOADED_TAG_NAME]);
}
/**
* Retrieves a certain frame based on a given name.
*
* @function getFrame
*
* @param {object} data - Is the data of the frame, so its name.
*
* @returns {HTMLElement} The targeted frame element.
*/
this.getFrame = (data) => {
let frameName = this.DATA_FRAME_EVENT_MAP[data[this.DATA_EVENT_TAG_NAME]];
return this.queryElementsWithDataAttribute(this.DATA_FRAME_TAG_NAME, frameName)[0];
}
/**
* Sets a frame as loaded, so it doesnt need to be fetched again.
*
* @function setFrameLoaded
*
* @param {HTMLElement} data - Is the frame element to be set as loaded.
*/
this.setFrameLoaded = (data) => {
data.setAttribute(this.formatDataTagName(this.DATA_LOADED_TAG_NAME), this.BOOLEAN_TRUE_NAME);
}
/**
* Sets a frame as unloaded, so it can to be fetched again.
*
* @function setFrameUnloaded
*
* @param {HTMLElement} data - Is the frame element to be set as unloaded.
*/
this.setFrameUnloaded = (data) => {
data.setAttribute(this.formatDataTagName(this.DATA_LOADED_TAG_NAME), this.BOOLEAN_FALSE_NAME);
}
/**
* Retrieves the settings currently displayed in the settings frame.
*
* @function setFrameLoaded
*
* @returns {object} The settings values with their names.
*/
this.getSettings = () => {
let frameData = {};
frameData[this.DATA_EVENT_TAG_NAME] = this.DATA_SETTINGS_TAG_NAME;
const frame = this.getFrame(frameData);
const inputs = frame.querySelectorAll('input');
let settingsData = {};
for (const input of inputs) {
settingsData[input.name] = input.value;
}
return settingsData;
}
/**
* Updates the main console with a message.
*
* @function updateConsole
*
* @param {string} data - Is the message to be displayed on the console.
*/
this.updateConsole = (data) => {
let newConsoleMessage = this.buildConsoleMessage(data);
let consoleContainer = this.getConsoleContainer();
consoleContainer.appendChild(newConsoleMessage);
newConsoleMessage.scrollIntoView();
}
/**
* Constructs a new message element for the console.
*
* @function buildConsoleMessage
*
* @param {string} data - Is the message to be displayed on the console.
*
* @returns {HTMLElement} The newly created console message element.
*/
this.buildConsoleMessage = (data) => {
const mainElement = document.createElement('div');
mainElement.classList.add(this.CONSOLE_MESSAGE_MAIN_CLASS_NAME);
let currentTime = new Date();
let year = this.formatTimeString(currentTime.getFullYear());
let month = this.formatTimeString(currentTime.getMonth() + 1);
let day = this.formatTimeString(currentTime.getDate());
let hour = this.formatTimeString(currentTime.getHours());
let minute = this.formatTimeString(currentTime.getMinutes());
let second = this.formatTimeString(currentTime.getSeconds());
let internalElements =
`<div class="time">[${year}-${month}-${day}-${hour}:${minute}:${second}]: </div>
<div class="content">${data}</div>`;
mainElement.innerHTML = internalElements;
return mainElement;
}
/**
* Clears all messages in the console.
*
* @function clearConsole
*/
this.clearConsole = () => {
this.clearChildren(this.getConsoleContainer());
}
/**
* Format a certain time number.
*
* @function formatTimeString
*
* @param {number} num - Is the time number to format.
*
* @returns {string} The formatted time number as a string.
*/
this.formatTimeString = (num) => {
let strNum = num.toString();
if (strNum.length < 2) {
strNum = "0" + strNum;
}
return strNum;
}
/**
* Retrieves the container that holds all console messages.
*
* @function getConsoleContainer
*
* @returns {HTMLElement} The container that holds the console messages.
*/
this.getConsoleContainer = () => {
return document.querySelector(this.CLASS_NAME_QUERY_FORMAT(this.CONSOLE_CONTAINER_CLASS_NAME));
}
/**
* Adds an error or message to the queue and displays it if no error is currently being displayed.
*
* @function addError
*
* @param {string} msg - The message to display.
* @param {boolean} isError - Whether the message is an error or just information.
*/
this.addError = (msg, isError=true) => {
this.errorQueue.push({ "message": msg, "isError": isError });
if (this.timers[this.ERROR_TIMEOUT_NAME] == null
&& this.timers[this.ERROR_ANIMATION_NAME] == null) this.showError();
}
/**
* Displays the next error or message in the queue on the page.
*
* @function showError
*/
this.showError = () => {
if (this.errorQueue.length == 0) return;
let error = this.errorQueue.shift();
let errorMessage = error.message;
const errorElement = document.createElement('div');
errorElement.classList.add(this.ERRORS_CLASS_NAME);
errorElement.innerText = errorMessage;
errorElement.style.setProperty(this.ANIM_LENGTH_CSS_VARIABLE, `${this.ERROR_ANIMATION_DURATION}ms`);
if (!error.isError) {
errorElement.classList.add(this.ERRORS_INFO_CLASS_NAME);
}
document.body.appendChild(errorElement);
this.timers[this.ERROR_TIMEOUT_NAME] = setTimeout(() => {
errorElement.classList.add(this.ERRORS_OUT_CLASS_NAME);
this.timers[this.ERROR_ANIMATION_NAME] = setTimeout(() => {
errorElement.remove();
this.timers[this.ERROR_ANIMATION_NAME] = null;
if (this.errorQueue.length != 0) this.showError();
}, this.ERROR_ANIMATION_DURATION);
this.timers[this.ERROR_TIMEOUT_NAME] = null;
}, this.ERROR_TIMEOUT_DURATION);
}
/**
* Formats a data tag name to match format "data-" + name
*
* @function formatDataTagName
*
* @param {string} name - The data tag name.
*
* @returns {string} The formatted data tag name.
*/
this.formatDataTagName = (name) => {
return this.DATA_TAG_NAME + name;
}
/**
* Parses a string to a boolean.
*
* @function parseStringToBool
*
* @param {string} string - The string to parse.
*
* @returns {boolean} The parsed string as a boolean.
*/
this.parseStringToBool = (string) => {
return (string == this.BOOLEAN_TRUE_NAME);
}
/**
* Retrieves all data attributes of an HTML element.
*
* @function getDataAttributes
*
* @param {HTMLElement} element - The element to retrieve the attributes of.
*
* @returns {object} The data attributes with their names.
*/
this.getDataAttributes = (element) => {
let attributes = element.attributes;
let dataAttributes = {};
for (let attribute of attributes) {
if (attribute.name.startsWith(this.DATA_TAG_NAME)) {
let dataName = attribute.name.substring(this.DATA_TAG_NAME.length);
dataAttributes[dataName] = attribute.value;
}
}
return dataAttributes;
}
/**
* Retrieves all HTML elements with a certain data tag with a certain value.
*
* @function queryElementsWithDataAttribute
*
* @param {string} attrName - The data attribute to query for.
* @param {string} attrValue - The value the attribute.
*
* @returns {Array<HTMLElement>} The found HTML elements.
*/
this.queryElementsWithDataAttribute = (attrName, attrValue=null) => {
const elements = document.querySelectorAll(this.DATA_TAG_QUERY_FORMAT(attrName));
if (attrValue == null) return elements;
let targetElements = [];
for (const element of elements) {
if (attrValue == null && element.hasAttribute(this.formatDataTagName(attrName))) {
targetElements.push(element);
} else if (element.getAttribute(this.formatDataTagName(attrName)) == attrValue) {
targetElements.push(element);
}
}
return targetElements;
}
/**
* Retrieves a parent HTML element which has a certain data attribute.
*
* @function getParentWithDataAttribute
*
* @param {string} attrName - The data attribute to query for.
* @param {HTMLElement} element - The HTML element to start at.
*
* @returns {HTMLElement} The found HTML parent.
*/
this.getParentWithDataAttribute = (attrName, element) => {
let parent = element.parentElement;
while (parent != null) {
if (parent.getAttribute(this.formatDataTagName(attrName)) != null) return parent;
parent = parent.parentElement;
}
return null;
}
/**
* Retrieves a parent HTML element which has a certain class.
*
* @function getParentWithClassName
*
* @param {string} name - The class name attribute to query for.
* @param {HTMLElement} element - The HTML element to start at.
*
* @returns {HTMLElement} The found HTML parent.
*/
this.getParentWithClassName = (name, element) => {
let parent = element.parentElement;
while (parent != null) {
if (parent.classList.contains(name)) return parent;
parent = parent.parentElement;
}
return null;
}
/**
* Clears all children of a certain HTML element.
*
* @function clearChildren
*
* @param {HTMLElement} element - The element to clear.
*/
this.clearChildren = (element) => {
while (element.firstChild) {
element.removeChild(element.lastChild);
}
}
/**
* Requests a file from the user.
*
* @function requestFile
*
* @param {Function} callback - The callback to call after execution.
*/
this.requestFile = (callback) => {
const input = document.createElement('input');
input.type = 'file';
input.name = 'file';
input.accept = '.xml';
input.onchange = () => {
callback(input);
};
input.oncancel = () => {
callback(null);
}
input.click();
}
/**
* Requests input from the user based off a statement.
*
* @function requestInput
*
* @param {Function} callback - The callback to call after execution.
* @param {string} request - The message to convey.
*/
this.requestInput = (callback, request) => {
let input = prompt(request);
callback(input);
}
/**
* Requests the user to confirm a statement.
*
* @function requestConfirm
*
* @param {Function} callback - The callback to call after execution.
* @param {string} request - The message to convey.
*/
this.requestConfirm = (callback, request) => {
let confirmed = confirm(request);
callback(confirmed);
}
}
}
/**
* Class for holding the attributes of a certain setting.
*
* @class Setting
*/
class Setting {
/**
* Constructs a new Setting.
*
* @constructor
*/
constructor(name, type, valName, title, min, max, val=null) {
this.name = name;
this.type = type;
this.valName = valName;
this.title = title;
this.min = min;
this.max = max;
this.val = val;
}
}
export default PageHandler;