Vue with dynamic routes and components - javascript

Trying to build a test app to see if Vue is a suitable replacment for our AngularJS app. Trying to learn Vue at the same time.
After the user logs in we fetch some roles for that user. Base off those roles is how the menu gets built.
User1 { Role1, Role2, Role3}
In theory
User2 {Role1, Role3}
So Role1 would have a path of /start/page1 and page1 (component) and two child components.
Same with Role2 path of /start/page2 and page2 would have components on it.
I don't really want to build the routes until I know which roles the user has.
I'm using quasar-framework.org and using the menu slide out. Trying to create a menu on the fly. Seems like I need the components to already be imported?
I'm able to build the menu by looping through the roles and setting up a list of menus.
Trying to build the routes on the fly using this.$router.addRoutes(newRoute);
To do that I need the component to already be imported.
The Quasar way is load the components on the fly I guess.
In router.js
function loadPage (component) {
return () => import(`../../pages/${component}.vue`)
}
I can't seem to use that function in a method section.
Is this possible in Vue?

Take a look at vue-router lazy loading documentation and Quasar lazy loading documentation
You can't do it in a method, but if the user permission don't match the route permissions the component is never loaded, which is basically what you want.
Example
const routes = [
{
path: '/some-page-protected',
component: () => import('pages/SomePage'),
meta: {role: 'admin'}
}
]
Or
const SomePage = () => ('pages/SomePage')
const routes = [
{
path: '/some-page-protected',
component: SomePage,
meta: {role: 'admin'}
}
]

Related

In Angular, how can one component to have multiple HTML templates?

I am developing an ecommerce application, and one major feature is that this app should have multiple themes. The total number of themes could be 100 or even more. However, these themes all have the same data (For example: all home page have same banner images, new product, feature product data.) .
I know I can use ng-template or TemplateRef to determine which piece of HTML should display. But since I have over 100 themes, both ng-template or TemplateRef methods will load a lot of extra files. So I think I need some sort of lazy load, when a component loads the data then lazy loads the correct HTML template. So how can I have this work?
Looks like it is possible, all our routes are handled by lazy loaded modules. This is our out-of-the-box route config:
const routes: Routes = [
{ path: '', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) }
];
While module lazy has this route config:
const routes: Routes = [
{ path: 'home', component: HomeComponent },
]
While HomeComponent is taken from the declarations of module lazy.
Then define another module, called for example lazy-two with the same route config, and its own HomeComponent.
Finally, you can switch between the modules by using this code:
lazyLoad() {
const routes: Routes = [
{
path: '',
loadChildren: () => import('./lazy-two/lazy-two.module')
.then(m => m.LazyTwoModule)
}
];
this.router.resetConfig(routes);
this.router.navigateByUrl('/home');
}
This will lazy load module lazy-two and refresh the route to /home - you will see the component of the new module displayed.
I couldn't create a stackblitz, some errors occurred probably because of lazy loading. So I ran it locally on my machine and pushed the code to GitHub
EDIT I managed to make a StackBlitz
I recommend used ComponentFactoryResolver to create the components that you need to render.
this.templates = [
{
id: "template-1",
component: Template1,
},
{
id: "template-2",
component: Template2,
},
];
ngOnInit() {
this.templates.forEach((element) => {
this.containerReference.createComponent(
this.factoryResolver.resolveComponentFactory(element.component)
);
});
}
in the .html you should have
<ng-container #containerReference><ng-container>
what about using the same component and styling it different when you select the template?

How to redirect to a specific page of Vue-app via Flask

I'm building an app that has a page which ends in '#' provides some meta info for the page without '#', for example if the page '/user/aabb' has info about the user 'aabb', then the page '/user/aabb#' is the meta page for that user.
The problem is, '/aabb' part doesn't really exist because the app is SPA. 'aabb' is simply delivered as a prop for the component used in '/user' routing. Nor I can directly access '/user/aabb#' in the same context.
So is there a way for Flask to render a specific page of a Vue-build app? so that if the user enters '/user/aabb' on the address bar it links into '/user' page with 'aabb' prop. If there is, I guess the following functionalities should be required.
Flask to redirect to a specific page inside of Vue-route.
Flask to send data to the vue-component of that page.
Vue to receive the data from Flask.
Or is there any other ways to solve this... issue?
Thanks in advance.
The solution to all your questions is to use Vue Router with HTML5 History Mode.
As I mentioned in your last question, set up your Flask app to use the Vue SPA as the front-end
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return app.send_static_file("index.html")
Then set up a router for your front-end URLs
// router.js
import Router from "vue-router"
import Vue from "vue"
Vue.use(Router)
export default new Router({
base: "/", // this should match the root path for your app
mode: "history",
routes: [{
name: "UserMeta",
path: "/user/:username#",
component: () => import("./path/to/UserMeta.vue"),
props: true
}, {
name: "User",
path: "/user/:username",
component: () => import("./path/to/User.vue"),
props: true
}]
})
You have to make the #-suffixed meta routes are listed before the normal pages in order to guarantee it doesn't think the username ends in #. See Matching Priority.
In the example above, both components receive the username route parameter as a prop.
You can then use one of the Data Fetching methods to load data into your components from your Flask API when your routes are loaded.
For example, using Fetching After Navigation and assuming you have a Flask app route for /api/user/<username>...
<template>
<div>
<div v-if="user">
<!-- show user details here -->
</div>
<div v-else>Loading...</div>
<//div>
</template>
<script>
export default {
name: "User",
props: { username: String },
data: () => ({ user: null }),
async created () {
const res = await fetch(`/api/user/${encodeURIComponent(this.username)}`)
this.user = await res.json()
}
}
</script>

How can I set the index page of my Gatsby site to be one of the dynamically generated pages?

I have a Gatsby site that queries information from a Wordpress REST API with GraphQL to dynamically create the site pages. I'd like to set my index page to be the homepage that is being created dynamically i.e home.html
I saw this post that was similar
On Gatsby CMS how can i set the about page as a index page
However, they have an about.js file that corresponds to their about page, meaning they can export it as a component and use it in index or they can even just copy the contents of that file over to index.js. The homepage that I want to set as my index is being generated dynamically and using a GraphQL query that can't be used outside of the page.js template. So I don't see an easy way to copy that over to another file.
I guess my last option would be to set my server to point to the static file in public/home.html and serve that as the site root, but the person in that posting tries to deter people from doing that.
Any ideas?
Here is page.js template that generates the pages of the site:
const PageTemplate = ({ data }) => (
<Layout>
{<h1 dangerouslySetInnerHTML={{ __html: data.currentPage.title }} />}
{
renderBlocks(gatherBlocks(data.currentPage.acf.page_blocks, data))
}
</Layout>
);
export default PageTemplate;
export const pageQuery = graphql`
query ($id: String!) {
currentPage: wordpressPage(id: {eq: $id}) {
title
id
parent {
id
}
template
acf {
page_blocks {
block_type {
acf_fc_layout
cs_title
cs_text
}
wordpress_id
}
}
}
}
`;
And here is my index page:
import React from "react"
import Layout from "../components/global/Layout"
const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<p>Welcome to the Tank Gatsby site.</p>
<p>Now go build something great.</p>
</Layout>
)
export default IndexPage
I experienced the same situation today. I used the following approach to use my dynamically created page with uri '/home'(fetched from wordpress using GraphQL query) as the home page of my Gatsby site:
Delete the default index.js file in your pages directory.
In gatsby-node.js file, change the uri
of page from '/home' to '/' just before using the CreatePage API.
Here is the sample code to achieve the desired result:
// loop through WordPress pages and create a Gatsby page for each one
pages.forEach(page => {
if(page.uri==='/home/')
page.uri = '/'
actions.createPage({
path: page.uri,
component: require.resolve(`./src/templates/${page.template.templateName}.js`),
context: {
id: page.id,
},
})
})
In the above code, pages refer to the pages fetched from WordPress using GraphQL.
I could not find an easy way to create index page programmatically. Made it work nonetheless, details below.
createRedirect is valid approach but might affect SEO and definitely affects E2E tests cause actual page content gets rendered with a small delay.
Another thing to consider is that having pages/index.js file is required in order to get index.html file generated on production build. This gets in the way of using createPage({ path: '/', ... cause in my case programmatically created index page was overwritten by the static one (made of pages/index.js). This looks like a bug to me (or rather not supported feature). Corresponding github issue.
looks like deletePage and createPage gatsby-node APIs work asynchronously, hence we have to delete index page created from static file and create the one we want in the same callback. Not 100% sure about this one, but that's my observation.
onCreatePage API is a good candidate since it gets called upon original index page creation and we can take that one out and replace it with the custom one, programmatically created.
There is a catch however - CreatePageArgs interface (unlike CreatePagesArgs) doesn't provide reference to graphql, hence fetching data might be tricky.
Final solution:
export function onCreatePage(args: CreatePageArgs): void {
const { page } = args;
if (page.path === '/') {
const { deletePage, createPage } = args.actions;
const indexPageComponentPath = path.resolve(
'./src/pages/index.tsx',
);
deletePage({
path: '/',
component: indexPageComponentPath,
});
createPage({
path: '/',
component: yourComponentPath,
});
}
}
There is a solution: use createRedirect in gatsby-node.js.
E.g.:
index.tsx
import React from 'react'
export default () => <></>
gatsby-node.js
...
exports.createPages = async ({ actions }) => {
const { createRedirect } = actions
createRedirect({
fromPath: '/',
toPath: '/home',
isPermanent: true,
redirectInBrowser: true,
})
}
...
I was able to address this by copying the contents of the page.js template into index.js , but instead of using a regular GraphQL query, which cannot be used outside of the page template, I used useStaticQuery instead and hardcoded the id of the index page I was retrieving data from.

Async components Vue 2

I'm trying to use async components. Here is my configuration:
Vue 2 using Single File Component approach
Webpack 2
Vue Router
The app is pretty basic, I have an "everyone" section contained in App and an "admin" section contained in Admin. I would like to load the component and all the .js related to the Admin if and only if I'm visiting the corresponding route.
After reading the vue-router docs on Lazy Loading, and the one of Vue2 on async components, I'm still not sure how to do that especially with the Single File Component approach.
Here is what I did for the moment but I don't know if it is ok since in the documentation of Vue2 they said :
Vue.component(
'async-webpack-example',
() => import('./my-async-component')
)
Also what do I have to do with webpack so it creates a chunk of everything related to Admin so that adminChunk.jsis just loaded when reaching admin route ?
What is the syntax to make a single file component a async component ?
app.js
const Admin = resolve => {
// require.ensure is Webpack's special syntax for a code-split point.
require.ensure(['./components/admin/Admin.vue'], () => {
resolve(require('./components/admin/Admin.vue'))
})
};
const routes = [
{ path: '/', component: App },
{ path: '/admin', meta: { requiresAdmin: true }, component: Admin},
];
Admin.vue
<template>
<admin-menu></admin-menu>
<child></child>
</template>
<script>
import AdminMenu from './Admin-Menu.vue'
import Child from './child.vue
export default{
data () {
},
components: {
AdminMenu,
Child,
},
}
</script>
You can pass a third parameter to the require.ensure function with the name of the chunk.

How can I get vue-router data into the parent template?

I have 2 route components:
1) A people product list component 2) A product detail component
The list shows the products and then there is a router-link to that product using history in the router definition/scope.
What I am trying to achieve is to get the data from the parent list routes into the child detail product template.
So I am working with vuex as I am storing the data in the store method. Below is the gist example with the setup I have got.
https://gist.github.com/mdunbavan/5cb756ff60e5c5efd4e5cd332dcffc04
The PeopleListing component works well and when clicking the router link it goes to the correct url and the data in the vue debug looks okay for vuex as below:
state:Object
currentProduct:undefined
products:Array[0]
route:Object
from:Object
fullPath:"/products/copy-of-long-shirt-dress-tudor"
hash:""
meta:Object (empty)
name:"product"
params:Object
path:"/products/copy-of-long-shirt-dress-tudor"
query:Object (empty)
What I am trying to do is basically render the data so it shows the router data within the homepage still and not look like it is going to another page which is what it is doing at the moment. The journey I am looking to create is as follows:
1) index page renders the PeopleListing
2) when clicking the it opens some animation within that template
3) the animation then renders the data from that clicked route such as '/products/the-product-title'
On point 3) we have to try and get all data attributes from that object.
Is it possible with the setup that I have got in my gist?
Thanks in advance!!
'product/:handle' is a child route for the '/' route, as you want to nest them.
Your router should look like this.
As you want to pass data to your route using params.
When props is set to true, the route.params will be set as the component props.
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/', component: PeopleListing,
children: [
{ name: 'product', path: '/products/:handle', component: ProductDetail, props: true }
]
},
]
});

Categories

Resources