How to layout Vue so pages include header/nav but ability to display stand alone pages such as registration - vue.js

My current Vue app is laid out as follows:
Such that login Vue would be live under VContent, and aspects of the header and nav are be disabled based on auth state. For example, logout button if store.isAuthenticaaed. See below:
If a Register exists on the login page leading to a registration Vue, how would I break that Vue out of the parent App.vue as to not display the header or nav at all? Should I move my header/wrapper down a level and if so how?

The below answer is totally based on how I understand your requirement, happy to correct if the assumptions are wrong.
Redirecting to which page can be handled at the router configuration.
As per your layout, I can see you have
<VApp>
<VBar />
<VContent >
<router-view />
</VContent>
<VNavigationDrawer />
</VApp>
Now, your store knows whether the user is authenticated or not.
So Why not simply remove <VBar /> and <VNavigation /> using v-if. However, you need to make sure that your state is having the correct state at the initial render.
I can simply create a computed property stating
showBarAndNavigation () {
return store.isAuthenticated && this.$route.name === 'Login' && // anymore handlers
}
<VApp>
<VBar v-if="showBarAndNavigation" />
<VContent >
<router-view />
</VContent>
<VNavigationDrawer v-if="showBarAndNavigation" />
</VApp>

Related

FontAwesome Icons Not Always Displaying in Nativescript (Vue) App?

I am working on an app in Nativescript-vue. The app requires a user to be logged in. When started, the app checks appSettings for whether the user is logged in. If logged in, the user is directed to a menu of choices ("Home"). If user is not logged in, the app loads a login form which validates credentials against a remote DB and then directs the user to the very same menu ("Home").
In both situations, the user is directed to the exact same file, Home.vue. This page consists of 5 or so buttons, each with a FontAwesome icon.
The problem is that the app displays or does not display the icons depending on how the user ends up at the Home.vue menu.
Icons are not displayed when the user is directed on startup to Home.vue (aka appSettings variable shows user is logged in, thus doesn't have to present credentials). Note that drilling further into the app by clicking a button that takes you to another *.vue file that also uses buttons with embedded FontAwesome icons does work.
Icons are displayed correctly when the user is routed to the Home.vue file after presenting valid username / password credentials, both at the Home page and subsequent menus in the app.
MAIN.JS
import Vue from 'nativescript-vue'
// Font Awesome!
import {TNSFontIcon, fonticon} from 'nativescript-fonticon';
TNSFontIcon.debug = true;
TNSFontIcon.paths = {
'fa': './assets/css/fontawesome.min.css',
'far': './assets/css/regular.min.css',
'fas': './assets/css/solid.min.css',
'fab': './assets/css/brands.min.css'
};
TNSFontIcon.loadCss();
Vue.filter('fonticon', fonticon);
import {UserServices} from "./assets/js/UserServices.js"
import Login from "./components/Login.vue";
import Home from "./components/Home.vue"
let user = new UserServices();
new Vue({
render: h => h('Frame', [
h(
(user.getLocalToken()) ? Home : Login
)])
}).$start();
HOME.VUE. (Very stripped down, but still exhibiting the stated behavior)
<template>
<Page class="page" #loaded="loaded">
<StackLayout>
<Button #tap="tapLogoutButton">
<FormattedString>
<Span
class="fas button-icon" stretch="none" :text="'fa-sign-out-alt' | fonticon"
/>
<Span
text=" Logout"
/>
</FormattedString>
</Button>
</StackLayout>
</Page>
</template>
<script>
import Login from "./Login";
import { UserServices } from '../assets/js/UserServices'
let u = new UserServices();
export default {
methods: {
tapLogoutButton(){
if (u.logout()){
this.$navigateTo(Login, {
clearHistory: true
});
};
},
}
};
</script>
Any insight into why the FA icons would appear on a single page (when loaded) depending on how the user ended up there?
You are trying use the plugin your first page before it's ready. loadCss() is an asynchronous function, returns a promise. You must make sure the promise is resolved before using the filter from plugin to render icons.
There are various ways you could solve this
If it's just couple icons in the home / login page, don't use the plugin, directly pass the font code.
you may create an event bus / state, that will hold a boolean value, set it to true upon resolving loadCss() method. Your Home & Login page can rely on the flag, may be with a v-if. It may be few milliseconds for the plugin to process the CSS files.
you may modify the plugin to optionally support synchronous loading,
perhaps even raise a pull request.
I solved this by using the unicode at the text attribute. Binding it to a Vue data.
At the template:
<Label class="fas" :text="file_privacy_police | fonticon"/>
At the <script> tag:
data() {
return {
address_book: "\uf2bb",
file_privacy_police: "\uf570"
};
},
But you can add the unicode directly at the text attribute:
<Label class="fas" :text="'\uf570' | fonticon"/>
This solved my not loading icons problem when already logged in and relaunching the app.

Vue rendering only part of my App.vue after a login

I am working on a Vue.js application that I am almost done, one major bug left. The bug/issue is that when you go to /login and login to the site you get redirected via a router push (tried replace too) and when this happens I want to render the whole dashboard. Currently since in my App.vue file the router view is a different part it only renders the dashboard info part and not my header or sidebar.
Pretty much imagine a dashboard without a header or sidebar. That's what's rendering. I'd be okay if I could do something like F5 does because then it all would load correctly though taking up to 2 seconds longer on login which is okay by me.
My App.vue file template code
<template>
<div class="fade page-sidebar-fixed page-header-fixed show page-container" v-if="!pageOptions.pageEmpty" v-bind:class="{
'page-sidebar-minified': pageOptions.pageSidebarMinified,
'page-content-full-height': pageOptions.pageContentFullHeight,
'page-with-top-menu': pageOptions.pageWithTopMenu,
'page-sidebar-toggled': pageOptions.pageMobileSidebarToggled,
'has-scroll': pageOptions.pageBodyScrollTop
}">
<Header />
<Sidebar v-if="!pageOptions.pageWithoutSidebar" />
<div id="content" class="content" v-bind:class="{ 'content-full-width': pageOptions.pageContentFullWidth, 'content-inverse-mode': pageOptions.pageContentInverseMode }">
<router-view></router-view>
</div>
</div>
<div v-else>
<router-view></router-view>
</div>
</template>
Looks like I have resolved my issue, it comes from vue-router and how I am doing that if statement in my template code. So in that code I am checking a boolean value then choosing which view to render. So I had though on all of my auth pages I set the value correctly on exit. Turns out not...
This was in my Login.vue file, idea was to have on an exit of the route that it would change the boolean to false which would let me render it right. This was something I did initally but had forgotten about till about 20 minutes ago.
Upon checking this I found the value was not being changed for some reason. So as a work around in the created part of my Dashboard.vue file I set the value to false explicitly
Login.vue
beforeRouteLeave (to, from, next) {
PageOptions.pageEmpty = false;
next();
},
Dashboard.vue
created() {
PageOptions.pageEmpty = false;
...
}
The main idea is to have several base pages each one of them is operate with its own set of internal views.
You have to redirect user to another view, which is the one and only active view and this view can contains sidebar header and main part that also contains router-view, and then! you load any needed components in it.
You have to have something like that:
App component is only contains router view tag and any other pages are load into this.
The routes structure then looks like that:
As you can see, there are two base views load in App view. And then the base view can has a lot of children. The level of nested routes is up to you. Here is the contents of my app Home view:
And the MainContent component which is contains router view only:
The good example of project structure is the one generated with vue-cli. You can use it to simplify dev process with a lot of benefits and good practice solutions.

Vue components re-rendering on router push

I have created a global component that i share across multiple routes. i have simplified the component below for demonstration purposes.
#name area-wrapper
<template>
<div id="area">
<div id="area-menu">
<menu/>
</div>
<div id="area-content">
<slot/>
</div>
</div>
</template>
Within the menu is a navbar which has options that will change the content of the slot which i could just turn into a component v-bind:is component.
What i have done is created several page for the routing
pages
_entity <--*** forgot to include this ***
app
index.vue
_appId.vue
new.vue
Each of these pages includes the component above and then adds in their own content for id="area-content
What i have been noticing is that the entire area-wrapper is being reloaded when i move from
website.com/app/112 (pages/app/_appId.vue)
website.com/app/11 (pages/app/_appId.vue)
I have noticed that if i move the area-wrapper to a layout then it works the problem is that the component will eventually be shared with several apps but will have a different <menu/> and layouts do not have slots
I'm not sure why vue is re-rendering the entire component even though it is shared among all the pages and is the same across each page.
What am i missing here?
If this is expected behavior my question becomes, how can i create a shared component that acts like a layout that i include in several pages without adjusting the props and have it not constantly reload
+==== UPDATE ====+
i have been trying to get nest routes to work because i believe this is what i am after. However nuxt is not generating them correctly
Per the documentation(``) i need to change my stucture to
pages
_entity
messaging
settings
index.vue
msg
index.vue
messaging.vue(wrong - changed back to index.vue) -> within here add the <nuxt-child> component
messaging.vue(need to move to _entity folder to create children)
nuxt should create the child components. However it is still continuing to create full routes. i am using nuxt-i18n will that cause a problem?
routes
...
{
path: "/:entity/messaging/messaging",
component: _8a865700,
name: "entity-messaging-messaging___en"
}, {
path: "/:entity/messaging/:msg?",
component: _1ef926cc,
name: "entity-messaging-msg___en"
}, {
path: "/:entity/messaging/settings",
component: _7b358e6a,
name: "entity-messaging-settings___en"
}
I originally created the nested route within the folder.Instead, you need to put the parent page within the root of what directory the folder exists.
pages
_entity
messaging
settings
index.vue
msg
index.vue
messaging.vue(wrong - changed back to index.vue) -> within here add the <nuxt-child> component
messaging.vue(need to move to _entity folder to create children)
You should define routing in your app as suggested here (pure Vue) or here (with vue-router). Without it you are reloading a whole page (and the app) when you change the URL.

Load a different page in nuxt at runtime based on the parameters in the route

We have a scenario in which a different page is required to be loaded based on whether parts of the route has parameters that are valid and that can be determined at run-time.
Consider the following example:
Request to http://example.com/param1/param2
If param1 is a valid product identifier (can be determined by an API call to another service) the product page loads or its considered a category and Category Page is loaded.
Considering Nuxt uses static routes mostly and the list of products are dynamic, is there a hook where you can execute custom code to load a different page ?
Cant you create _product page
like described in nuxt docs:
https://nuxtjs.org/guide/routing/#dynamic-routes
And in your code make something like:
<template>
<div>
<nuxt-child />
</div>
</template>
<script>
export default {
asyncData({route, params, redirect}) {
//use route
console.log(route.params.slug)
//directly use params
console.log(params.slug)
redirect(`/`);
},
};
</script>
or use mounted() hook if you are creating SPA

Vue prerender flickering

I have the following solution now:
<template>
<section id="prod-main">
<prod-preview v-for="prod in products" :id="prod.id" :key="prod.id"/>
</section>
</template>
export default {
...
computed: {
products: function () {
return this.$store.getters['products/getPreview']
}
}
...
}
Vuex store will receive info after some delay from my backend. So at first call it will be empty. Now I want to use vue spa prerender and here I see a flickering.
As I understood it works like:
1. Browser loads HTML with products
2. Execute js that replace products with nothing because the store is empty.
3. After some delay shows it again with backend info.
How can I fix it? I should left prerender for indexing and I can't hardcode the backend reply.
You can use the setting captureAfterTime to wait for your async call to complete, before saving the html of the page.
Other settings are available :
// NOTE: Unless you are relying on asynchronously rendered content,
// such as after an Ajax request, none of these options should be
// necessary. All synchronous scripts are already executed before
// capturing the page content.
// Wait until a specific event is fired on the document.
captureAfterDocumentEvent: 'custom-post-render-event',
// This is how you would trigger this example event:
// document.dispatchEvent(new Event('custom-post-render-event'))
// Wait until a specific element is detected with
// document.querySelector.
captureAfterElementExists: '#content',
// Wait until a number of milliseconds has passed after scripts
// have been executed. It's important to note that this may
// produce unreliable results when relying on network
// communication or other operations with highly variable timing.
captureAfterTime: 5000,
Another issue can be related to how the prerendered HTMl gets hydrated, i've openned an issue on github, but they still haven't addressed it (and are not willing to ?)
https://github.com/chrisvfritz/prerender-spa-plugin/issues/131
The solution is to add data-server-rendered="true" to your vuejs parent node in the prerendered html, like this:
<div id="root" data-server-rendered="true">...
You can use the option postProcessHtml to do so.
I don't know if I understand your problem here but have you tried to add a v-if to avoid flickering:
<template>
<section id="prod-main">
<prod-preview
v-if="products.length > 0"
v-for="prod in products"
:id="prod.id"
:key="prod.id"/>
</section>
</template>