Vue + Vuetify + vue-router: changing toolbar content based on page - vue.js

Vuetify has a pretty flexible layout scheme encompassing a menu, toolbar, content, and footer, that allows some nice-looking material design schemes, e.g. Google Contacts:
Consider a standard setup with the layout being controlled by the router, with a fixed menu across the site, but a dynamic toolbar that changes with the page shown. What's the best practice for changing the content of the toolbar depending on what page is shown? In the Google Contacts example, we only want the search bar to show up on the Contacts page.
Based on my rudimentary knowledge of Vue, it seems like defining a router layout with a scoped slot. There are likely a lot of other hacky ways to achieve this. But I'm looking for a clean, modular way to define toolbar content across pages.
Ideas:
As of a while ago vue-router didn't support named slots. But this seems to have changed recently, although there is no documentation.
Named views seem to be a nice way to support tie the toolbar content to the main page with vue-router. But there doesn't seem to be a good way for the toolbar to 'talk' to the main page as there would be with a slot.

You can define multiple router views in your application. Imagine your layout looks extremely simplified like this:
<v-app id="app">
<router-view name="navigation"></router-view>
<router-view></router-view>
</v-app>
Then you can define a route with components for both router views:
{
path: '/hello',
components: {
default: MyHelloComponent,
navigation: MyNavigationForHelloComponent
}
}
Documentation
Working Example from Documentation

Related

Does Nuxt3 support named router views?

I'm building a Nuxt app with a layout that consists of 3/4 main content and 1/4 sidebar navigation that uses tabs that expand based on the current route.
My aim is to essentially have two router-views — one on the main page and one in each tab.
With the Vue Router I guess this would be possible to achieve via named views e.g.
<router-view name="rightSidebar"></router-view>
But obviously with Nuxt the idea is that the routing is taken care of behind the scenes...so it isn't possible to configure.
But none the less is it possible to use <NuxtPage /> in this way?
Nuxt.js does not support the router-view. However, you can use the NuxtPage inbuilt component. Just remember that it only works after the pages you are trying to nest have been added in the pages directory.
Here is a link to an overview of it from the Nuxt docs.

Can I Dynamically Inject a Router View

I am trying to accomplish something that I am not sure is possible or semantically good. I am building out a personal portfolio website and I liked the idea of a full page navigation where each link transitions into a 90% view and the router view loads in that space(See codepen below for transition example). I've stripped out the main router-view from my codesandbox. I am wondering maybe I need named router views?
Another thing I'd like to know is if I could keep the html semantic at all if I basically inject a main tag directing after a nav link? There has to be a better way than how I am doing this. I just need to step back and look at it differently.
CodeSandBox Portfolio:
CodePen Tester for Navigation Transition (I will probably do this a different way using Vue transition components)
See the Pen Portfolio Nav Tester by CJ Haviland (#cjhaviland) on CodePen.

Can Vue-Router handle clicks from normal anchors rather than router-link?

I have a scenario where there are two major components on a page; a frame-like component that contains common functionality for many applications (including a bookmark/tab bar) and my actual application code.
Since the frame doesn't actually own the page that it's included on, it seems like it would be incorrect for it to define any routes, however the current page may define their own routes that may match one of those links. In that case, I'd like vue-router to handle those anchor clicks and navigate appropriately rather than doing a full page reload.
Here's a simplified template of what this looks like:
Frame (an external dependency for my app):
<Frame>
<TabStrip>
</TabStrip>
<slot></slot>
<Frame>
App1:
<Frame>
<App>You're looking at: {{ pageId }}!</App>
</Frame>
So when any of the app1 domain links are clicked from that tab strip, I want my route definitions in app1 to pick that up rather than it causing a page load. Since that component is owned by the frame, I don't have access to write <router-link> since links to many different apps may co-exist there.
Any thoughts?
Whoo, this is an old one! However, since this question was high in my search results when I was researching this problem, I figured I should answer it.
My use-case was similar to the one in the comments: I needed to capture normal <a> links within rendered v-html and parse them through the router (the app is rendering Markdown with a light modification that generates internal links in some cases).
Things to note about my solution:
I'm using Vue3, not Vue2; the biggest difference is that this is the new Vue3 composition-style single page component syntax, but it should be easy to backport to Vue2, if necessary, because the actual things it's doing are standard Vue.
I stripped out the markdown logic, because it doesn't have anything to do with this question.
Note the code comment! You will very likely need to design your own conditional logic for how to identify links that need to be routed vs. other links (e.g. if the application in the original question has same-origin links that aren't handled by the Vue app, then copy/pasting my solution as-is won't work).
<script setup>
import { useRouter } from "vue-router"
const router = useRouter()
const props = defineProps({
source: {
type: String,
required: true,
},
})
function handleRouteLink(event) {
const target = event.target
// IMPORTANT! This is where you need to make a decision that's appropriate
// for your application. In my case, all links using the same origin are
// guaranteed to be internal, so I simply use duck-typing for the
// properties I need and compare the origins. Logic is inverted because I
// prefer to exit early rather than nest all logic in a conditional (pure
// style choice; works fine either way, and a non-inverted conditional is
// arguably easier to read).
if (!target.pathname || !target.origin || target.origin != window.location.origin) {
return
}
// We've determined this is a link that should be routed, so cancel
// the event and push it onto the router!
event.preventDefault()
event.stopPropagation()
router.push(target.pathname)
}
</script>
<template>
<div v-html="source" #click="handleRouteLink"></div>
</template>

Aurelia layout is recreated on every route change

Background
I have a typical web app with a left column menu; and a right column for content.
If I put a menu in the left column and use <router-view></router-view> in the right, as you would expect the content is swapped out as you navigate routes.
See a basic gist.run here. This is obviously not my actual app so ignore the oversimplifications. You can see by clicking on one of the menu items it changes style, then click one of the home/page links and the menu item keeps its applied class.
The problem
If I add a layout for these two routes, the menu looses its state when changing routes. i.e. the applied "active" style is lost because the layout is reloaded, instead of just the slot being reloaded. gist.run here:
app.js:
config.map([
{ route: '', name: 'home', moduleId: 'home', layoutView: "layout.html", layoutViewModel: "layout" },
{ route: 'page', name: 'page', moduleId: 'page', layoutView: "layout.html", layoutViewModel: "layout" }
]);
layout.html:
<template>
<!-- left menu content here -->
<div class="layout">
<!-- route view on right: -->
<slot></slot>
</div>
</template>
My home.html and route.html pages are identical to the previous gist without layouts:
<template>
Home
</template>
Note that slots are not needed here (e.g. <div slot="name">...) because there is only one slot. I've tried including them and the result is the same.
I would expect that the layout/router behaviour should look the same - that the layout isn't reloaded on each route change - even though it's wired differently underneath.
This is a problem because I may have animations on the menu items (sliding, dropdowns, active classes etc.) and I don't want those starting from the default position each time a route is changed. I may also have controls in the left panel such as a search box or other form, and may not want that refreshing on each route change.
Is there any way I can make the layout behave the same (keep "state") as the router-view?
As a way around it I've considered:
an initial state that I put together from the current route (could get messy or not cover all options)
saving state in a service or singleton somehow, but I can't figure out if this is possible with a view
router viewports. Not quite as flexible because I can't have multiple different app.html's with different viewports laid out in different ways. It also requires a module-per-viewport or other trickery, and can't have all the content from the same view as with slots...
Please tell me if there's something else I can do or if I'm doing it all wrong!
thanks.
I just yesterday asked about this over at the Aurelia Gitter room, and the response I got was that layout lifecycle is tied to the routed module's lifecycle. In practice, this means that:
If your activationStrategy is refresh, the layout will be destroyed and recreated even when navigating to the same route with different parameters.
If your activationStrategy is invokeLifecycle, the layout will not be destroyed and recreated if you navigate to the same route, but it will be if you navigate to a different route.
So if you have some state that you want to keep in the parent structure, and still be able to have multiple positions where content is projected on a per-route basis, viewports are the way to go, with the caveats that you already noted.

Aurelia: How can I modify sidebar content from inside a router view?

I'm trying to wrap my head around how "inner components" can adjust the content of "outer components". Let's say I have an application template that looks something like this:
<template>
<div class="sidebar">
<div>Some app-wide content</div>
<div>
<!-- I want to put some view-specific content here -->
</div>
</div>
<div class="main-body">
<router-view></router-view>
</div>
</template>
Each subview wants to render different content to the sidebar. Obviously this would be easy if the subview included the sidebar area itself, but let's say it is important to preserve the structure and we don't want to replicate the boilerplate of the sidebar across every view.
Is there any way for a child view to declare "export this extra component for display in another place?" I imagine something like injecting the parent view and calling a method on it, but I can't figure it out from the documentation.
Simple demo:
It's fairly simple, actually. Just import and inject your sidebar or any other viewmodel and call a method or update a property.
https://gist.run/?id=745b6792a07d92cbe7e9937020063260
Solution with Compose:
If you wanted to get more elaborate, you could set a compose view.bind variable to that your sidebar would pull in a different view/viewmodel based on the set value.
https://gist.run/?id=ac765dde74a30e009f4aba0f1acadcc5
Alternate approach:
If you don't want to import, you could also use the eventAggregator to publish an event from the router view and subscribe to listen to that event from your sidebar and act accordingly. This would work well for a large app setting where you didn't want to tie them too closely together but wanted the sidebar to react correctly to unpredictable routing patterns by always responding when triggers were published.
https://gist.run/?id=28447bcb4b0c67cff472aae397fd66c0
#LStarkey's <compose> solution is what I was looking for, but to help others I think it's worth mentioning two other viable solutions that were suggested to me in other forums:
View ports. You can specify multiple named router views in a template and then populate them by passing in a viewPorts object to the router instead of specifying a single moduleId. The best source of documentation is a brief blurb in the "Cheat Sheet" of the Aurelia docs.
Custom elements. It's a little more "inside-out" but you could define most of the outer content as a custom element that has slots for the sidebar and the main body; each child view would define this custom element and pass in the appropriate pieces.