javascriptvue.jsgolden-layout

Programmatically add imported vue to Golden-Layout


I'm trying to programmatically add an imported vue to a new Golden Layout pane. I've use the drag and drop example to build upon, but it is only adding HTML to the pane, not a vue that requires props passed to it, such as the pane width and height. https://golden-layout.com/tutorials/dynamically-adding-components.html

I've been trying to add it via the layout.registerComponent(), with containter.on("open", funtion, ect. but can't find:

  1. the element to mount the vue onto.
  2. the new dragged-and-dropped pane width and height.

As far as the docs say, on "open" should fire after the new pane/container is created. https://golden-layout.com/docs/Container.html#Events

However, as I've (erroneously?) implemented it the pane/container is not yet added when on "open"

The same vue is added to the default layout upon layout.init(); without an issue and works flawlessly.

But adding the same vue via the drag and drop method (as currently implemented) does not work and ends up with the following:

[Vue warn]: Cannot find element: #priceChart2

I've been doing my research and so far, can't figure out the solution.

import "./goldenlayout-base.css";
import "./goldenlayout-dark-theme.css";
// required for dynamic component instantiation from the config
import Vue from "vue";
import $ from "jquery";
import GoldenLayout from "./goldenlayout.js";

// import a price chart
import PriceChart from "src/components/TradingVueJs/Chart.vue"

let chartCnt = 1

export default {
  name: "Trader",
  props: {},
  components: {},
  computed: {},
  data() {
    return {};
  },
  mounted() {
    var config = {
      content: [
        {
          type: "column",
          isClosable: true,
          reorderEnabled: true,
          title: "",
          content: [
            {
              type: "stack",
              width: 100,
              height: 50,
              isClosable: true,
              reorderEnabled: true,
              title: "",
              activeItemIndex: 0,
              content: [
                {
                  type: "component",
                  componentName: "priceChart",
                  componentState: {
                    text: "Price Chart",
                  },
                  isClosable: true,
                  reorderEnabled: true,
                  title: "Chart",
                  id: "Chart1"
                },
              ],
            },
            {
              type: "stack",
              header: {},
              isClosable: true,
              reorderEnabled: true,
              title: "",
              activeItemIndex: 0,
              height: 50,
              content: [
                {
                  type: "component",
                  componentName: "example",
                  componentState: {
                    text: "Some message",
                  },
                  isClosable: false,
                  reorderEnabled: true,
                  title: "Example",
                  id: "Example",
                },
              ],
            },
          ],
        },
      ],
    };

    var layout = new GoldenLayout(config, $("#layoutContainer"));

    layout.registerComponent("example", function(container, state) {
      container.getElement().html("<h2>" + state.text + "</h2>");
    });


   layout.registerComponent("priceChart", function(container, state) {
      
      let id = "priceChart"+chartCnt++
      container.on("open", () => {
        container.getElement().html("<div id='"+id+"' class='chartComponent'></div>");
        // mount price chart component
        const chart = Vue.extend(PriceChart);
        const Chart = new chart({ 
          propsData: { 
            id: id,
            width: container.width, 
            height: container.height 
            } 
        });
        Chart.$mount('#'+id)
      })
    });

    //  Update GL on window resize
    window.addEventListener("resize", () => {
      layout.updateSize();
    });

    // attach the state change listener
    layout.on("stateChanged", () => {
      this.onLayoutStateChanged(layout.toConfig());
    });

    layout.init();

    var addMenuItem = function(component, title, text) {
      var element = $("<li>" + text + "</li>");
      $("#menuContainer").append(element);

      var newItemConfig = {
        title: title,
        type: "component",
        componentName: component,
        componentState: { text: text },
      };

      layout.createDragSource(element, newItemConfig);
    };

    addMenuItem("priceChart", "BTC/USDT", "Price Chart");
    addMenuItem("example", "Add me!", "You've added me!");
    addMenuItem("example", "Me too!", "You've added me too!");
  },

  methods: {
    resetLayout() {
      window.location.reload(true);
    },
    onLayoutStateChanged(state) {
      var layoutState = JSON.stringify(state, null, 2);
      // eslint-disable-next-line no-console
      console.log("changed state", layoutState);
    }
  }
};
<template>
  <!--  this is the golden-layout container where all the vue components will be contained -->
  <div id="wrapper">
    <ul id="menuContainer"></ul>
    <div id="layoutContainer"></div>
  </div>
</template>


Solution

  • In the end I had to use a setInterval() to wait for the element to appear in the DOM.

    I found a great solution here:

    https://gist.github.com/chrisjhoughton/7890303#gistcomment-2638757

    /**
     * Wait for the specified element to appear in the DOM. When the element appears,
     * provide it to the callback. If waiting times out, send null to the callback.
     *
     * @param selector a jQuery selector (eg, 'div.container img')
     * @param callback function that takes selected element (null if timeout)
     * @param maxtries number of times to try (return null after maxtries, false to disable, if 0 will still try once)
     * @param interval ms wait between each try
     */
    waitForEl(selector, callback, maxtries = false, interval = 100) {
      const poller = setInterval(() => {
        const el = jQuery(selector)
        const retry = maxtries === false || maxtries-- > 0
        if (retry && el.length < 1) return // will try again
        clearInterval(poller)
        callback(el || null)
      }, interval)
    }