UNPKG

cheetah-framework

Version:

Cheetah Framework JS used in all our applications

508 lines (419 loc) 14.3 kB
/* global Echo echoActive */ import StoreFactory from '@cheetah/store/StoreFactory' const modules = {} const resourceToModuleMap = {} const registeredModules = {} const persistedModules = {} class CheetahStore { constructor (module) { this.options = getStoreOptions(module) this.name = this.options.moduleName // Standard vuex module this.subscribeUnregister = null this.subscribeActionsUnregister = null this.channel = null if (this.isCheetahStore()) { this.name = this.options.model.resourceName this.module = this.options.preventFetch ? null : StoreFactory.make(this.options) resourceToModuleMap[this.name] = this } else { this.module = module.default } if (!this.name) { console.error('Missing mandatory moduleName for following module') console.log(module) throw new Error('Missing mandatory moduleName') } modules[this.name] = this } isStandardStore () { return !this.isCheetahStore() } isCheetahStore () { return !!this.options.model } /** * Return true if a vuex module of that name exists. * Include CheetahStore and custom vuex store. * @param {string} moduleName * @returns {boolean} */ static moduleExists (moduleName) { return typeof modules[moduleName] !== 'undefined' } /** * Return true if a CheetahStore with a laravel resource of that name exists. * @param {string} resourceName * @returns {boolean} */ static exists (resourceName) { return typeof resourceToModuleMap[resourceName] !== 'undefined' } /** * Return true if the vuex module is registered. * $store.registerModule(resourceName, module) has been called * @param resourceName * @returns {boolean} */ static vuexModuleLoaded (resourceName) { return typeof cheetahApp.$store._modules.root._children[resourceName] !== 'undefined' } /** * Return true if a vuex store (CheetahStore only) listens a * certain eventName of a certain channel. The channel has * the name of the resourceName. * @param resourceName * @param eventName [created, updated, deleted, bulkCreated, bulkUpdated, bulkDeleted] * @returns {boolean} */ static isListening (resourceName, eventName) { if (!echoActive) { return false } return modules?.[resourceName]?.channel?.subscription?.subscribed === true && this.canListen(resourceName, eventName) } /** * Reload all items of a vuex store (CheetahStore only), but only if * it's fetch already. * @param {string} resourceName */ static reload (resourceName) { if (CheetahStore.isFetched(resourceName)) { CheetahStore.dispatch( `${resourceName}/fetch`, { force: true } ) } } /** * Call this function after you delete an item * @param {string} resourceName * @param {object} payload - payload must contain id */ static delete (resourceName, payload) { // if already waiting a pusher event of that type if (CheetahStore.isListening(resourceName, 'deleted')) { return } if (CheetahStore.vuexModuleLoaded(resourceName)) { CheetahStore.commit(`${resourceName}/DELETE`, payload) } Bus.$emit(`deleted-${resourceName}`, payload) } /** * Call this function after you bulk delete items * @param resourceName * @param ids - array of deleted ids [1,2,3,...] */ static bulkDelete (resourceName, ids) { // if already waiting a pusher event of that type if (CheetahStore.isListening(resourceName, 'bulkDeleted')) { return } // if vuex store of that resourceName is loaded if (CheetahStore.vuexModuleLoaded(resourceName)) { CheetahStore.reload(resourceName) } Bus.$emit(`bulkDeleted-${resourceName}`, ids) } /** * Call this function after you update an item * @param {string} resourceName * @param {object} payload - payload must contain id */ static update (resourceName, payload) { // if already waiting a pusher event of that type if (CheetahStore.isListening(resourceName, 'updated')) { return } // if vuex store of that resourceName is loaded if (CheetahStore.vuexModuleLoaded(resourceName)) { CheetahStore.commit(`${resourceName}/UPDATE`, payload) } Bus.$emit(`updated-${resourceName}`, payload) } /** * Call this function after you bulk update items * @param resourceName * @param payload - array of updated instances [{ id: 1, last_update: '...' }, ...] */ static bulkUpdate (resourceName, payload) { // if already waiting a pusher event of that type if (CheetahStore.isListening(resourceName, 'bulkUpdated')) { return } // if vuex store of that resourceName is loaded if (CheetahStore.vuexModuleLoaded(resourceName)) { CheetahStore.reload(resourceName) } Bus.$emit(`bulkUpdated-${resourceName}`, payload) } /** * Call this function after you store an item * @param {string} resourceName * @param {object} payload - payload must contain id */ static store (resourceName, payload) { // if already waiting a pusher event of that type if (CheetahStore.isListening(resourceName, 'created')) { return } // if vuex store of that resourceName is loaded if (CheetahStore.vuexModuleLoaded(resourceName)) { CheetahStore.commit(`${resourceName}/STORE`, payload) } Bus.$emit(`created-${resourceName}`, payload) } static isFetched (resourceName) { return _.get(cheetahApp.$store._modules.root._children, resourceName + '.state.loaded') === true } /** * This method is automatically called to register vuex module and, * in case it is a CheetahStore, bind Echo events if required (liveUpdate = true). * @param moduleName * @param {VueComponent} vueComponent - Vue instance requesting this module * @param {boolean} persisted * @returns {boolean} return true if module is successfully loaded */ static load (moduleName, vueComponent, persisted = false) { if (!CheetahStore.moduleExists(moduleName)) { console.error('Unknown vuex module ' + moduleName) return false } if (!CheetahStore.vuexModuleLoaded(moduleName)) { modules[moduleName].register() } if (modules[moduleName].options.persisted || persisted) { persistedModules[moduleName] = true } if (!persistedModules[moduleName]) { // increments registered modules if (typeof registeredModules[moduleName] === 'undefined') { registeredModules[moduleName] = 0 } registeredModules[moduleName]++ vueComponent.$once('hook:beforeDestroy', _ => { if (--registeredModules[moduleName] === 0) { setTimeout( () => { if (persistedModules[moduleName]) { return } if (registeredModules[moduleName] === 0) { modules[moduleName].unregister() } }, modules[moduleName].options.unregisterDelay ) } }) } (_.get(modules[moduleName], 'options.dependencies') || []).forEach(dependency => { CheetahStore.load(dependency, vueComponent, modules[moduleName].options.persisted) }) return true } register () { if (!this.options.preventFetch || this.isStandardStore()) { cheetahApp.$store.registerModule(this.name, this.module) } if (this.options.subscribe) { this.subscribeUnregister = cheetahApp.$store.subscribe(this.options.subscribe) } if (this.options.subscribeAction) { this.subscribeActionsUnregister = cheetahApp.$store.subscribeAction(this.options.subscribeAction) } if (this.isCheetahStore() && echoActive && this.options.liveUpdate) { this.bindEvents() } this.options.register.call(this) } unregister () { this.options.unregister.call(this) if (this.isCheetahStore() && echoActive && this.options.liveUpdate) { this.unbindEvents() } if (_.isFunction(this.subscribeUnregister)) { this.subscribeUnregister() this.subscribeUnregister = null } if (_.isFunction(this.subscribeActionsUnregister)) { this.subscribeActionsUnregister() this.subscribeActionsUnregister = null } if (this.isCheetahStore()) { CheetahStore.commit(`${this.options.model.resourceName}/FLUSH`) } if (!this.options.preventFetch || this.isStandardStore()) { cheetahApp.$store.unregisterModule(this.name) } } canListen (eventName) { if (this.options.liveUpdate === true) { return true } if (_.isArray(this.options.liveUpdate)) { return _.includes(this.options.liveUpdate, eventName) } return false } static canListen (resourceName, eventName) { return !!resourceToModuleMap?.[resourceName]?.canListen(eventName) } static commit (mutatorFullPath, payload) { if (typeof cheetahApp.$store._mutations[mutatorFullPath] === 'undefined') { return } cheetahApp.$store.commit(mutatorFullPath, payload) } static dispatch (actionFullPath, payload) { if (typeof cheetahApp.$store._actions[actionFullPath] === 'undefined') { return Promise.reject(new CheetahStoreError(`Store action "${actionFullPath}" does not exists.`)) } return cheetahApp.$store.dispatch(actionFullPath, payload) } bindEvents () { if (this.isStandardStore()) { return } if (this.channel === null) { this.channel = Echo.private('cheetah.admin.' + this.options.model.resourceName) if (this.canListen('created')) { this.channel.listen('.created', createListener.bind(this)) } if (this.canListen('bulkCreated')) { this.channel.listen('.bulkCreated', bulkCreateListener.bind(this)) } if (this.canListen('updated')) { this.channel.listen('.updated', updateListener.bind(this)) } if (this.canListen('bulkUpdated')) { this.channel.listen('.bulkUpdated', bulkUpdateListener.bind(this)) } if (this.canListen('deleted')) { this.channel.listen('.deleted', deleteListener.bind(this)) } if (this.canListen('bulkDeleted')) { this.channel.listen('.bulkDeleted', bulkDeleteListener.bind(this)) } if (this.options.broadcast) { this.options.broadcast.call(this, this.channel) } } else { this.channel.subscribe() } } unbindEvents () { if (this.isStandardStore()) { return } this.channel.subscription.unsubscribe() } } function getStoreOptions (module) { return _.defaults(module.default ? _.get(module, 'default.options', {}) : module.options, { model: null, moduleName: null, liveUpdate: true, broadcast: null, subscribe: null, // vuex plugin on mutations subscribeAction: null, // vuex plugin on actions unregisterDelay: 500, persisted: false, register: () => {}, unregister: () => {}, dependencies: [], state: {}, getters: {}, actions: {}, mutations: {}, preventFetch: false }) } function createListener (payload) { const model = this.options.model const resourceName = model.resourceName // Force store to fetch if we only received the id if (isIdOnlyPayload(payload, model)) { CheetahStore.dispatch(`${resourceName}/reloadItem`, payload[model.idKey]).then(response => { Bus.$emit(`created-${resourceName}`, response.data) }).catch(error => { if (!(error instanceof CheetahStoreError)) { throw error } // if module preventFetch is true model.get(payload[model.idKey], null, 'list').then(response => { Bus.$emit(`created-${resourceName}`, response.data) }) }) } else { CheetahStore.commit(`${resourceName}/STORE`, payload) Bus.$emit(`created-${resourceName}`, payload) } } function bulkCreateListener (payload) { const resourceName = this.options.model.resourceName // if vuex store of that resourceName is loaded if (CheetahStore.vuexModuleLoaded(resourceName)) { CheetahStore.reload(resourceName) } Bus.$emit(`bulkCreated-${resourceName}`, payload) } function updateListener (payload) { const model = this.options.model const resourceName = model.resourceName // Force store to fetch if we only received the id if (isIdOnlyPayload(payload, model)) { CheetahStore.dispatch(`${resourceName}/reloadItem`, payload[model.idKey]).then(response => { Bus.$emit(`updated-${resourceName}`, response.data) }).catch(error => { if (!(error instanceof CheetahStoreError)) { throw error } // if module preventFetch is true model.get(payload[model.idKey], null, 'list').then(response => { Bus.$emit(`updated-${resourceName}`, response.data) }) }) } else { CheetahStore.commit(`${resourceName}/UPDATE`, payload) Bus.$emit(`updated-${resourceName}`, payload) } } function bulkUpdateListener (payload) { const resourceName = this.options.model.resourceName // if vuex store of that resourceName is loaded if (CheetahStore.vuexModuleLoaded(resourceName)) { CheetahStore.reload(resourceName) } Bus.$emit(`bulkUpdated-${resourceName}`, payload) } function deleteListener (payload) { const resourceName = this.options.model.resourceName if (CheetahStore.vuexModuleLoaded(resourceName)) { CheetahStore.commit(`${resourceName}/DELETE`, payload) } Bus.$emit(`deleted-${resourceName}`, payload) } function bulkDeleteListener (payload) { const resourceName = this.options.model.resourceName // if vuex store of that resourceName is loaded if (CheetahStore.vuexModuleLoaded(resourceName)) { CheetahStore.reload(resourceName) } Bus.$emit(`bulkDeleted-${resourceName}`, payload) } class CheetahStoreError extends Error {} /** * Return true if the backend only sent the id of the instance. * * When the payload size of instance is to big to * go through Pusher we just received the id. */ function isIdOnlyPayload (payload, model) { return _.difference( _.keys(payload), [model.idKey, 'updated_at'] ).length === 0 } export default CheetahStore export { isIdOnlyPayload, CheetahStoreError }