UNPKG

cipherguard-mad

Version:

Cipherguard javascript application library for the open source password manager for teams

365 lines (329 loc) 9.89 kB
/** * Cipherguard ~ Open source password manager for teams * Copyright (c) KhulnaSoft Ltd (https://www.khulnasoft.com) * * Licensed under GNU Affero General Public License version 3 of the or any later version. * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) KhulnaSoft Ltd (https://www.khulnasoft.com) * @license https://opensource.org/licenses/AGPL-3.0 AGPL License * @link https://www.khulnasoft.com KhulnaSoft(tm) */ import $ from 'jquery'; import Control from '../control/control'; import DefineList from '../model/list/list'; import ComponentState from '../model/state/componentState'; import View from '../view/view'; const Component = Control.extend('mad.Component', /* @static */{ defaults: { // The component's icon. icon: null, // The component's label. label: 'ComponentController', // The classes to add to the HTML Element the component is created on. cssClasses: ['js_component'], // The component is considered loaded on start loadedOnStart: true, // The default HTML Element the component will be wrapped by. Used by the ComponentHelper to create the DOM element. tag: 'div', // Override the default template to use any other existing one. template: null, // The component's view controller used to drive the component's view. viewClass: View, stateClass: ComponentState, // The data used by the view viewData: {}, // Should the component notify others while it's loading. silentLoading: true }, /** * Register of components. */ _components: new DefineList() }, /** @prototype */ { /** * Constructor * @param {string} el The element selector * @param {object} options The component options */ init: function(el, options) { this._super(el, options); this._initState(options); this._loadedDependencies = new DefineList(); this._registerComponent(); }, /** * Init the component state */ _initState: function(options) { const state = new this.options.stateClass(options.state); state.on('destroyed', (ev, destroyed) => this.onDestroyedChange(destroyed)); state.on('disabled', (ev, disabled) => this.onDisabledChange(disabled)); state.on('hidden', (ev, hidden) => this.onHiddenChange(hidden)); state.on('loaded', (ev, loaded) => this.onLoadedChange(loaded)); state.on('started', (ev, started) => this.onStartedChange(started)); state.on('empty', (ev, empty) => this.onEmptyChange(empty)); this.state = state; }, /** * Register the component. * @private */ _registerComponent: function() { Component._components.push(this); }, /** * Unregister the component. * @private */ _unregisterComponent: function() { const id = this.getId(); const index = Component._components.indexOf({id: id}); if (index != -1) { Component._components.splice(index, 1); } }, /** * Add a loaded dependency. The component loaded state depend on its children. * @param {Component} The component to observe */ addLoadedDependency: function(component) { this._loadedDependencies.push(component); if (component.state.loaded == false) { this.state.loaded = false; } component.state.on('loaded', () => { const loaded = this._loadedDependencies.reduce((carry, dependency) => carry && dependency.state.loaded, true); this.state.loaded = loaded; }); }, /** * Observe when the component is destroyed * @param {boolean} started True if destroyed. It cannot be false */ onDestroyedChange: function(destroyed) { if (destroyed) { $(this.element).removeClass('ready'); } }, /** * Observe when the component is enable / disable * @param {boolean} disabled True if disabled, false otherwise */ onDisabledChange: function(disabled) { if (disabled) { $(this.element).addClass('disabled'); } else { $(this.element).removeClass('disabled'); } }, /** * Observe when the component is shown / hidden * @param {boolean} hidden True if hidden, false otherwise */ onHiddenChange: function(hidden) { if (hidden) { $(this.element).addClass('hidden'); } else { $(this.element).removeClass('hidden'); } }, /** * Observe when the component is loaded / loading * @param {boolean} loaded True if loaded, false otherwise */ onLoadedChange: function(loaded) { if (loaded) { $(this.element).addClass('ready'); } else { $(this.element).removeClass('ready'); } }, /** * Observe when the component is started / stopped * @param {boolean} started True if started, false otherwise */ // eslint-disable-next-line no-unused-vars onStartedChange: function(started) { }, /** * Observe when the component is empty / filled * @param {boolean} empty True if empty, false otherwise */ onEmptyChange: function(empty) { const emptyFeedback = $('.empty-content', this.element); if (empty) { $(this.element).addClass('empty'); emptyFeedback.removeClass('hidden'); } else { $(this.element).removeClass('empty'); emptyFeedback.addClass('hidden'); } }, /** * @inheritdoc */ destroy: function() { // If the component has been destroyed, but the HTMLElement still exists. if (this.element && this.element != null) { // Remove the optional css classes from the HTMLElement. for (const i in this.options.cssClasses) { $(this.element).removeClass(this.options.cssClasses[i]); } // Remove the construct class name. $(this.element).removeClass(this.constructor.name); } // Destroy the view. if (this.view) { this.view.destroy(); } this.state.destroyed = true; delete this._unregisterComponent(); this._super(); }, /** * Destroy the component and remove the DOM element. */ destroyAndRemove: function() { const element = this.element; this.destroy(); if (element) { $(element).remove(); } }, /** * Pass a variable to the component's View & template. * @param {string|array} name The variable name or an array of variables. * @param {mixed} value (optional) The variable value if a variable name as been given. */ setViewData: function(name, value) { if (typeof name == 'object') { const data = name; for (const i in data) { this.setViewData(i, data[i]); } } else { this.options.viewData[name] = value; } return this; }, /** * Get a variable value that has been passed to the component's View & template. * @param {mixed} name (optional) The variable name. If null, returns all the variables passed * to the component's View & template. * @return {mixed} */ getViewData: function(name) { if (typeof name == 'undefined') { return this.options.viewData; } return this.options.viewData[name]; }, /** * @inheritdoc */ start: function() { this.beforeStart(); this.initView(); this.updateWrapperElement(); this.render(); this.afterStart(); this.state.started = true; if (this.options.loadedOnStart) { this.state.loaded = true; } return this; }, /** * Refresh the component. */ refresh: function() { if (this.state.destroyed) { return; } $(this.element).empty(); this.render(); this.afterStart(); return this; }, /** * Called right before the component is started. * Override this function if you want add a specific behavior. */ beforeStart: function() { }, /** * Initialize the component's View. */ initView: function() { this.view = new this.options.viewClass(this.element, { template: this.options.template, cssClasses: this.options.cssClasses, controller: this }); }, /** * Called right after the component has been started. * Override this function if you want add a specific behavior. * This hook is in other word your component main function. */ afterStart: function() { }, /** * Update the DOM wrapper element. */ updateWrapperElement: function() { this.options.cssClasses.forEach(cssClass => $(this.element).addClass(cssClass)); if (this.state.disabled) { this.view.addClass('disabled'); } else { this.view.removeClass('disabled'); } if (this.state.hidden) { this.view.addClass('hidden'); } else { this.view.removeClass('hidden'); } }, /** * Render the component */ render: function() { if (this.options.template) { this.setDefaultViewData(); this.beforeRender(); let render = this.view.render(); render = this.afterRender(render); this.view.insertInDom(render); } }, /** * Set the default view data */ setDefaultViewData: function() { this.setViewData('control', this); this.setViewData('controller', this); this.setViewData('icon', this.options.icon); this.setViewData('label', this.options.label); this.setViewData('view', this.view); this.setViewData('id', this.id); }, /** * Called right before the component is rendered. * Override this function if you want add a specific behavior. * This hook is commonly used to pass data to the component's View & template. */ beforeRender: function() { }, /** * Called right after the component has been rendered. * Override this function if you want add a specific behavior. * @param {string} render The render content * @return {string} The updated render content */ afterRender: function(render) { return render; } }); export default Component;