UNPKG

mobx-view-model

Version:
222 lines (221 loc) 7.82 kB
import { action, computed, observable, runInAction } from 'mobx'; import { mergeVMConfigs, applyObservable, } from '../config/index.js'; import { generateVmId } from '../utils/index.js'; export class ViewModelStoreBase { config; viewModels; linkedComponentVMClasses; viewModelIdsByClasses; instanceAttachedCount; /** * It is temp heap which is needed to get access to view model instance before all initializations happens */ viewModelsTempHeap; /** * Views waiting for mount */ mountingViews; /** * Views waiting for unmount */ unmountingViews; vmConfig; constructor(config) { this.config = config; this.viewModels = observable.map([], { deep: false }); this.linkedComponentVMClasses = observable.map([], { deep: false }); this.viewModelIdsByClasses = observable.map([], { deep: true }); this.instanceAttachedCount = observable.map([], { deep: false }); this.mountingViews = observable.set([], { deep: false }); this.unmountingViews = observable.set([], { deep: false }); this.vmConfig = mergeVMConfigs(config?.vmConfig); this.viewModelsTempHeap = new Map(); applyObservable(this, [ ['mountedViewsCount', computed], ['mount', action], ['unmount', action], ['attachVMConstructor', action], ['attach', action], ['detach', action], ['linkComponents', action], ['unlinkComponents', action], ], this.vmConfig.observable.viewModelStores); } get mountedViewsCount() { return [...this.instanceAttachedCount.values()].reduce((sum, count) => sum + count, 0); } processCreateConfig(config) { this.linkComponents(config.VM, config.component, config.ctx?.externalComponent); } createViewModel(config) { const VMConstructor = config.VM; const vmConfig = mergeVMConfigs(this.vmConfig, config.config ?? config.vmConfig); const vmParams = { ...config, vmConfig, }; if (vmConfig.factory) { return vmConfig.factory(vmParams); } return new VMConstructor(vmParams); } generateViewModelId(config) { if (config.id) { return config.id; } else if (this.vmConfig.generateId) { return this.vmConfig.generateId(config.ctx); } else { return generateVmId(config.ctx); } } linkComponents(VM, ...components) { components.forEach((component) => { if (component && !this.linkedComponentVMClasses.has(component)) { this.linkedComponentVMClasses.set(component, VM); } }); } unlinkComponents(...components) { components.forEach((component) => { if (component && this.linkedComponentVMClasses.has(component)) { this.linkedComponentVMClasses.delete(component); } }); } getIds(vmLookup) { if (!vmLookup) return []; if (typeof vmLookup === 'string') { return [vmLookup]; } const viewModelClass = (this.linkedComponentVMClasses.get(vmLookup) || vmLookup); const viewModelIds = this.viewModelIdsByClasses.get(viewModelClass) || []; return viewModelIds; } getId(vmLookup) { const viewModelIds = this.getIds(vmLookup); if (viewModelIds.length === 0) return null; if (process.env.NODE_ENV !== 'production' && viewModelIds.length > 1) { console.warn(`Found more than 1 view model with the same identifier. Last instance will been returned`); } return viewModelIds.at(-1); } has(vmLookup) { const id = this.getId(vmLookup); if (!id) return false; return this.viewModels.has(id); } get(vmLookup) { // helps to users of this method to better observe changes in view models // this.viewModels.keys(); const id = this.getId(vmLookup); if (!id) return null; const observedVM = this.viewModels.get(id); return observedVM ?? this.viewModelsTempHeap.get(id) ?? null; } getAll(vmLookup) { const viewModelIds = this.getIds(vmLookup); return viewModelIds.map((id) => this.viewModels.get(id)); } async mount(model) { this.mountingViews.add(model.id); await model.mount?.(); runInAction(() => { this.mountingViews.delete(model.id); }); } async unmount(model) { this.unmountingViews.add(model.id); await model.unmount?.(); runInAction(() => { this.unmountingViews.delete(model.id); }); } attachVMConstructor(model) { const constructor = model.constructor; if (this.viewModelIdsByClasses.has(constructor)) { const vmIds = this.viewModelIdsByClasses.get(constructor); if (!vmIds.includes(model.id)) { vmIds.push(model.id); } } else { this.viewModelIdsByClasses.set(constructor, [model.id]); } } dettachVMConstructor(model) { const constructor = model.constructor; if (this.viewModelIdsByClasses.has(constructor)) { const vmIds = this.viewModelIdsByClasses .get(constructor) .filter((it) => it !== model.id); if (vmIds.length > 0) { this.viewModelIdsByClasses.set(constructor, vmIds); } else { this.viewModelIdsByClasses.delete(constructor); } } } markToBeAttached(model) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error this.viewModelsTempHeap.set(model.id, model); if ('linkStore' in model) { model.linkStore(this); } else if ('attachViewModelStore' in model) { model.attachViewModelStore(this); } this.attachVMConstructor(model); } async attach(model) { const attachedCount = this.instanceAttachedCount.get(model.id) ?? 0; this.instanceAttachedCount.set(model.id, attachedCount + 1); if (this.viewModels.has(model.id)) { return; } this.viewModels.set(model.id, model); this.attachVMConstructor(model); await this.mount(model); this.viewModelsTempHeap.delete(model.id); } async detach(id) { const attachedCount = this.instanceAttachedCount.get(id) ?? 0; this.viewModelsTempHeap.delete(id); const model = this.viewModels.get(id); if (!model) { return; } const nextInstanceAttachedCount = attachedCount - 1; this.instanceAttachedCount.set(id, nextInstanceAttachedCount); if (nextInstanceAttachedCount <= 0) { if ('willUnmount' in model) { model.willUnmount(); } this.instanceAttachedCount.delete(id); this.viewModels.delete(id); this.dettachVMConstructor(model); await this.unmount(model); } } isAbleToRenderView(id) { const isViewMounting = this.mountingViews.has(id); const hasViewModel = this.viewModels.has(id); return !!id && hasViewModel && !isViewMounting; } clean() { this.viewModels.clear(); this.linkedComponentVMClasses.clear(); this.viewModelIdsByClasses.clear(); this.instanceAttachedCount.clear(); this.mountingViews.clear(); this.unmountingViews.clear(); this.viewModelsTempHeap.clear(); } }