Asked
Active
Viewed 1,200 times
2
I have a small Vue app that loads components dynamically
First I write a func called SPA, this func is responsible for creating the main app and handle loading components ```js /* * param {object} params: { * {CSS selector/HTMLElement} el: required, an existing DOM element to Vue mount on * {CSS selector} componentEl: required, placeholder element in "el" for Vue component * {CSS selector/HTML string} initComponentTemplate: optional, default Vue component template when initialize app * {object} data: optional, data for Vue instance * {object} methods: optional, methods for Vue instance * } * returns {SPA} * example create new SPA object window.exampleSPA = new SPA({ el: "#app-view", componentEl: "#app-view-component" }); */ var SPA = function(params) { var app = this; /* * Construct params for Vue instance */ var vmConstruct = { el: null, data: function() { var fullData = { /* * Current component * currentView presents for current child component In this, props technical used to pass data around components, https://v2.vuejs.org/v2/guide/components.html#Props currentItem will contain data pass form parent to child component and vice-versa Vue will send data in currentItem to child component whenever having a component rendered into this. In components, we get data sent from parent via props "passingMessage". Vue will also listen "pass" event to get data from child component then set to currentItem, the "pass" event is emitted by child component aims to pass data back to parent. */ currentView: "init", // current data passed to component currentItem: {} }; // add properties from SPA initialization if (!params.hasOwnProperty("data")) { return fullData; } for (var prop in params.data) { if (fullData.hasOwnProperty(prop)) { continue; } fullData[prop] = params.data[prop]; } return fullData; }, computed: {}, created: function() {}, mounted: function () { this.$nextTick(function () { // Code that will run only after the entire view has been rendered }); }, methods: { /* * Set data for currentItem, * usually, data get from child component will be bind to currentItem * param {object} params */ setCurrentItem: function(params) { this.currentItem = params; } } }; if (params.el === 'undefined') { throw new Error("Missing param el"); } vmConstruct.el = params.el; //assume params.el is HTMLElement if (params.el.charAt(0) === "#") { //params.el is CSS selector vmConstruct.el = document.querySelector(params.el); } //check for existing of component element, //replace it with a fixed template if (params.componentEl === 'undefined') { throw new Error("Missing param componentEl"); } var componentTemplate = ''; if (params.componentEl.charAt(0) !== "#") { throw new Error("Param componentEl invalid, must be CSS selector"); } var componentEl = vmConstruct.el.querySelector(params.componentEl); if (componentEl === null) { throw new Error('Placeholder element in "el" for Vue component not found'); } componentEl.innerHTML = componentTemplate; //Create the 'default' component var initComponentTemplate = ''; if (typeof params.initComponentTemplate === "string") { initComponentTemplate = params.initComponentTemplate; } Vue.component("init", { template: initComponentTemplate }); if (params.hasOwnProperty("methods")) { for (var prop in params.methods) { if (vmConstruct.hasOwnProperty(prop)) { continue; } vmConstruct.methods[prop] = params.methods[prop]; } } /* * Create view model */ this.vm = new Vue(vmConstruct); //require lib public/libs/base/js/script.js this.assets = new Admin.assets(); }; /* * Change the page to target component * param {object} params: { * {string} tagName * } */ SPA.prototype.setCurrentView = function(params) { //And then change the page to that component this.vm.currentView = params.tagName; }; /* * Dynamically components loading * param {object} params: { * {anchor DOM object} anchor * {string} action * } */ SPA.prototype.getComponent = function(params) { var app = this; var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { //Load new template and script in it var templateContainer = app.getTemplateContainer(); templateContainer.innerHTML = xmlhttp.responseText; var responsePage = templateContainer.querySelector('title'); if (responsePage) { var redirectAnchor = document.createElement("a"); redirectAnchor.href = xmlhttp.responseURL; window.location.href = redirectAnchor.origin + redirectAnchor.pathname; } var links = templateContainer.querySelectorAll('link'); app.assets.load({links: links}); var scripts = templateContainer.querySelectorAll('script'); app.assets.load({scripts: scripts}, function(error, result) { app.setCurrentView({tagName: params.action}); }); } }; xmlhttp.open("get", params.anchor.href); xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlhttp.send(); }; /* * Set SPA component * We use DOMElement of HTML a tag as "params" object * The property "pathname" in attribute "href" will be used to parse component ID * Example pathname "/example/path" will be parsed into component ID "example-path", * so in Vue component setup, we need to create component with: * Vue.component("example-path", {...}); * param {anchor DOM object} params: { * ... * {string} pathname: "/example/path" * ... * } */ SPA.prototype.setComponent = function(params) { var action = params.pathname.replace(/\//g, "-").substr(1); if (!(action in Vue.options.components)) { this.getComponent({anchor: params, action: action}); } else { this.setCurrentView({tagName: action}); } }; /* * param {object} params */ SPA.prototype.getTemplateContainer = function(params) { var app = this; var templateSelector = 'spa-template-container'; var templateContainer = document.querySelector('#'+templateSelector); if (templateContainer === null) { templateContainer = document.createElement("div"); templateContainer.setAttribute("id", templateSelector); templateContainer.style.display = "none"; app.vm.$el.appendChild(templateContainer); } return templateContainer; }; ``` Then I write a global component that will get from server and run on demand ```html Content Vue.component("component-name", { template: "#component-name", ..... ..... }); ``` I listen click event to load Components, each time I need to load a component, I call *spa.setComponent*
As you can see in SPA func how I load component
I tested for loading script sequence but it is ok, component created before I set currentView.
Now I'm stuck
My source may not good but please focus on the problem, I need to understand what happened and fix that.
Many thanks for any help!
tony19
- 125,647
- 18
- 229
- 307
carboncrystal
- 63
- 1
- 10