UNPKG

adhara

Version:

foundation for any kind of website: microframework

279 lines (245 loc) 8.15 kB
/** * @class * @classdesc a base class that is to be extended by all the view classes * */ class AdharaView extends AdharaEventHandler{ /** * @constructor * @param {Object} [settings] * @param {String} [settings.key=undefined] - Instance key * @param {String} settings.c - CSS Selector from parent view to place content of this class * @param {HandlebarTemplate} settings.t - View template * */ constructor(settings = {}){ super(); this.settings = settings; let {key, c} = this.settings; this.ts = `d${performance.now().toString().replace(".", '-')}`; this.context = new Context(key||this.ts, this); this._parentContainer = c; Adhara.addViewToInstances(this); this.is_active = false; this._registerEvents(["ViewRendered", "SubViewsRendered", "ViewFormatted", "ViewDestroyed"]); this.fetching_data = false; this.onInit(); this._subViews = []; this.rendered = false; this.initialized = true; } /** * @function * @instance * @description Anything that needs to be done right after initializing the view. * */ onInit(){ //Override this method to do any miscellaneous operations/assignments right after initializing the View } /** * @method * @getter * @returns {HandlebarTemplate} template of the view * */ get template(){ if(this.settings.t) return this.settings.t; if(Adhara.router.getCurrentPageName()){ return Adhara.router.getCurrentPageName().replace(/_/g, "-"); }else{ let className = this.constructor.name.toLowerCase(); if(className.endsWith("view")){ return className.slice(0, className.length - 4); }else{ return className; } } } get parentContainer(){ return this._parentContainer; } set parentContainer(_){ this._parentContainer = _; } getCustomTemplate(type){ for(let ViewClass of Adhara.viewHierarchy){ if(ViewClass.isPrototypeOf(this.constructor)){ if(Adhara.app.customViewConfig[type] && Adhara.app.customViewConfig[type][ViewClass.name]){ return Adhara.app.customViewConfig[type][ViewClass.name]; } } } } /** * @method * @getter * @returns {HandlebarTemplate} template of the view * */ get errorTemplate(){ return this.getCustomTemplate("error") || null; } /** * @method * @getter * @returns {HandlebarTemplate} structure to be rendered when data is being fetched * */ get fetchingDataTemplate(){ return this.getCustomTemplate("fetching_data") || ""; } /** * @method * @getter * @returns {HandlebarTemplate} structure to be rendered when there is no data available * */ get noDataTemplate(){ return this.getCustomTemplate("no_data") || ""; } /** * @function * @instance * @description Helper method to get required template. Error template or success template. * */ getTemplate(){ return this.fetching_data?this.fetchingDataTemplate:(this.errors?(this.errorTemplate||this.template):this.template); } get isImmortal(){ return false; } isActive(){ return Adhara.isActiveView(this) && this.is_active; } create(){ Adhara.addToActiveViews(this); this.is_active = true; this.fetch().then(()=>{this.render();}); this.render(); } async fetch(){ this.fetching_data = true; await this.fetchData(); this.fetching_data = false; } /** * @function * @instance * @description hook to make API calls, data change event will be triggered on successful API call. * By default no API call will be made and dataChange method will be called right away. * */ async fetchData(){ } _getHTML(template){ return Adhara.app.renderTemplate( template || this.getTemplate(), this ); } /** * @param {Context} context - current view context from which this class instance is to be looked up in the tree * @param {String} [tag] - instance tag * */ static of(context, tag){ return context.getViewFromRenderTree(this, tag); } get r(){ return { d: Adhara.app.d }; } getParentContainerElement(){ return document.querySelector(this.parentContainer); } querySelector(css_selector){ return this.getParentContainerElement().querySelector(css_selector); } querySelectorAll(css_selector){ return this.getParentContainerElement().querySelectorAll(css_selector); } setState(){ this.render(); } render(){ let container = this.getParentContainerElement(); if(!container){ console.warn(`No container defined/available for ${this.constructor.name}, selecting with ${this.parentContainer}`); return; } container.innerHTML = this._getHTML(); if(this.fetching_data){ return; } this.trigger("ViewRendered"); this._format(container); setTimeout(()=> { this.format(container); this.trigger("ViewFormatted"); }, 0); this.renderSubViews(); this.trigger("SubViewsRendered"); this.rendered = true; } /** * @function * @getter * @returns {String} A CSS selector inside which child views are to be rendered. * */ get contentContainer(){ throw new Error("implement this method"); } /** * @function * @instance * @returns Array<AdharaView> * */ get subViews(){ return this._subViews; } renderSubViews(){ for(let sub_view of (this.subViews || [])){ Adhara.createView(sub_view, this); } } _format(container){ for(let action of [ "click", "change", "blur", "focus", "scroll", "contextmenu", "copy", "cut", "dblclick", "drag", "dragend", "dragenter", "dragleave", "dragover", "dragstart", "drop", "focus", "focusin", "focusout", "input", "invalid", "mousedown", "mouseenter", "mouseleave", "mouseover", "mouseout", "mouseup", "paste", "scroll", "show", "toggle", "wheel", "keyup", "keydown", "keypress" ]){ let onActionElements = container.querySelectorAll(`[data-on${action}]`); for(let actionElement of onActionElements){ if(actionElement.dataset[`_ae_${action}_`] === "true"){ continue; } actionElement.addEventListener(action, event => { let data = actionElement.dataset; let action_key = `on${action}`; if(this[data[action_key]]){ this[data[action_key]](event, data, this); }else{ let fn = getValueFromJSON(window, data[action_key]); if(fn) { fn(event, data, this); }else{ throw new Error(`Invalid function: ${data[action_key]} in View ${this.constructor.name}`); } } }); actionElement.dataset[`_ae_${action}_`] = "true"; } } } format(container){ //Control the DOM elements after rendering } refresh(){ Adhara.createView(this); } onDestroy(){ // This method will be called just before destroying the view } destroy(){ // This method will destroy the view this.onDestroy(); this.trigger("ViewDestroyed"); this.is_active = false; try{ this.getParentContainerElement().innerHTML = ""; }catch(e){/*Do nothing*/} } }