I need to render a different layout for the same route for a specific URI with different components depending on the user being on mobile or in desktop.
I would like to avoid having route path checks in the PageCommon(layout component) to keep it clean.
The app has a main component taking care of the layout, it has different router-views where we load the different components for each page URI. This would be a normal route for that.
{
path: '',
component: PageCommon,
children: [
{
path: '',
name: 'Home',
components: {
default: Home,
header: Header,
'main-menu': MainMenu,
'page-content': PageContent,
footer: Footer,
'content-footer': ContentFooter
}
},
I can't change the route components property once the component is loaded so I tried to make a wrapper and pass the components dynamically.
{
path: 'my-view',
name: 'My_View',
component: () => import('#/components/MyView/ViewWrapper')
},
In /components/MyView/ViewWrapper'
<page-common v-if="isMobile">
<my-mobile-view is="default"></my-mobile-view>
<main-menu is="main-menu"></main-menu>
</page-common>
<page-common v-else>
<my-desktop-view is="default"></my-desktop-view>
<header is="header"></header>
<main-menu is="main-menu"></main-menu>
<footer is="footer"></footer>
</page-common>
</template>
I would expect that the components passed inside page-common block would be substituted on the appropriate , but is not how it works, and Vue just loads page-common component with empty router-views.
Is there any approach for this?
Note that I already tried using :is property for loading different components, but the problem then is on how to tell the parent to use this or that component for this page. This is the code for that:
<template>
<component :is="myView"></component>
</template>
<script>
import DesktopView from "#/components/MyView/DesktopView";
import MobileView from "#/components/MyView/MobileView";
export default {
name: 'MyView',
components: {
DesktopView,
MobileView,
},
data(){
return {
myView: null,
isMobile: this.detectMobile()
}
},
methods : {
getViewComponent() {
return this.isMobile ? 'mobile-view' : 'desktop-view';
}
},
created() {
this.myView = this.getViewComponent();
}
}
</script>
I could use this approach for each of the PageCommon router views, creating a component for each that does the above, but it looks like a very bad solution.
A computed method is all you need.
You should have this top level Logic in App.vue and the <router-view> should be placed in both DesktopView and MobileView.
// App.vue
<template>
<component :is="myView"></component>
</template>
<script>
import DesktopView from "#/components/MyView/DesktopView";
import MobileView from "#/components/MyView/MobileView";
export default {
name: 'MyView',
components: {
DesktopView,
MobileView,
},
computed: {
myView() {
return this.detectMobile() ? 'mobile-view' : 'desktop-view';
}
}
}
</script>
You may also want to consider code splitting by setting up Dynamic Components for those layouts since Mobile will load Desktop View because it is compiled into final build, register them globally as dynamic imports instead if importing them in MyView and then delete components also after doing the following instead, this way only the one that is needed will be downloaded saving mobile users their bandwidth:
// main.js
import LoadingDesktopComponent from '#/components/LoadingDesktopComponent '
Vue.componenet('desktop-view', () => ({
component: import('#/components/MyView/DesktopView'),
loading: LoadingDesktopComponent // Displayed while loading the Desktop View
})
// LoadingDesktopComponent .vue
<template>
<div>
Optimizing for Desktop, Please wait.
</div>
</template>
<script>
export default {
name: 'loading-component'
}
</script>
Routing logic will only be processed when <router-view> is available,this means you can delay the presentation of Vue Router, for example you can have :is show a splash screen like a loading screen on any URI before displaying a component in :is that contains <router-view>, only than at that point will the URI be processed to display the relevant content.
Related
I am new to Vuejs. I try to trigger the transition, every time the Homepage router link is clicked by user. I read some of documentation of Passing Props to Route Components enter link description here, and still cannot get it work. I tested the transition is working. But it seems like the loaded boolean did not passed to my component correctly.
Router index.js
{
path: '/',
name: 'Home',
component: Homepage,
props: { loaded: true }
},
Homepage component:
<template>
<transition name='fade'>
<div class="rgba-blue-grey-strong container" v-show="loaded">
<p>Text with transition Every time user access Homepage component</p>
</div>
</transition>
</template>
<script>
export default {
name: "Homepage",
props:['loaded'],
data(){
return{
loaded: false,
}
}
}
You have loaded defined in both props and data object. Remove the one in your data object and it'll be fine.
<script>
export default {
name: "Homepage",
props:['loaded'],
data(){
return {
}
}
}
</script>
So currently I'm importing a component multiple times with different names.
import Page1 from "./Page/Page"
import Page2 from "./Page/Page"
import Page3 from "./Page/Page"
import Page4 from "./Page/Page"
I'm doing this as I want each instance to have its own set of properties, which then I use <keep-alive> to maintain their state.
I am also using them inside a <component :is="".
I was wondering if there was a way to create multiple instances without multiple import.
Codesandbox:
https://codesandbox.io/s/5x391j8y4x
you will notice that if I switch between the HelloWorlds, that the input will maintain their instances (input will change to what they were holding)
You don't need to use <component> because you only have one component type that you want to use: HelloWorld. <component> is only needed when you want to dynamically render different component types.
The reason why you require <keep-alive> is because the HelloWorld component has local state (msg) which will be lost once the component instance is destroyed.
You will need to use key to force Vue to instantiate a new instance of HelloWorld based on the page, and you need <keep-alive> to prevent each instance from being destroyed when you switch between pages.
Here's an example:
<ul>
<li
v-for="page in pages"
#click="currentPage = page"
:key="page.key">{{ page.title }}</li>
</ul>
<keep-alive>
<hello-world
:key="currentPage.key"
:title="currentPage.title"/>
</keep-alive>
import HelloWorld from './components/HelloWorld'
export default {
name: "App",
components: {
HelloWorld,
},
data() {
const pages = [
{
key: "home",
title: "Home"
},
{
key: "about",
title: "About"
},
{
key: "contact",
title: "Contact"
}
]
const currentPage = pages[0]
return {
currentPage,
pages
}
}
}
I have your usual admin dashboard (Core-UI) which I'm modifying for my own needs. I have an "aside" component into which I want to load MonitorAside.vue whenever I am on the Monitor page (I am using vue-router)
Here's a small rundown.
src/containers/Full.vue imports all the main components (including aside) and then uses router-view to render the view based on the route.
src/components/Aside.vue is the component in question. It contains fixed content but I want its content to dynamically be changed if another component requires to do so.
src/views/Monitor.vue is the page in question and thus the page which dynamically needs to inject/swap the content of the aside component. Note that this component is not imported in Full.vue but is rendered through the router there.
src/views/asides/MonitorAside.vue is the component I want to be loaded into Aside.vue whenever I am on the Monitor page.
How would I go about doing this?
So whenever vue-router navigates to a different page, you want the content of your Aside component to change? You can watch the $route value and then render the content depending on your route names, something like:
<template>
<div>
<monitor-aside v-if="page === 'monitor'"></monitor-aside>
<div v-else>
<!-- your default aside content -->
</div>
</div>
</template>
<script>
import MonitorAside from '../views/asides/MonitorAside.vue'
export default {
data() {
return {
page: null
}
},
methods: {
setContent(routeName) {
if (routeName === 'Monitor') {
this.page = 'monitor';
} else {
this.page = null;
}
}
},
created() {
this.setContent(this.$route.name);
},
watch: {
'$route'(to, from) {
this.setContent(to.name);
}
}
components: {
MonitorAside
}
}
</script>
I have navbar blade, component with text and another components with page.
It works like I have component with text in navbar, and another component after navbar. That's three another components. How to change text from for example index.vue in text.vue?
That's what I have:
Text.vue:
<template>
<p class="title">{{msg}}</p>
</template>
<script>
export default {
props: [
'msg',
],
data() {
return {
}
},
mounted() {
},
methods: {
}
}
</script>
Component in navbar.blade.php:
<navbar-title></navbar-title>
And I try to change it in index.vue, that should work when we are on this page:
data() {
return {
msg: 'text',
}
But it doesn't work. How to do it correctly?
EDIT:
Vue.component('title', require('./components/Title.vue'));
To pass the message variable from your index.vue through navbar.vue to title.vue each needs to pass the property to the child and each child must pass the property on again all throughout the tree.
Something like this should work for your case: <title :msg="msg"></title>
I'm trying to create simple ToDo app using Ractive.js and Redux, but I ran into a problem with rendering more than one component on my page.
Javascript code:
import Ractive from 'ractive';
import template from './Home.html';
import './Home.less';
import { AddToDoForm, ToDoList } from '../';
export default Ractive.extend({
template,
data: {
message: 'This is the home page.',
},
components: {
AddToDoForm,
ToDoList
}
});
HTML of the component:
<AddToDoForm store={{store}} />
<ToDoList store={{store}} />
But only the first component is rendered. The store parameter I'm passing is the Redux store, but it doesn't work even without it.
I would add to verify defaults as a
...
components:{
AddToDoForm: AddToDoForm,
ToDoList: ToDoList
}
...
syntax examples (answer/31096635)