summaryrefslogtreecommitdiff
path: root/public-src/golden-layout-vue.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'public-src/golden-layout-vue.jsm')
-rw-r--r--public-src/golden-layout-vue.jsm163
1 files changed, 163 insertions, 0 deletions
diff --git a/public-src/golden-layout-vue.jsm b/public-src/golden-layout-vue.jsm
new file mode 100644
index 0000000..92c36fb
--- /dev/null
+++ b/public-src/golden-layout-vue.jsm
@@ -0,0 +1,163 @@
+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;
+};