import Vue from 'vue'; import LayoutManager from 'golden-layout'; /** * `this` will be set to the VueComponentHandler instance. */ function onOpen() { /* mount the Vue instance *inside* of _container.getElement(). * To do that we need to create a temporary element inside of * it for the Vue instance to replace. */ const tmp = document.createElement('div'); this._container.getElement()[0].appendChild(tmp); this._vueInstance.$mount(tmp); } /** * `this` will be set to the VueComponentHandler instance. */ function onDestroy() { this._vueInstance.$destroy(); this._container.off('open', onOpen, this); this._container.off('destroy', onDestroy, this); } function deepCopySanitize(obj) { if (obj === undefined) return undefined; return JSON.parse(JSON.stringify(obj)); } /** * A specialised GoldenLayout component that binds GoldenLayout container * lifecycle events to Vue components */ class VueComponentHandler { /** * @param {Function} the Vue component constructor/class that we're wrapping * @param {lm.container.ItemContainer} container as passed by GoldenLayout * @param {Object} state as passed by GoldenLayout */ constructor(vueComponentClass, container, state) { this._container = container; /* Get the state directly from the container, rather * than trusting the `state` argument; `state` has * `config.componentName` injected in to it, poluting * our Vue instance's `$data`. * * But, the `state` argument was a deep copy of the * real state; we still need it to be a copy, so that * data-propagation doesn't bypass our $watch. * * See golden-layout/src/js/items/Component.js:lm.items.Component(). */ const realState = deepCopySanitize(container.getState()); var options = {}; Object.assign(options, container.layoutManager._vueOptions); if (container._config.vueOptions) { Object.assign(options, container._config.vueOptions); } if (realState) { options.data = Object.assign(options.data || {}, realState); } this._vueInstance = new vueComponentClass(options); // add Vue -> GoldenLayout event translator this._vueInstance.$watch('$data', (newData, oldData) => { this._container.setState(deepCopySanitize(newData)); }, { deep: true }); // add GoldenLayout -> Vue event translators this._container.on('open', onOpen, this); this._container.on('destroy', onDestroy, this); } } LayoutManager.prototype._vueInit = function() { if (!this._vueInitialized) { this._components["lm-vue-component"] = VueComponentHandler; this._vueOptions = {}; this._vueInitialized = true; } }; /** * Register a Vue component for use with this GoldenLayout * LayoutManager. * * The component can then be used just like any other GoldenLayout * component. * * - The `componentState` config item is bound with the Vue * component's `$data`; the component's private data will be * reflected in `layoutManager.toConfig()`, allowing the component * state to be persisted as part of the layoutManager state. * * - The `vueOptions` config item becomes the Vue component's options. * This is merged with any global Vue options set with * `.setVueOptions()`, and obviously `componentState` becomes * `vueOptions.data`. This MUST be a "plain" JSON-encodable object, * and cannot contain complex objects like a Vuex.Store. * * @public * @param {String} name Like `Vue.component()` registration, the * `name` argument is optional if the component * itself has a default name. * @param {Function|Object} component Like `Vue.component()` * registration, this can be a * constructor function, or an * options object (in which case it * will be automatically turned in * to a constructor function by * passing it to `Vue.extend`). * @returns {void} */ LayoutManager.prototype.registerVueComponent = function(name, component) { this._vueInit(); if (component === undefined) { component = name; name = component.name; } if (!name) { throw new Error('Cannot register a component without a name'); } if (!component.name) { component.name = name; } if (typeof component !== "function") { component = Vue.extend(component); } class ThisVueComponentHandler extends VueComponentHandler { constructor(container, state) { super(component, container, state); } } this.registerComponent(name, ThisVueComponentHandler); }; /** * Set the Vue options. * * You may be wondering why this is a separate function, instead of * simply relying on the `vueOptions` field to the settings object. * The settings object *must* be a "plain" JSON-type object; it can't * include complex objects, like a Vuex.Store (it would actually stack * overflow if we tried putting a Vuex.Store in the settings object). * * @public * @param {Object} options * * @returns {void} */ LayoutManager.prototype.setVueOptions = function(options) { this._vueInit(); this._vueOptions = options; };