File: D:/HostingSpaces/brameda/brameda.nl/resources/js/kms/attributes/componentAreaManager.js
/**
* The main class to manage components
*/
class ComponentAreaManager {
/**
* @param {HTMLDivElement} wrapper
* @param {ComponentManagerApiController} componentManagerApiController
*/
constructor(wrapper, componentManagerApiController) {
this._componentAreaWrapperElement = wrapper;
this._componentManagerApiController = componentManagerApiController;
if (this._componentAreaWrapperElement === undefined || this._componentAreaWrapperElement.tagName !== "DIV") {
console.error('ComponentAreaManager:constructor Did not get the expected div element that represents the components wrapper.');
return;
}
if (this._componentManagerApiController === undefined) {
console.error('ComponentAreaManager:constructor Did not get the expected _componentManagerApiController.');
return;
}
this._componentAreaAttributeKey = this._componentAreaWrapperElement.id;
//Get the input field for the components attribute that will hold all data for the components attribute values.
this._masterInput = this._componentAreaWrapperElement.querySelector('input[name="' + this._componentAreaAttributeKey + '"]');
if (!this._masterInput) {
console.error('ComponentAreaManager:constructor Did not find an hidden input with name "' + this._componentAreaAttributeKey + '" inside of the wrapper.');
return;
}
this._componentAreaContainer = this._componentAreaWrapperElement.querySelector('.js-components');
if (!this._componentAreaContainer) {
console.error('ComponentAreaManager:constructor the wrapper did not contain a div with class components.');
return;
}
if (!'componentAttributeFieldsRegex' in this._componentAreaWrapperElement.dataset) {
console.error('The wrapper element must, but did not have a componentAttributeFieldsRegex dataset value');
return;
}
this._componentAttributeFieldsRegex = this._componentAreaWrapperElement.dataset.componentAttributeFieldsRegex;
this._saveVersion = '0.9.1';
this._addComponentButtonElements = [];
this._eventMap = {};
this._loaded = false;
this._setAddComponentButtonElements();
this._loadComponentsUsingSaveStateData()
}
/**
* Sets up the add component buttons by finding them in the wrapper and adding listeners to them
*/
_setAddComponentButtonElements() {
this._addComponentButtonElements = this._componentAreaWrapperElement.querySelectorAll(".js-add-component");
let addComponentButtonsElementsLength = this._addComponentButtonElements.length;
for(let index = 0; index < addComponentButtonsElementsLength; index++)
{
let currentAddComponentButtonElement = this._addComponentButtonElements[index];
currentAddComponentButtonElement.addEventListener('click', this._addComponentButtonClicked.bind(this))
}
}
/**
* Click handler for add component buttons
*
* @param {MouseEvent} mouseEvent
*/
_addComponentButtonClicked(mouseEvent)
{
let self = this;
mouseEvent.preventDefault();
let button = mouseEvent.target;
if(!'component' in button.dataset) {
console.error('ComponentAreaManager: One of the add component buttons was missing the component dataset attribute. Please set it to the name of a valid component');
return;
}
let componentSaveState = new ComponentSaveState();
componentSaveState.id = parseInt(self._componentAreaWrapperElement.dataset.timesAddedComponent) * -1;
componentSaveState.componentTypeId = parseInt(button.dataset.componentType); //The id of the component to resolve
componentSaveState.sortOrder = 0;
this._componentManagerApiController.getComponentHtmlElement(
componentSaveState,
this._componentAreaWrapperElement.id).then(function(data) {
self.incrementTimesComponentAddedCounter();
let componentHtmlElement = data.element;
self._addComponent(componentHtmlElement);
self._updateMasterInput();
});
}
/**
* Adds new components based on the component area savestate from another component area manager
*
* @param {ComponentAreaSaveState} originalComponentAreaSaveState
*/
addNewComponentsBasedOnComponentAreaSaveState(originalComponentAreaSaveState) {
if(!(originalComponentAreaSaveState instanceof ComponentAreaSaveState)) {
console.error('ComponentAreaManager: The given "originalComponentAreaSaveState" parameter was not an instance of ComponentAreaSaveState. Not adding its components.');
}
let self = this;
let componentsCount = originalComponentAreaSaveState.componentsCount;
for(let index = 0; index < componentsCount; index++) {
//Get the component save state and its type to make the copy
let originalComponentSaveState = originalComponentAreaSaveState.getComponentSavestateAt(index);
//Create a new component save state based on the original
let componentSaveState = new ComponentSaveState();
componentSaveState.id = parseInt(self._componentAreaWrapperElement.dataset.timesAddedComponent) * -1;
componentSaveState.componentTypeId = originalComponentSaveState.componentTypeId; //The id of the component to resolve
componentSaveState.sortOrder = 0;
//Add the components after loading it from the api
this._componentManagerApiController.getComponentHtmlElement(
componentSaveState,
this._componentAreaWrapperElement.id).
then(function(data) {
self.incrementTimesComponentAddedCounter();
let componentHtmlElement = data.element;
self._addComponent(componentHtmlElement);
self._updateMasterInput();
});
}
}
/**
* Increments the counter that keeps track of how often a component was added before saving.
*/
incrementTimesComponentAddedCounter()
{
return this._componentAreaWrapperElement.dataset.timesAddedComponent = (parseInt(this._componentAreaWrapperElement.dataset.timesAddedComponent) + 1).toString();
}
/**
* Adds a component to the component wrapper and add listeners to it to detect changes
* {HTMLElement} componentHtmlElement
* {Number} position
*/
_addComponent(componentHtmlElement)
{
let self = this;
//Add listeners to the attributes to detect changes and trigger a master input update
let foundComponentInputsModel = this._findAttributeDataInputs(componentHtmlElement);
let inputsCount = foundComponentInputsModel.inputElements.length;
for(let index = 0; index < inputsCount; index++) {
let input = foundComponentInputsModel.inputElements[index];
let debouncedKeyFunction = self._debounce(self._attributeInputChanged.bind(self), 500);
input.addEventListener('change', self._attributeInputChanged.bind(self));
input.addEventListener('blur', self._attributeInputChanged.bind(self));
input.addEventListener('keydown', debouncedKeyFunction.bind(self));
}
//Add listeners to componentable attributes to detect changes and trigger a master input update
let componentableComponentInputs = this._findComponentableInputs(componentHtmlElement);
let componentableInputsCount = foundComponentInputsModel.inputElements.length;
for(let index = 0; index < componentableInputsCount; index++) {
let input = componentableComponentInputs.inputElements[index];
if(!input) continue;
let debouncedKeyFunction = self._debounce(self._attributeInputChanged.bind(self), 500);
input.addEventListener('change', self._attributeInputChanged.bind(self));
input.addEventListener('blur', self._attributeInputChanged.bind(self));
input.addEventListener('keydown', debouncedKeyFunction.bind(self));
}
//Make the delete button work
let deleteButton = componentHtmlElement.querySelector('.js-component-delete');
deleteButton.addEventListener('click', function(event) {
event.preventDefault();
componentHtmlElement.parentElement.removeChild(componentHtmlElement);
self._updateMasterInput();
});
//Make the move down button work
let downButton = componentHtmlElement.querySelector('.js-component-move-up');
downButton.addEventListener('click', function(event) {
event.preventDefault();
if(componentHtmlElement.previousSibling) {
componentHtmlElement.parentElement.insertBefore(componentHtmlElement, componentHtmlElement.previousSibling);
}
self._updateMasterInput();
});
//Make the move up button work
let upButton = componentHtmlElement.querySelector('.js-component-move-down');
upButton.addEventListener('click', function(event) {
event.preventDefault();
if(componentHtmlElement.nextSibling && componentHtmlElement.nextSibling.nextSibling) {
componentHtmlElement.parentElement.insertBefore(componentHtmlElement, componentHtmlElement.nextSibling.nextSibling);
} else {
componentHtmlElement.parentElement.appendChild(componentHtmlElement);
}
self._updateMasterInput();
});
//Put some of the component's attributes in tabs when needed
this._putComponentAttributesInTabs(componentHtmlElement);
//Add the component
this._componentAreaContainer.appendChild(componentHtmlElement);
this.triggerEvent('componentAdded', [componentHtmlElement, this._loaded]);
}
/**
* Triggered when an attribute input was changed.
*
* @param event
*/
_attributeInputChanged(event)
{
this._updateMasterInput();
}
/**
* updates the master input which holds all attribute values
*/
_updateMasterInput()
{
let self = this;
let componentAreaSaveState = this.getComponentAreaSaveState();
self._masterInput.value = JSON.stringify(componentAreaSaveState);
}
_putComponentAttributesInTabs(componentElement) {
//Validate that we did get a component element, or show error and return;
let attributeWrapper = componentElement.querySelector('.attributes');
if (!attributeWrapper) {
console.error('Expected to get a "componentElement" but did not. It did not contain a div with class "attributes" in it.');
return;
}
//Retrieve the attributes in the component and store the length
let attributes = attributeWrapper.children;
let attributesCount = attributes.length;
//Create tab configuration that we use to build the tabs later on. Each property represents a tab name.
//each property value is an array of attribute elements that belong to the tab name.
let tabsConfiguration = {};
for (let i = 0; i < attributesCount; i++) {
let attributeElement = attributes[i];
if ("tab" in attributeElement.dataset) {
if (!tabsConfiguration.hasOwnProperty(attributeElement.dataset.tab)) tabsConfiguration[attributeElement.dataset.tab] = [];
tabsConfiguration[attributeElement.dataset.tab].push(attributeElement);
}
}
//Now build tabs if the tabsConfiguration object has children
let tabsCount = Object.keys(tabsConfiguration).length;
if (tabsCount > 0) {
//First build the tab "framework" html and add it to the attributes container
let domParser = new DOMParser();
let document = domParser.parseFromString(
'<div class="component-tab">' + //Note check kms.js. It uses the class name to make the tabs work
' <ul class="component-tab__list">' +
' </ul>' +
' <div class="component-tab__container">' +
' </div>' +
'</div>'
, 'text/html');
let componentTab = document.body.firstChild;
attributeWrapper.appendChild(componentTab);
//Build and add tabs. And put the attributes inside the correct ones
let index = 0;
for (let tabName in tabsConfiguration) {
if (!tabsConfiguration.hasOwnProperty(tabName)) continue; //Skip "tabName" if it is a property on the objects prototype
//Create the tab switch button to switch to the first tab
document = domParser.parseFromString(
'<li class="component-tab__list-item">' +
' <a class="component-tab__button">' + tabName + '</a>' +
'</li>'
, 'text/html');
let tabSwitchButton = document.body.firstChild;
//Add the tabSwitchButton to the tab list
let tabList = componentTab.getElementsByClassName('component-tab__list')[0];
tabList.appendChild(tabSwitchButton);
//Create the content holder
document = domParser.parseFromString('<div class="component-tab__content"></div>', 'text/html');
let tabContent = document.body.firstChild;
//Add the content holder in the componentTab container
let container = componentTab.getElementsByClassName('component-tab__container')[0];
container.appendChild(tabContent);
//Put the attributes in the tab content holder
let attributesCount = tabsConfiguration[tabName].length;
for (let i = 0; i < attributesCount; i++) {
tabContent.appendChild(tabsConfiguration[tabName][i]);
}
//Make the first tab active
if (index === 0) {
tabSwitchButton.classList.add('active');
tabContent.classList.add('active')
}
//Make the button switch to the correct tab by using the is-active classes
tabSwitchButton.addEventListener('click', function (clickEvent) {
clickEvent.preventDefault();
let activeTabIndex = 0;
for (let tabSwitchButtonIndex = 0; tabSwitchButtonIndex < tabsCount; tabSwitchButtonIndex++) {
let currentTabSwitchButton = tabList.children[tabSwitchButtonIndex];
currentTabSwitchButton.classList.remove('active');
if (currentTabSwitchButton === tabSwitchButton) {
currentTabSwitchButton.classList.add('active');
activeTabIndex = tabSwitchButtonIndex;
}
}
for (let tabContentIndex = 0; tabContentIndex < tabsCount; tabContentIndex++) {
let currentTabContent = container.children[tabContentIndex];
currentTabContent.classList.remove('active');
if (tabContentIndex === activeTabIndex) currentTabContent.classList.add('active');
}
});
index++;
}
}
}
/**
* Does search inputs, textarea's and select's in the given component div element or else inside of
* the componentsWrapper and checks if they match against the regex that identifies them as attribute data fields
*
* @param {HTMLDivElement|null} componentElement
* @param {ComponentSaveState} componentSavestate to also pass when resolved
* @return {FoundComponentInputsModel}
*/
_findAttributeDataInputs(componentElement, componentSavestate = undefined)
{
let dataInputs = [];
let attributesContainer = componentElement.getElementsByClassName('attributes')[0];
let possibleDataInputs = attributesContainer.querySelectorAll('input, textarea, select');
let inputsQuantity = possibleDataInputs.length;
for (let index = 0; index < inputsQuantity; index++) {
let input = possibleDataInputs[index];
if(input.name !== undefined && input.name.match(new RegExp(this._componentAttributeFieldsRegex))) {
dataInputs.push(input)
}
}
let model = new FoundComponentInputsModel();
model.inputElements = dataInputs;
model.componentElement = componentElement;
model.componentSaveState = componentSavestate;
return model;
}
/**
* Gets the value of the select / autocomplete input for selecting a model you want to link the component to.
* Notice that the code is pretty much the same as _findAttributeDataInputs.
*
* @param {HTMLDivElement|null} componentElement
* @param {ComponentSaveState} componentSavestate to also pass when resolved
* @return {FoundComponentInputsModel}
*
* @private
*/
_findComponentableInputs(componentElement, componentSavestate = undefined) {
let dataInputs = [];
let componentableSelectorsContainer = componentElement.querySelector('.componentableSelectors');
if(!componentableSelectorsContainer) return new FoundComponentInputsModel();
let possibleDataInputs = componentableSelectorsContainer.querySelectorAll('input, textarea, select');
let inputsQuantity = possibleDataInputs.length;
for (let index = 0; index < inputsQuantity; index++) {
let input = possibleDataInputs[index];
if(input.name !== undefined && input.name.match(new RegExp(this._componentAttributeFieldsRegex))) {
dataInputs.push(input);
}
}
let model = new FoundComponentInputsModel();
model.inputElements = dataInputs;
model.componentElement = componentElement;
model.componentSaveState = componentSavestate;
return model;
}
/**
* @return {NodeListOf<HTMLElementTagNameMap[HTMLDivElement]>}
*/
getComponentElements()
{
return this._componentAreaContainer.querySelectorAll('.c-component');
}
/**
* Returns the component area wrapper div
*
* @return {HTMLElement}
*/
getWrapperElement()
{
return this._componentAreaWrapperElement;
}
/**
* Returns a ComponentAreaSaveState
* And contains a save version to allow non breaking updates in the future.
*
* @return ComponentAreaSaveState
*/
getComponentAreaSaveState()
{
let saveState = new ComponentAreaSaveState();
//Loop over all components.
let components = this.getComponentElements();
let componentsCount = components.length;
for(let componentNumber = 0; componentNumber < componentsCount; componentNumber++)
{
//Loop over all attributes in the components...
let currentComponent = components[componentNumber];
// findAttributeDataInputsPromises.push(this._findAttributeDataInputs(currentComponent));
let foundComponentInputsModel = this._findAttributeDataInputs(currentComponent);
let foundComponentsInputModelForLinkable = this._findComponentableInputs(currentComponent);
//Create an array of objects. The objects properties are field names and the objects values are the values of those field names
let attributeInputData = {};
let inputsCounts = foundComponentInputsModel.inputElements.length;
for(let currentInputNumber = 0; currentInputNumber < inputsCounts; currentInputNumber++) {
let currentInput = foundComponentInputsModel.inputElements[currentInputNumber];
if(currentInput.tagName === "INPUT" && currentInput.type === "file") {
//Ignore file input
} else {
attributeInputData[currentInput.name] = currentInput.value;
}
}
//Create an array containing one object that has a property name that corresponds to an inputs field name and the value of that property is the value of that input.
let linkableInputObject = {};
let linkableInputsCount = foundComponentsInputModelForLinkable.inputElements.length;
for(let currentInputNumber = 0; currentInputNumber < linkableInputsCount; currentInputNumber++) {
let currentInput = foundComponentsInputModelForLinkable.inputElements[currentInputNumber];
if(currentInput.tagName === "INPUT" && currentInput.type === "file") {
//Ignore file input
} else {
linkableInputObject[currentInput.name] = currentInput.value;
}
}
let componentSaveState = new ComponentSaveState();
componentSaveState.id = Number(foundComponentInputsModel.componentElement.dataset.id);
componentSaveState.componentTypeId = Number(foundComponentInputsModel.componentElement.dataset.componentTypeId);
componentSaveState.data = attributeInputData;
componentSaveState.version = this._saveVersion;
componentSaveState.sortOrder = Number(componentNumber);
componentSaveState.componentableData = linkableInputObject;
saveState.addComponentSaveState(componentSaveState);
}
return saveState;
}
/**
* Uses the master input to rebuild the saved componentarea with components with their data
*/
_loadComponentsUsingSaveStateData() {
let self = this;
//Retrieve and decode the save state data
let savedData = this._masterInput.value;
if(!savedData) return false;
let saveState = ComponentAreaSaveState.fromJsonString(savedData);
if(!saveState) return false;
let getComponentHtmlElementPromises = [];
let savedComponentsCount = saveState.componentsCount;
for(let currentComponentNumber = 0; currentComponentNumber < savedComponentsCount; currentComponentNumber++) {
let currentComponentSaveState = saveState.getComponentSavestateAt(currentComponentNumber);
getComponentHtmlElementPromises.push(this._componentManagerApiController.getComponentHtmlElement(currentComponentSaveState, this._componentAreaWrapperElement.id));
}
Promise.all(getComponentHtmlElementPromises).then(function(componentDatas) {
let componentDatasCount = componentDatas.length;
//Rebuild structure
for (let componentNumber = 0; componentNumber < componentDatasCount; componentNumber++) {
let componentData = componentDatas[componentNumber];
let componentHTMLElement = componentData.element;
self._addComponent(componentHTMLElement);
}
//Loop over the same componentHTMLElements and fill their attribute inputs
for (let currentComponentNumber = 0; currentComponentNumber < componentDatasCount; currentComponentNumber++) {
let currentComponentData = componentDatas[currentComponentNumber];
let currentComponentSavestate = currentComponentData.componentSavestate;
let attributeData = currentComponentSavestate.data;
for (let attributeKeyName in attributeData) {
if(!attributeData.hasOwnProperty(attributeKeyName)) continue;
if (currentComponentSavestate.data.hasOwnProperty(attributeKeyName)) {
let attributeFields = self._componentAreaWrapperElement.querySelectorAll('[name="'+attributeKeyName+'"]');
let nAttributeFields = attributeFields.length;
if(nAttributeFields > 0) {
for (let index = 0; index < nAttributeFields; index++) {
attributeFields[index].value = attributeData[attributeKeyName]
}
} else {
console.error('Could not load data for field: '+attributeKeyName+' since it could not be found. Skipping it');
}
}
}
let componentableData = currentComponentSavestate.componentableData;
for (let attributeKeyName in componentableData) {
if(!componentableData.hasOwnProperty(attributeKeyName)) continue;
if (currentComponentSavestate.componentableData.hasOwnProperty(attributeKeyName)) {
let attributeFields = self._componentAreaWrapperElement.querySelectorAll('[name="'+attributeKeyName+'"]');
let nAttributeFields = attributeFields.length;
if(nAttributeFields > 0) {
for (let index = 0; index < nAttributeFields; index++) {
attributeFields[index].value = componentableData[attributeKeyName]
}
} else {
console.error('Could not load data for field: '+attributeKeyName+' since it could not be found. Skipping it');
}
}
}
}
//sessionStorage.removeItem("componentScrollPosition");
if(isset(sessionStorage.getItem("componentScrollPosition"))) {
self._componentAreaWrapperElement.parentElement.parentElement.parentElement.scrollTop = sessionStorage.getItem("componentScrollPosition");
}
self.loaded = true;
});
}
/**
* Registers a event to a callback and returns the a reference to the event handler for that specific event
*
* @param event (string)
* @param callback (callable)
*/
on(event, callback)
{
if(!this._eventMap.hasOwnProperty(event)) this._eventMap[event] = [];
return this._eventMap[event].push(callback);
}
/**
* Call event callbacks
*
* @param event (string)
* @param eventArgs (array) an array of arguments to pass to the callback
*/
triggerEvent(event, eventArgs) {
if(!this._eventMap.hasOwnProperty(event)) return;
let nEvents = this._eventMap[event].length;
for(let index = 0; index < nEvents; index++)
{
let callback = this._eventMap[event][index];
if(eventArgs && eventArgs.length > 0) {
callback.apply(this, eventArgs)
} else {
callback.call(this);
}
}
}
/**
* Returns a function, that, as long as it continues to be invoked, will not
* be triggered. The function will be called after it stops being called for
* N milliseconds. If `immediate` is passed, trigger the function on the
* leading edge, instead of the trailing.
*
* @param func
* @param wait
* @param immediate
* @return {Function}
*/
_debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
}
/**
* Used by the ComponentAreaManager to talk to the backend
*/
class ComponentManagerApiController {
constructor()
{
this.domParser = new DOMParser();
}
/**
* @param {ComponentSaveState} componentSavestate
* @param {string} componentAreaAttributeKey
* @return {Promise} that resolves with a HTMLDivElement that represents the newly requested component with it's attributes in it
*/
getComponentHtmlElement(componentSavestate, componentAreaAttributeKey)
{
let self = this;
return new Promise(function(resolve, reject) {
let postData = {
componentAreaAttributeKey: componentAreaAttributeKey,
componentSaveState: JSON.stringify(componentSavestate)
};
axios.post('/api/dynamic/component/resolve', postData).then(function(response) {
let toResolve = {
element: self._stringToHtmlElement(response.data),
componentSavestate: componentSavestate
};
resolve(toResolve);
}).catch(function(error) {
console.error('ComponentManagerApiController:getComponentHtmlElement Could not get HTML of a component because of the following error:',error);
reject(error);
})
});
}
getComponentAreaRegexForComponent(componentKey)
{
if(componentKey === undefined) {
console.error('ComponentManagerApiController:getComponentAreaRegexForComponent Could not get the regex for an undefined componentKey. Please specify it.');
return;
}
return new Promise(function(resolve, reject) {
axios.post('/api/dynamic/component/attribute_regex', {
componentKey: componentKey,
}).then(function(response) {
resolve(response.data);
}).catch(function(error) {
console.error('ComponentManagerApiController:getComponentAreaRegexForComponent Could not get the regex to find inputs for a component because of the following error:',error);
reject(error);
})
});
}
/**
* @return {Promise} that resolves with an array of component type names. Or rejects with the already console logged error.
*/
getAllComponentTypeNames()
{
return new Promise(function(resolve, reject) {
axios.get('/api/dynamic/component').then(function(response) {
return response.data; //Must be an array full of component type names
}).catch(function(error) {
console.error('ComponentManagerApiController:getAllComponentTypeNames Could not the names of all available componentTypes because of an error:', error);
reject(error);
})
});
}
/**
* Converts a string to an HTMLElement
*
* @param {string} data
* @return {HTMLElement}
*/
_stringToHtmlElement(data)
{
let document = this.domParser.parseFromString(data, "text/html");
return document.body.firstElementChild;
}
}
/**
* Holds information about a component HtmlElement, its input elements
* and the save state for that component.
*/
class FoundComponentInputsModel {
constructor() {
this._inputElements = [];
this._componentElement = null;
this._componentSaveState = null;
}
/**
* @return {null|Array}
*/
get inputElements() {
return this._inputElements;
}
/**
* @param {NodeList} value
*/
set inputElements(value) {
if(typeof value !== 'object') { //Pssst...array primitives don't exist. Don't tell anyone :)
console.error('ComponentSaveState: Did not set inputElements since the parameter was not an array. Actual: ', value)
return;
}
this._inputElements = value;
}
/**
* @return {null|HTMLDivElement}
*/
get componentElement() {
return this._componentElement;
}
/**
* @param {HTMLDivElement} value
*/
set componentElement(value) {
if(!value instanceof HTMLDivElement) {
console.error('ComponentSaveState: Did not set componentElement since the parameter was not an HTMLDivElement.')
return;
}
this._componentElement = value;
}
/**
* @return {null|ComponentSaveState}
*/
get componentSaveState() {
return this._componentSaveState;
}
/**
* @param {null|ComponentSaveState} value
*/
set componentSaveState(value) {
if(!value instanceof ComponentSaveState) {
console.error('ComponentSaveState: Did not set componentSaveState since the parameter was not ComponentSaveState.')
return;
}
this._componentSaveState = value;
}
}
/**
* Represents data that can be saved and loaded for a Component.
* Always used in combination with ComponentAreaSaveState
*/
class ComponentSaveState
{
constructor()
{
this._version = '0.9.1';
this._id = null;
this._componentTypeId = null;
this._data = {};
this._componentableData = {};
this._sortOrder = null;
this.toJSON = this._toJson.bind(this);
}
_toJson()
{
return {
id: this._id,
version: this._version,
componentTypeId: this._componentTypeId,
data: this._data,
componentableData: this._componentableData,
sortOrder: this._sortOrder,
}
}
static fromObject(jsonObject)
{
//Validate the object
if(
(!jsonObject.hasOwnProperty('id') || typeof jsonObject.id !== 'number') ||
(!jsonObject.hasOwnProperty('componentTypeId') || typeof jsonObject.componentTypeId !== 'number') ||
(!jsonObject.hasOwnProperty('version') || typeof jsonObject.version !== 'string') ||
(!jsonObject.hasOwnProperty('sortOrder') || typeof jsonObject.sortOrder !== 'number') ||
(!jsonObject.hasOwnProperty('data') || typeof jsonObject.data !== 'object') ||
(!jsonObject.hasOwnProperty('componentableData') || typeof jsonObject.data !== 'object')
) {
console.error('Could not create a ComponentSaveState instance from an object since that object did not contain all of the following properties of the correct types: id (number), componentTypeId (number), version (string), sortOrder (number), data (array object). Actual: ', jsonObject)
return;
}
//Create a new instance of the current class that we return later on.
let componentSaveState = new this;
componentSaveState.id = jsonObject.id;
componentSaveState.componentTypeId = jsonObject.componentTypeId;
componentSaveState.version = jsonObject.version;
componentSaveState.sortOrder = jsonObject.sortOrder;
componentSaveState.data = jsonObject.data;
componentSaveState.componentableData = jsonObject.componentableData;
return componentSaveState;
}
get version() {
return this._version;
}
set version(value) {
if(typeof value !== 'string') {
console.error('ComponentSaveState: Did not set version since the parameter was not a string. Actual: ', value);
return;
}
this._version = value;
}
get id() {
return this._id;
}
set id(value) {
if(value === "" || typeof value !== 'number') {
console.error('ComponentSaveState: Did not set the id since the parameter was not a number. Actual: ', typeof value, 1);
return;
}
this._id = value;
}
get componentTypeId() {
return this._componentTypeId;
}
set componentTypeId(value) {
if(value === "" || typeof value !== 'number') {
console.error('ComponentSaveState: Did not set componentTypeId since the parameter was not a number. Actual: ', typeof value, 1);
return;
}
this._componentTypeId = value;
}
get data() {
return this._data;
}
set data(value) {
if(typeof value !== 'object') {
console.error('ComponentSaveState: Did not set data since the parameter was not an object. Actual: ', value);
return;
}
this._data = value;
}
get sortOrder() {
return this._sortOrder;
}
set sortOrder(value) {
if(value === "" || typeof value !== 'number') {
console.error('ComponentSaveState: Did not set sortOrder since the parameter was not a number. Actual: ', value);
}
this._sortOrder = value;
}
get componentableData() {
return this._componentableData;
}
set componentableData(value) {
if(typeof value !== 'object') {
console.error('ComponentSaveState: Did not set componentableData since the parameter was not an object. Actual: ', value);
return;
}
this._componentableData = value;
}
}
/**
* Represents data that can be saved and loaded for a ComponentArea.
* This is the actual data container for the backend.
*/
class ComponentAreaSaveState {
constructor()
{
this.toJSON = this._toJson.bind(this);
this._componentSaveStates = [];
}
/**
* @param {ComponentSaveState} value
*/
addComponentSaveState(value) {
if(!value instanceof ComponentSaveState) {
console.error('ComponentAreaSaveState: The passed "componentSaveState" was not an instance of ComponentSaveState. Actual: ', value);
return;
}
this._componentSaveStates.push(value);
}
/**
*
* @param index
* @return {ComponentSaveState}
*/
getComponentSavestateAt(index)
{
return this._componentSaveStates[index];
}
/**
*
* @return {Array}
* @private
*/
_toJson()
{
return this._componentSaveStates;
}
/**
*
* @param {string} json
* @return {ComponentAreaSaveState}
*/
static fromJsonString(json) {
let jsonObject = null;
try {
jsonObject = JSON.parse(json);
} catch (e) {
console.error('ComponentAreaSaveState: The given jsonString does not represent ComponentAreaSaveState since the json string was not a valid json')
return;
}
//Create a new instance of the current class that we return later on.
let componentAreaSaveState = new this;
//Create components from the saved data and put it in the new ComponentAreaSaveState instance
let componentsCount = jsonObject.length;
for(let index = 0; index < componentsCount; index++)
{
let currentComponentSaveStateObject = jsonObject[index];
let currentComponentSaveState = ComponentSaveState.fromObject(currentComponentSaveStateObject);
componentAreaSaveState.addComponentSaveState(currentComponentSaveState);
}
return componentAreaSaveState;
}
/**
* Returns the amount of components
*
* @return {number}
*/
get componentsCount() {
return this._componentSaveStates.length;
}
}