Vue 3 - Load components based on backend setup - vue.js

I would like to setup a dashboard in vue 3 where the admin can customize the template a bit. In fact he should been able to define the sorting of components. At the moment the dashboard loads its components the default way in one column:
template:
<ComponentA />
<ComponentB />
<ComponentC />
Script:
import ComponentA from '#assets/components/componentA';
import ComponentB from '#assets/components/componentB';
import ComponentC from '#assets/components/componentC';
The backend sorting for the components is not yet finally written so i'm still very flexible it could be an array of the component names but i will be able to set this based on the Vue requirements. Unfortunately i did not found anything regarding such a functionality to "load components based on a custom configuration" and not "hardcoded".
can please anyone point me into the right direction how to archive such a functionality? Maybe with some small explanation. I've tried already to build something via the methods but all i've got was compiling errors...
In fact an admin should be able to set another sorting like:
<ComponentC />
<ComponentA />
<ComponentB />
Additional note:
All other parameters for the frontend are saved in a global "moduleConfig" object which is defined in the index file - thats because it will be filled by PHP. There is no need to change the dashboard in realtime, on page load all settings are given, so no update hooks are required. My target is to save the wanted struct there also in form of an array or so (as i said not fix defined yet).
Maybe:
let moduleConfig = {
...
dashboardLayout: ['ComponentA','ComponentC','ComponentB'],
...
}

Related

Vue component communication between header component and components in the router-view

Im facing a problem for my VUE app, Im using the vue Router to navigate to my component
In my Header component I use router-link to navigate to a Home component
The problem is :
In my Header component I would like a checkBox (a boolean variable) that change the content of my Home component (rendered in the router-view) like a v-if that would check the boolean variable in the Header
Here is my App.vue template I was trying to solve the problem through emits but Im Kinda stuck for passing data inside a component (inside the router-view)
<template>
<div class="content">
<HeaderComponent #eventCheckBox="handleCheckBox" />
<router-view />
<FooterComponent />
</div>
Do you guys have already faced this issue, is there a way to do it the classic way or should I try plugins like Portal or Teleport ?
Portal and Teleport are the same, just a different name (teleport in Vue3, being the official name).
As of the rest of the question, this explains it very well: https://stackoverflow.com/a/49702934/8816585
Mainly, you need to see if you need to use a store like Pinia (recommended one) and Vuex. Most of the time, you could use a parent/child relationship. Here is another article explaining when you would need something like that: https://markus.oberlehner.net/blog/should-i-store-this-data-in-vuex/#alternatives-to-storing-data-in-vuex
In Vue3, you could even not use a store and rely solely on the singleton pattern + composables (again, a lot of articles are available for that one).
TLDR: use your Vue devtools and inspect the state flowing.
If you need more, reach for more powerful tools.

Gridsome: How to add Vue.use(MyPlugin) for a single page only?

I have added ReCaptcha plugin using Vue.use(ReCaptcha, {some options}) in Gridsome main.js which is displaying on all pages.
How to add plugin for a particular page only in gridsome?
I've never used ReCaptcha or Gridsome before, but generally, if you install a plugin using npm, then the simplest way to use it on a single page would be to import the plugin to the specific component rendered on the route you want to use it on. i.e
/* === MyComponent.vue === */
<script>
import plugin from 'packageName';
// or
import { pluginExport } from 'packageName';
export Default{
// You can then use the plugin/pluginExport here
}
From there you should be able to use the package in that specific component as you normally would if you implemented it app-wide with Vue.use. In some cases, depending on how the plugin is meant to be used, you may need to register the imported plugin Module as a child component in the components object. Like this vuejs QR Code generator for example.

How to render a custom component into a map's marker popup?

I am using a Vue plugin component Timeago, and i want to show a timeago time in the content of a map plugin. A super-simplified example to illustrate the usage would be something like:
let popup = L.responsivePopup().setContent(`
<h1>Hello world</h1>
<p>A thing happened <Timeago datetime="${datetime}"></Timeago></p>
`);
L.marker([lat, lon]).bindPopup(popup).addTo(this.map);
According to this answer, and Vue's documentation, i should be able to compile this using Vue.compile(), but i am not understanding the concept.
First, there is no explanation on what a "full build" is. How can i tell if what i have is a "full build"? Searching for "vuejs full build" doesn't return anything that is being referred to as literally that phrase, even though they use it in the documentation there. All i know is that when i try to call Vue.compile() with having imported import Vue from 'vue', it complains saying:
TypeError: vue__WEBPACK_IMPORTED_MODULE_6__.default.compile is not a function
So i don't know where to go from there.
And then second thing is (assuming the first thing gets sorted), will i have to make my HTML hold an empty div with a specific id, wait for it to render, and then call the Vue.compile() on it, since the sample code there runs with .mount() on an element id? Because that seems a little "incorrect", having seemingly an extra step. Or is that the only way to make this scenario work?
If there is an alternative simpler wait of making this work, like getting the Timeago component to just return the rendered string only, such as 2 hours ago that i can incorporate into my string literal, that would work for me as well. Either way is fine.
Vue has differents build of it's package, full means that package can compile templates and and run it. All build types can be found here https://v2.vuejs.org/v2/guide/installation.html#Explanation-of-Different-Builds
Vue.compile allows you to use render functions, which allow you to manipulate DOM and create elements in a programmatic way. Here you can find Vue documentation about it https://v2.vuejs.org/v2/guide/render-function.html
About the issue you are facing, you can create a vue component and put that popup inside it, this component when render will render the timeago component.
<template>
<h1>Hello world</h1>
<p>A thing happened <Timeago datetime="${datetime}"></Timeago></p>
</template>
import Timego from './Timeago.vue'
export default {
components: {
Timeago
},
props: {
datetime: String
}
}

Preserve component state with vue-router?

I have a listing/detail use case, where the user can double-click an item in a product list, go to the detail screen to edit and then go back to the listing screen when they're done. I've already done this using the dynamic components technique described here: https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components. But now that I'm planning to use vue-router elsewhere in the application, I'd like to refactor this to use routing instead. With my dynamic components technique, I used keep-alive to ensure that when the user switched back to the list view, the same selection was present as before the edit. But it seems to me that with routing the product list component would be re-rendered, which is not what I want.
Now, it looks like router-view can be wrapped in keep-alive, which would solve one problem but introduce lots of others, as I only want that route kept alive, not all of them (and at present I'm just using a single top level router-view). Vue 2.1 has clearly done something to address this by introducing include and exclude parameters for router-view. But I don't really want to do this either, as it seems very clunky to have to declare up front in my main page all the routes which should or shouldn't use keep-alive. It would be much neater to declare whether I want keep-alive at the point I'm configuring the route (i.e., in the routes array). So what's my best option?
You can specify the route you want to keep alive , like:
<keep-alive include="home">
<router-view/>
</keep-alive>
In this case, only home route will be kept alive
Vue 3
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
Exactly as is, you don't need a Component attribute in the App.vue. Also your this'll work even if your components don't have a name property specified.
I had a similar problem and looked at Vuex but decided it would require too much changes/additions in my code to add to the project.
I found this library https://www.npmjs.com/package/vue-save-state which solved the problem for me, keeping the state of 1 component synchronized with localStorage, and it only took a few minutes and a few lines of code (all documented in the Github page of the package).
One solution without localStorage:
import {Component, Provide, Vue} from "vue-property-decorator";
#Component
export default class Counter extends Vue {
#Provide() count = 0
/**
* HERE
*/
beforeDestroy() {
Object.getPrototypeOf(this).constructor.STATE = this;
}
/**
* AND HERE
*/
beforeMount() {
const state = Object.getPrototypeOf(this).constructor.STATE;
Object.entries(state || {})
.filter(([k, v]) => /^[^$_]+$/.test(k) && typeof v !== "function")
.forEach(([k]) => this[k] = state[k]);
}
}
What seems to me is you are looking for some kind of state management. If you have data which is shared by multiple components and you want to render component in different order, but dont want to load data again for each component.
This works like following:
Vue offers a simple state management, but I will recommend to use Vuex which is a standard for state management among vue community.

Aurelia top level component without routing

Is it possible to add an Aurelia top level component without the router?
The goal is to create a component without the router since my application doesn't need any url based navigation.
From what I can tell Aurelia seems to take you down a path where components are instantiated via routing based on how the component is registered with the router.
Instead I would like to just add markup for the top level component on the main index.html page:
<my-component bind.current="'123456'"></my-component>
I would like define components without a router and only use the templating and data binding capabilities of Aurelia.
Is that possible?
Tried this in index.html (inside the body tag of the default project)
<require from='./dist/my-component'></require>
<my-component></my-component>
But it does not seem to pick it up. Ideally I would like to just define it in markup on a page served from the server since it would enable me to sett attributes dynamically on the elements
<my-component current.bind={{someServerGeneratedId}}></my-component>
In the above I would use a templating framework like mustache to dynamically render the Aurelia when the page is served from the server.
I could wrap the component in another "landing" component, but that makes it hard to benefit from setting things up with server generated bindings.
UPDATE:
Per Rob's response: https://github.com/aurelia/framework/issues/175#issuecomment-126965417
- He is expecting to add the ability to add a root component to the landing page in a future release. I understand there are ways to not use the router, but it still depends on pulling in a partial view during bootstrapping of the application. This does not use the router directly, but conceptually this is really just an implied/convention based client side nav. In the end there is a client side request to pull in the view, which means I can't generate the html dynamically from the initial server response.
Yes you can do this very easily without the router. Just remove the router configuration from your app.js and in app.html remove the router code there as well.
I think the issue you are running in to is that you are specifying the dist folder again in your index.html. You should just reference it like this -
<require from="my-component"></require>
<my-component current.bind="someServerGeneratedId"></my-component>
This will bind up correctly.
I guess you're missunderstanding the route concept here.
At the time of writing, Aurelia's index.html page is your initial page where you put your "loading" stuff and where Aurelia bootstraps the entire app.
So, you can't put a custom component directly on it, but that should not be a problem.
If you don't change any configuration on Aurelia, it will look for your app.html to bootstrap your app, and there you can have anything you want (routes or not, doesn't matter). So, you should put your component there, beside the other tags/components/etc you need.
I've made a plunker without any routing and with a custom component in the app.html, and something simulating what you need.
<template>
<require from='./my-component'></require>
<my-component current.bind="serverGeneratedID"></my-component>
</template>
http://plnkr.co/edit/mLb8Ym638b4V2e9LDp0A?p=preview
If you need anything else, comment here and I'll try to go further.