Your IP : 18.119.213.129


Current Path : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/ui/vue/vuex/src/builder/
Upload File :
Current File : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/ui/vue/vuex/src/builder/model.js

/**
 * Bitrix Vuex wrapper
 * Interface Vuex model (Vuex builder model)
 *
 * @package bitrix
 * @subpackage ui
 * @copyright 2001-2019 Bitrix
 */

import {BitrixVue} from "ui.vue";
import {Vuex} from "../vuex.js";
import {VuexBuilder} from "./builder.js";
import {VuexBuilderDatabaseIndexedDB} from "./database/indexeddb.js";
import {VuexBuilderDatabaseLocalStorage} from "./database/localstorage.js";
import {VuexBuilderDatabaseJnSharedStorage} from "./database/jnsharedstorage.js";

export class VuexBuilderModel
{
	/**
	 * Create new instance of model.
	 *
	 * @returns {VuexBuilderModel}
	 */
	static create()
	{
		return new this;
	}

	/**
	 * Get name of model
	 *
	 * @override
	 *
	 * @returns {String}
	 */
	getName()
	{
		return '';
	}

	/**
	 * Get default state
	 *
 	 * @override
	 *
	 * @returns {Object}
	 */
	getState()
	{
		return {};
	}

	/**
	 * Get default element state for models with collection.
	 *
 	 * @override
	 *
	 * @returns {Object}
	 */
	getElementState(params = {}): object
	{
		return {};
	}

	/**
	 * Get object containing fields to exclude during the save to database.
	 *
 	 * @override
	 *
	 * @returns {Object}
	 */
	getStateSaveException()
	{
		return undefined;
	}

	/**
	 * Get getters
	 *
 	 * @override
	 *
	 * @returns {Object}
	 */
	getGetters()
	{
		return {};
	}

	/**
	 * Get actions
	 *
 	 * @override
	 *
	 * @returns {Object}
	 */
	getActions()
	{
		return {};
	}

	/**
	 * Get mutations
	 *
 	 * @override
	 *
	 * @returns {Object}
	 */
	getMutations()
	{
		return {};
	}

	/**
	 * Method for validation and sanitizing input fields before save in model
	 *
	 * @override
	 *
	 * @param fields {Object}
	 * @param options {Object}
	 *
	 * @returns {Object} - Sanitizing fields
	 */
	validate(fields, options = {})
	{
		return {};
	}

	/**
	 * Set external variable.
	 *
	 * @param variables {Object}
	 * @returns {VuexBuilderModel}
	 */
	setVariables(variables = {})
	{
		if (!(typeof variables === 'object' && variables))
		{
			this.logger('error', 'VuexBuilderModel.setVars: passed variables is not a Object', store);
			return this;
		}

		this.variables = variables;

		return this;
	}

	getVariable(name, defaultValue = undefined)
	{
		if (!name)
		{
			return defaultValue;
		}

		let nameParts = name.toString().split('.');
		if (nameParts.length === 1)
		{
			return this.variables[nameParts[0]];
		}

		let result;
		let variables = Object.assign({}, this.variables);

		for (let i = 0; i < nameParts.length; i++)
		{
			if (typeof variables[nameParts[i]] !== 'undefined')
			{
				variables = result = variables[nameParts[i]];
			}
			else
			{
				result = defaultValue;
				break;
			}
		}

		return result;
	}

	/**
	 * Get namespace
	 *
	 * @returns {String}
	 */
	getNamespace()
	{
		return this.namespace? this.namespace: this.getName();
	}

	/**
	 * Set namespace
	 *
	 * @param name {String}
	 *
	 * @returns {VuexBuilderModel}
	 */
	setNamespace(name)
	{
		this.namespace = name.toString();
		this.databaseConfig.name = this.namespace;

		return this;
	}

	/**
	 * Set database config for model or disable this feature.
	 *
	 * @param active {boolean}
	 * @param config {{name: String, siteId: String, userId: Number, type: VuexBuilder.DatabaseType}}
	 *
	 * @returns {VuexBuilderModel}
	 */
	useDatabase(active, config = {})
	{
		this.databaseConfig.active = !!active;

		let updateDriver = this.db === null;
		if (config.type)
		{
			this.databaseConfig.type = config.type.toString();
			updateDriver = true;
		}
		if (config.storage)
		{
			this.databaseConfig.storage = config.storage.toString();
		}
		if (config.siteId)
		{
			this.databaseConfig.siteId = config.siteId.toString();
		}
		if (config.userId)
		{
			this.databaseConfig.userId = config.userId;
		}
		if (typeof config.timeout === 'number')
		{
			this.databaseConfig.timeout = config.timeout;
		}

		if (!this.databaseConfig.active && this.db !== null)
		{
			this.databaseConfig.type = null;
			updateDriver = true;
		}

		if (updateDriver)
		{
			if (this.databaseConfig.type === VuexBuilder.DatabaseType.indexedDb)
			{
				this.db = new VuexBuilderDatabaseIndexedDB(this.databaseConfig);
			}
			else if (this.databaseConfig.type === VuexBuilder.DatabaseType.localStorage)
			{
				this.db = new VuexBuilderDatabaseLocalStorage(this.databaseConfig);
			}
			else if (this.databaseConfig.type === VuexBuilder.DatabaseType.jnSharedStorage)
			{
				this.db = new VuexBuilderDatabaseJnSharedStorage(this.databaseConfig);
			}
			else
			{
				this.db = null;
			}
		}

		return this;
	}

	/**
	 * @returns {VuexBuilderModel}
	 * @deprecated
	 */
	useNamespace(active)
	{
		if (BitrixVue.developerMode)
		{
			if (active)
			{
				console.warn('VuexBuilderModel: Method `useNamespace` is deprecated, please remove this call.');
			}
			else
			{
				console.error('VuexBuilderModel: Method `useNamespace` is deprecated, using VuexBuilder without namespaces is no longer supported.');
			}
		}

		return this;
	}

	/**
	 * @returns {Promise}
	 * @deprecated use getModule instead.
	 */
	getStore()
	{
		return this.getModule();
	}

	/**
	 * Get Vuex module.
	 *
	 * @returns {Promise}
	 */
	getModule()
	{
		return new Promise((resolve, reject) => {

			let namespace = this.namespace? this.namespace: this.getName();
			if (!namespace)
			{
				this.logger('error', 'VuexBuilderModel.getStore: current model can not be run in Vuex modules mode', this.getState());
				reject();
			}

			if (this.db)
			{
				this._getStoreFromDatabase().then(state => resolve({
					namespace,
					module: this._createStore(state)
				}));
			}
			else
			{
				resolve({
					namespace,
					module: this._createStore(this.getState())
				});
			}
		});
	}

	/**
	 * Get default state of Vuex module.
	 *
	 * @returns {Object}
	 */
	getModuleWithDefaultState()
	{
		let namespace = this.namespace? this.namespace: this.getName();
		if (!namespace)
		{
			this.logger('error', 'VuexBuilderModel.getStore: current model can not be run in Vuex modules mode', this.getState());
			return null;
		}

		return {
			namespace,
			module: this._createStore(this.getState())
		};
	}

	/**
	 * Get timeout for save to database
	 *
 	 * @override
	 *
	 * @returns {number}
	 */
	getSaveTimeout()
	{
		return 150;
	}

	/**
	 * Get timeout for load from database
	 *
	 * @override
	 *
	 * @returns {number|boolean}
	 */
	getLoadTimeout()
	{
		return 1000;
	}

	/**
	 * Get state after load from database
	 *
 	 * @param state {Object}
	 *
	 * @override
	 *
	 * @returns {Object}
	 */
	getLoadedState(state = {})
	{
		return state;
	}

	/**
	 * Save current state after change state to database
	 *
 	 * @param state {Object|function}
	 *
	 * @returns {Promise}
	 */
	saveState(state = {})
	{
		if (!this.isSaveAvailable())
		{
			return true;
		}

		this.lastSaveState = state;

		if (this.saveStateTimeout)
		{
			this.logger('log', 'VuexModel.saveState: wait save...', this.getName());
			return true;
		}

		this.logger('log', 'VuexModel.saveState: start saving', this.getName());

		let timeout = this.getSaveTimeout();
		if (typeof this.databaseConfig.timeout === 'number')
		{
			timeout = this.databaseConfig.timeout;
		}

		this.saveStateTimeout = setTimeout(() =>
		{
			this.logger('log', 'VuexModel.saveState: saved!', this.getName());
			let lastState = this.lastSaveState;
			if (typeof lastState === 'function')
			{
				lastState = lastState();
				if (typeof lastState !== 'object' || !lastState)
				{
					return false;
				}
			}

			this.db.set(
				this.cloneState(lastState, this.getStateSaveException())
			);

			this.lastState = null;
			this.saveStateTimeout = null;
		}, timeout);

		return true
	}

	/**
	 * Reset current store to default state
	 **
	 * @returns {Promise}
	 */
	clearState()
	{
		if (this.store)
		{
			this.store.commit(this.getNamespace()+'/'+'vuexBuilderModelClearState');

			return true;
		}

		return this.saveState(
			this.getState()
		);
	}

	/**
	 * Clear database only, store state does not change
	 **
	 * @returns {Promise}
	 */
	clearDatabase()
	{
		if (!this.isSaveAvailable())
		{
			return true;
		}

		this.db.clear();

		return true;
	}

	isSaveAvailable()
	{
		return this.db && this.databaseConfig.active;
	}

	isSaveNeeded(payload)
	{
		if (!this.isSaveAvailable())
		{
			return false;
		}

		let checkFunction = function(payload, filter = null)
		{
			if (!filter)
			{
				return true;
			}

			for (let field in payload)
			{
				if (!payload.hasOwnProperty(field))
				{
					continue;
				}

				if (typeof filter[field] === 'undefined')
				{
					return true;
				}
				else if (typeof filter[field] === 'object' && filter[field])
				{
					let result = checkFunction(payload[field], filter[field]);
					if (result)
					{
						return true;
					}
				}
			}

			return false;
		};

		return checkFunction(payload, this.getStateSaveException());
	}

	/**
	 * Create new instance of model.
	 */
	constructor()
	{
		this.databaseConfig = {
		 	type: VuexBuilder.DatabaseType.indexedDb,
		 	active: null,
		 	storage: 'default',
		 	name: this.getName(),
		 	siteId: 'default',
		 	userId: 0,
			timeout: null
		};

		this.db = null;
		this.store = null;

		this.namespace = null;

		this.variables = {};
	}

	setStore(store)
	{
		if (!(store instanceof Vuex.Store))
		{
			this.logger('error', 'VuexBuilderModel.setStore: passed store is not a Vuex.Store', store);
			return this;
		}

		this.store = store;
		return this;
	}

	_getStoreFromDatabase()
	{
		clearTimeout(this.cacheTimeout);
		return new Promise((resolve) =>
		{
			const loadTimeout = this.getLoadTimeout();

			if ((loadTimeout !== false) && (typeof loadTimeout === 'number'))
			{
				this.cacheTimeout = setTimeout(() => {
					this.logger('warn', 'VuexModel.getStoreFromDatabase: Cache loading timeout', this.getName());
					resolve(this.getState());
				}, loadTimeout);
			}
			else
			{
				this.cacheTimeout = null;
			}

			this.db.get().then(cache =>
			{
				clearTimeout(this.cacheTimeout);
				cache = this.getLoadedState(cache? cache: {});

				let state = this.getState();
				if (cache)
				{
					state = this._mergeState(state, cache);
				}

				resolve(state);
			}, (error) =>
			{
				clearTimeout(this.cacheTimeout);
				resolve(this.getState());
			})
		});
	}

	_mergeState(currentState, newState)
	{
		for (let key in currentState)
		{
			if (!currentState.hasOwnProperty(key))
			{
				continue;
			}

			if (typeof newState[key] === 'undefined')
			{
				newState[key] = currentState[key];
			}
			else if (
				!(newState[key] instanceof Array) &&
				typeof newState[key] === 'object' && newState[key] &&
				typeof currentState[key] === 'object' && currentState[key]
			)
			{
				newState[key] = Object.assign({}, currentState[key], newState[key]);
			}
		}

		return newState;
	}

	_createStore(state)
	{
		let result = {
			namespaced: true,
			state,
			getters: this.getGetters(),
			actions: this.getActions(),
			mutations: this.getMutations()
		};

		result.mutations.vuexBuilderModelClearState = (state) => {
			state = Object.assign(state, this.getState());
			this.saveState(state);
		};

		return result;
	}

	/**
	 * Utils. Convert Object to Array
	 * @param object
	 * @returns {Array}
	 */
	static convertToArray(object)
	{
		let result = [];
		for (let i in object)
		{
			if (object.hasOwnProperty(i))
			{
				result.push(object[i]);
			}
		}
		return result;
	}

	/**
	 * Clone state without observers
	 * @param element {object}
	 * @param exceptions {object}
	 */
	cloneState(element, exceptions = undefined)
	{
		let result;

		if (element instanceof Array)
		{
			result = [].concat(
				element.map(element => this.cloneState(element))
			);
		}
		else if (element instanceof Date)
		{
			result = new Date(element.toISOString());
		}
		else if (typeof element === 'object' && element)
		{
			result = {};
			for (let param in element)
			{
				if (!element.hasOwnProperty(param))
				{
					continue;
				}
				if (
					typeof exceptions === 'undefined'
					|| typeof exceptions[param] === 'undefined'
				)
				{
					result[param] = this.cloneState(element[param])
				}
				else if (typeof exceptions[param] === 'object' && exceptions[param])
				{
					result[param] = this.cloneState(element[param], exceptions[param])
				}
			}
		}
		else
		{
			result = element;
		}

		return result;
	}

	logger(type, ...args)
	{
		if (type === 'error')
		{
			console.error(...args);
			return undefined;
		}
		else if (typeof BX.VueDevTools === 'undefined')
		{
			return undefined;
		}

		if (type === 'log')
		{
			console.log(...args);
		}
		else if (type === 'info')
		{
			console.info(...args);
		}
		else if (type === 'warn')
		{
			console.warn(...args);
		}
	}
}