Change Vue view by object rather than url router - javascript

Writing a chrome extension with Vue.js and I need to change the views based on user action.
Typically, I would use the Vue router to auto-magically abstract all this logic away... however the router is bound to the URL.
Is it possible to change this behaviour in any way?
i.e, the <router-view/> is in App.vue as normal, but as the user clicks through the extension the router is not watching the URL in the browser address bar but instead say some other object?

Dynamic Components
Vue router is specifically for the url bar. You can use dynamic components when you want to switch a view component based on some other data. It uses a <component> tag with an is attribute:
<component :is="currentTabComponent"></component>
From the docs:
In the example above, currentTabComponent can contain either:
the name of a registered component, or
a component’s options object
Here's a demo where the variable currentTabComponent contains the name of the component:
new Vue({
el: "#app",
components: {
Home: { template: `<div class="component"><h1>Home</h1> The home component</div>` },
About: { template: `<div class="component"><h1>About</h1> An about component</div>` },
},
data() {
return {
currentTabComponent: 'Home'
}
},
methods: {
toggle() {
this.currentTabComponent = this.currentTabComponent === 'Home' ? 'About' : 'Home';
}
}
});
.component {
border: 3px solid #cccccc;
margin: 10px;
padding: 12px;
}
<div id="app">
<component :is="currentTabComponent"></component>
<button #click="toggle">Toggle</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Related

Why is my svelte js router link component breaking routing in my demo SPA project when trying to use a slot?

Iam new to Svelte, and JS in general. Iam building a demo dashboard project to get a grasp on Svelte after going through the framework documentation. For this, Iam not using any router for this project, just some sample code I took from a medium post to build a simple and minimal router using the svelte store.
In my App, I have 2 page components: Home.svelte and About.svelte.
I have a router.js file like this:
import Home from '#pages/Home.svelte';
import About from '#pages/About.svelte';
import { writable } from 'svelte/store';
const router = {
'/': Home,
'/home': Home,
'/about': About
}
export default router;
export const curRoute = writable('/home');
In my App.svelte, I have :
<script>
import router, { curRoute } from "#router/router";
onMount(() => {
curRoute.set(window.location.pathname);
if (!history.state) {
window.history.replaceState(
{ path: window.location.pathname },
"",
window.location.href
);
}
});
</script>
<svelte:component this={router[$curRoute]} />
I have a text link component, that works fine when a use it:
<script>
import { curRoute } from "#router/router";
export let page = {
path: "/home",
name: "Home"
};
function redirectTo(event) {
// change current router path
curRoute.set(event.target.pathname);
// push the path into web browser history API
window.history.pushState(
{ path: page.path },
"",
window.location.origin + page.path
);
}
</script>
<style>
a {
text-transform: uppercase;
text-decoration: underline;
padding: 1rem;
}
</style>
<a href={page.path} on:click|preventDefault={redirectTo}>{page.name}</a>
At some point, I need the same component, but able to wrap some additional markup, like a card, or any other element. That's where I thought I would use the svelte slots feature. Therefore, I built a second component very similar to the one above, but with a slot, instead of the {page.name} variable. The component is like this:
<script>
import { curRoute } from "#router/router";
export let page = {
path: "/home",
name: "Home"
};
function redirectTo(event) {
// change current router path
curRoute.set(event.target.pathname);
// push the path into web browser history API
window.history.pushState(
{ path: page.path },
"",
window.location.origin + page.path
);
}
</script>
<style>
a {
text-transform: uppercase;
text-decoration: underline;
padding: 1rem;
}
</style>
<a href={page.path} on:click|preventDefault={redirectTo}>
<slot>{page.name}</slot>
</a>
the {page.name} variable is supposed to be used as a default value when no slot content is provided. Iam able to import and use the component. Here are the two components side by side.
<RouterLink page={{path: '/about', name: 'About'}} />
<RouterLinkSlot page={{path: '/about', name: 'About'}}>
<span>A nice slotted link</span>
</RouterLinkSlot>
But when I click on the rendered link on the frontend, it doesn't throw any error, but doesn't load the related page component and the page area remains blank, whereas it works with the non slot version. Tried to search for an answer but couldn't find any.
I see this is old but the issue is in event.target.pathname. That's only available when the click target is an <a> tag. When you add a <span> tag, via the slot, that value became undefined. Instead you should just refer to the page.path value that you've exported.
curRoute.set(page.path);
You won't need event in this case.

Vuejs hide component on signup page with v-if using quasar framework

I am using quasar drawer and i want to hide the drawer on signup view, with my current code it does hide but the problems is that when i reload the signup page it still renders in the DOM in a few milliseconds and then drawer disappear.
I don't know how to fix it not seeing the drawer render in the DOM at all, or maybe have control on the drawer, set it to false by default and explicitly render it manually on other routes.
<template>
<div>
<q-header elevated class="bg-indigo-4">
<q-toolbar>
<q-toolbar-title class="flex flex-center"></q-toolbar-title>
</q-toolbar>
</q-header>
<q-drawer
v-model="drawer"
v-if="!$route.meta.hideDrawer"
:width="300"
:breakpoint="400"
>
<q-scroll-area
style="height: calc(100% - 150px); margin-top: 150px; border-right: 1px solid #ddd"
>
<drawer-navigation></drawer-navigation>
</q-scroll-area>
<drawer-header></drawer-header>
</q-drawer>
</div>
</template>
<script>
import DrawerNavigation from "#/components/navigation/DrawerNavigation";
import DrawerHeader from "#/components/navigation/DrawerHeader";
export default {
components: {
DrawerNavigation,
DrawerHeader
},
data() {
return {
drawer: true
};
}
};
</script>
router/index.js
{
path: "/employers/signup",
name: "EmployersSignup",
component: () =>
import(/* webpackChunkName: "about" */ "../views/EmployerSignup.vue"),
meta: { hideDrawer: true }
},
Try to make another variable like isDrawerHidden: false and place in the v-if of the q-drawer
and use a watcher for the !$route.meta.hideDrawer and assing to the variable isDrawerHidden the value of the watched element

Mount component into DOM with JS in Vue.js

I'm making a GUI for a webgame with Vue.JS, now I'm using http-vue-loader to avoid using Webpack and similar (sorry but I really hate it).
Now, usually what I do for get my component into my DOM is this:
GUI.js
// Creating Vue.js istance for GUI
Pixua.GUI = new Vue({
el: '#GUI',
components: {
'Debug': httpVueLoader('./gui/windows/Debug.vue')
},
data: {
row: 0,
column: 0
}
});
Index.html
<div id="GUI">
<windows id="windows">
<debug v-bind:row="row" v-bind:column="column" ></debug>
</windows>
</div>
Debug.vue (it's my component)
<template>
<px-window title="Debug" :width="200" :is-open.sync="isOpen">
Casella cliccata:
<ul>
<li>Riga: {{row}}</li>
<li>Colonna: {{column}}</li>
</ul>
</px-window>
</template>
<script>
module.exports = {
props: ['row','column'],
data: function() {
return {
isOpen: true
}
}
}
</script>
<style>
</style>
Now my question is: is possible to get the component mounted/rendered into the DOM dinamically with JS (via GUI.js) and without specifying it into the index.html (so without <debug v-bind:row="row" v-bind:column="column" ></debug>)?
You can use Dynamic components
<component v-bind:is="myComponent"></component>
And then change the value of myComponent to the component you want to load.
data() {
return {
myComponent: 'debug'
}
}
Will render the debug component

vue-router replace parent view with subRoute

I'm using the vue-router and have a question regarding subRoutes. I would like to set up my routes so the main routes are listings and the subRoutes are things like edit/add/etc.
I want the subRoute components to replace the <router-view> of the parent route. The way I understand the documentation and from what I've tested, it looks like I should define another <router-view> in the parent components template for the subRoute to render into but then the user-list would remain visible.
Example routes:
'/users': {
name: 'user-list',
component(resolve) {
require(['./components/users.vue'], resolve)
},
subRoutes: {
'/add': {
name: 'add-user',
component(resolve) {
require(['./components/users_add.vue'], resolve)
}
}
}
}
Main router view:
<!-- main router view -->
<div id="app">
<router-view></router-view>
</div>
User list:
<template>
<a v-link="{ name: 'add-user' }">Add</a>
<ul>
<li>{{ name }}</li>
</ul>
</template>
Add user:
<template>
<div>
<a v-link="{ name: 'user-list' }">back</a>
<input type="text" v-model="name">
</div>
</template>
When I click on "Add", I want to be filled with the add-user template. Is this possible?
Also, can I establish a parent-child relationship between the user-list and add-user components? I would like to be able to pass props (list of users) to the add component and dispatch events back up to the user-list.
Thanks!
It sounds like those edit/add routes should not be subRoutes, simple as that.
just because the path makes it seem like nesting doesn't mean you have to actually nest them.
'/users': {
name: 'user-list',
component(resolve) {
require(['./components/users.vue'], resolve)
}
},
'users/add': {
name: 'add-user',
component(resolve) {
require(['./components/users_add.vue'], resolve)
}
}
So I played around with this for quite a bit and finally figured out how this can be achieved. The trick is to make the listing a subRoute too and have the root-level route just offer a <router-view> container for all child components to render into. Then, move the "list loading" stuff to the parent component and pass the list to the list-component as a prop. You can then tell the parent to reload the list via events.
That's it for now, it works like a charm. Only drawback is that I have another component that I didn't really need. I'll follow up with an example when I find the time.
edit
Added an example as requested. Please note: this code is from 2016 - it may not work with current Vue versions. They changed the event system so parent/child communication works differently. It is now considered bad practice to communicate in both directions (at least the way I'm doing it here). Also, these days I would solve this differently and give each route it's own module in the store.
That said, here's the example I would have added back in 2016:
Let's stick with the users example - we have a page with which you can list/add/edit users.
Here's the route definition:
'/users': {
name: 'users',
component(resolve) {
// this is what I meant with "root-level" route, it acts as a parent to the sub routes
require(['./pages/users.vue'], resolve)
},
subRoutes: {
'/': {
name: 'user-list',
component(resolve) {
require(['./pages/users/list.vue'], resolve)
}
},
'/add': {
name: 'user-add',
component(resolve) {
require(['./pages/users/add.vue'], resolve)
}
},
// etc.
}
},
users.vue
<template>
<div>
<router-view :users="users"></router-view>
</div>
</template>
<script>
/**
* This component acts as a parent and sort of state-storage for
* all user child components. It's the route that's loaded at /users.
* the list component is the actual component that is shown though, but since
* that's a sibling of the other child components, we need this.
*/
export default {
events: {
/**
* Update users when child says so
*/
updateUsers() {
this.loadUsers();
}
},
data() {
return {
users: []
}
},
route: {
data() {
// load users from server
if (!this.users.length) {
this.loadUsers();
}
}
},
methods: {
loadUsers() {
// load the users from an API or something
this.users = response.data;
},
}
}
</script>
./pages/users/list.vue
<template>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
<script>
export default {
props: ['users'],
// the rest of the component
}
</script>

Add dynamically components in vue.js not working

I try to add dynamically component. This component set into props.
I not register components into components:{} section, because I don't now how many components and their name will be send from props.
I use ES6 syntax, where import js modules not work into ready(){} section.
My code:
let Child = Vue.extend({
data() {
return {
msg: 'CHILDREN'
}
},
template: '<div class="c-child">component {{msg}}</div>'
})
let Parent = Vue.extend({
props: {
component: ''
},
data() {
return {
msg: 'PARENT'
}
},
template: '<div class="c-parent">from component- {{msg}}<br><component :is="component"/></div>',
})
// register
Vue.component('parent-component', Parent)
// create a root instance
let vue = new Vue({
el: '#root'
})
.c-parent {
border: 1px solid red;
padding: 10px;
}
.c-child {
border: 1px solid green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.common.js"></script>
<div id="root">
<parent-component :component="Child"></parent-component>
</div>
Dynamic component not render:
<component :is="component"/>
P.S. This code work on jsfiddle - open link
You need to parse the interpreted component name to :is="" component, defining them the following way should work:
import GroupsGrid from './../../components/groups'
import RolesGrid from './../../components/roles'
import MainTabs from './../../components/main-tabs.vue' // <-- this component will be render other components
data: {
tabs: {
'GroupsGrid': 'groups-grid',
'RolesGrid': 'roles-grid'
}
},
And still used like this
<component :is="tabs.GroupsGrid"></component>

Categories

Resources