Express: pass variable to main layout - express

I'm starting to learn Express and I'm stuck trying to pass a variable in my default layout, that gets loaded every time I render a view, so I don't have to pass it every time I res.render something, like below:
router.get('/', function(req, res, next) {
res.render('home', { title: 'title1', 'isuser':req.user});
});
router.get('/loggedin',function(req,res){
res.render('loggedin', {title: 'title2', 'isuser':req.user});
});
router.get('/register', isLoggedIn, function(req,res)
{
res.render('register', {title: 'title3', 'isuser':req.user});
});
I don't want to pass isuser every time I render, that's why I want to just pass it to my main layout so the information is used automatically.
The default layout isn't explicitly rendered in code (with res.render) but acts like a frame for all rendered views and gets loaded automatically. In my case I use handlebars and I load the default layout like so, in my main app.js:
app.engine('handlebars', expbhs({defaultLayout:'defaultLayout'}));

In Node, you can simply create endpoints server-side, like this:
app.get('login/', function(request, response){
response.render('loginpage', { any data you need });
});
app.get('logout/', function(request, response){
response.render('logoutpage', { any data you need });
});
loginpage and logoutpage are in your views folder and can be jade, handlebars, or any other template. The data to be filled in can be passed as shown above, possibly extracted from the request.
In your HTML you can simply link to your 'login' page (not '/login'), and this should then return your login page.

I realize this question is a couple years old, but I had the exact same question. I think the OP is using express-handlebars (given the "expbhs" abbreviation), so this documentation should be relevant: https://www.npmjs.com/package/express-handlebars#helpers
When you create your instance of express-handlebars, you can pass it some parameters such as defaultLayout and a list of helper functions to generate the data you want to insert into your template.
In my case, I was trying to display the current year in the main template's footer (which appears on every page). Like OP, I did not want to pass this data into every single route.
var handlebars = require('express-handlebars').create({
defaultLayout:'main',
helpers: {
year: function() {
return new Date().getFullYear();
}
}
});
This results in displaying the year where I've written {{year}} in main.handlebars. Hope this helps someone else.

Related

How do I pass parameters with router.push in vue.js?

I'm working on a vue.js application. I'm trying to pass parameters in a redirect from one component to another like this:
this.$router.push({
path: '/my-path',
query: {
anArray: [...]
}
});
Once the component at my-path loads, I'm able to retrieve the parameters like this:
const theArray = this.$route.query.anArray;
The problem is that as soon as I refresh the page, the parameters are gone. When I open Chrome DevTools and put a break point where I retrieve the array from $route.query, I see this:
0: "[object Object]"
1: "[object Object]"
2: "[object Object]"
It seems obvious that it's getting this from the url which is:
http://localhost:8080/my-path?anArray=%5Bobject%20Object%5D&anArray=%5Bobject%20Object%5D&anArray=%5Bobject%20Object%5D
It doesn't seem to realize the 'object' terms in the url are just encodings of actual objects, which are available from $route.query the first time it loads.
Is there another way to pass parameters to a component using $router.push() such that the parameters persist on the page even after refreshing?
I could just try:
this.$router.push('/my-path?anArray=[...]');
...but my goal is to hide the parameters from the user (so don't show them in the url). This is another reason I'm looking for an alternate way of passing parameters (it doesn't even hide them).
I also tried:
this.$router.push({
path: '/my-path',
params: {
anArray: [...]
}
});
...but this made the parameters unavailable in the component (I wonder if this has anything to do with our global router which routes '/my-path' to the MyPath component without specifying parameters; is it wiping out the parameters?).
Thanks.
If you want to hide the parameters from the user, you must not use query. Instead, you should use parameters. Here I let you an example:
//routes.js
path: '/:data',
name: 'Home',
component: () => import('pages/YourPage.vue')
//Passing parameters
this.$router.push({
name: 'Home',
params: { data: yourData}
});
//Receiving parameters in Home component
created() {
console.log('Params: ', this.$route.params);
}
I hope this could be usefull
While params suggested #elC0mpa is a correct answer here are some alternatives depending on the use case:
localStorage/SessionStorage
Save the paramters into localStorage/SessionStorage and retrieve them in the loading sequence of your destination-page as #nachodd pointed out.
⚠ It should be noted that only key value pairs in form of strings are being saved into these storages.
You will need something along the line of
localStorage.setItem(itemKey,JSON.stringify(itemValue)
to set the values and
const itemValue = JSON.parse(localStorage.getItem('itemKey')
to recive it. Also localStorage.removeItem('itemKey') for cleanup.
vuex store
You may want to consider saving your data in the store all together and access it from any vue-component.
The commands needed are this.$store.commit('pathToStore/setterForKey',value) to save something into the store and this.$store.getters[pathToStore/getterForKey'] to receive the items value.
⚠ It should be noted that you need to set up the store accordingly with every state setter/mutation, getter and action. See this documentation.

Watch for URL query parameter in Vuex store

I am using Nuxt.js with Vuex and I would like to trigger a mutation when somebody enters in my web with a certain parameter (ex: https://example.com/?param=abc), and pass the parameter to a state.
I tried to check the documentation of the watchQuery property https://nuxtjs.org/api/pages-watchquery, but there’s no examples about how to do this, I just found this How to watch on Route changes with Nuxt and asyncData but I can’t see any way of how to write an action in Vuex store with watchQuery.
I tried writing:
actions: {
watchQuery: true,
asyncData ({ query, app }) {
const { start } = query
const queryString = start ? `?start=${start}` : ''
return app.$axios.$get(`apps/${queryString}`)
.then(res => {
commit('setParam',res.data);
})
},
}
But that syntax is not allowed.
Any help would be welcome, thanks in advance!
From my understanding watchQuery sets a watcher for query string, meaning it's waiting for the query to change while the page is already rendered making it possible to call methods like asyncData() again.
Since you only want to save a certain parameter when the user enters the page and then pass the paramater to a state you just need to move your asyncData method to a page from which you want to get the parameter, you will also need to extract store and query from the context automatically passed into asyncData and then using the store and query save the query parameter into your state.
Here is a simple demonstrantion
// Your page from which you want to save the param
export default {
asyncData({store, query}) { // here we extract the store and query
store.state.somethingForSavingTheParam = query.nameOfTheParamYouWantToSave
// instead of using store.state you could use store.commit(...) if that's what you want
}
}

explain vue-router component as a function

I have seen in several different places the following type of route definition:
{ path : '/dashboard',
component: { render (c) { return c('router-view') }},
children:[{
path:"",
component: Dashboard
}]
},
I am trying to understand how this is different then
{ path : '/dashboard',
component: Dashboard
},
I think it is related to the optional addition of child routs (e.g. /dashboard/user) so that and the children array here just explains that the Dashboard component renders the path /dashboard whereas if I had the second piece of code then it can only render /dashboard.
What I do want to know is what exactly this does
component: { render (c) { return c('router-view') }},
I assume this is some form of a degenerated component but I don't understand what exactly does it do and how.
In Vue, a component is created using an object containing its configuration.
The simplest possible component may look something like this
componentConfig = {
template: '<div>test</div>'
};
Vue.component('test', componentConfig);
In some cases, a developer might not want to use template, and would want to create element from scratch using pure Javascript. That's where render function comes in.
Vue recommends using templates to build your HTML in the vast majority
of cases. There are situations however, where you really need the full
programmatic power of JavaScript. That’s where you can use the render
function, a closer-to-the-compiler alternative to templates.
from https://v2.vuejs.org/v2/guide/render-function.html#Basics
To change the example above to using render function:
componentConfig = {
render: function(createElement) {
return createElement('div', 'test')
}
};
Vue.component('test', componentConfig);
They would produce the exact same result:
https://codepen.io/jacobgoh101/pen/ZoKwKb?editors=1010
https://codepen.io/jacobgoh101/pen/PemVmy?editors=1010
In other words, render function is simply an alternative to using template.
{
component: {
render(c) {
return c('router-view')
}
}
}
is equal to
{
component: {
render(createElement) {
return createElement('router-view')
}
}
}
is equal to
{
component: {
template: `<router-view></router-view>`
}
}
Because render function is closer-to-the-compiler, it's faster compared to using template. That's probably why the author of your code does it this way.
I don't know the rest of your code, but it looks like this might be an implementation of the vue-router Lazy Loading functionality. Basically, Vue + Webpack are going to split your code into chunks and only load those chunks whenever the user attempts to navigate to those routes, rather than loading them all and creating a bigger bundle to download than necessary.
When building apps with a bundler, the JavaScript bundle can become quite large, and thus affect the page load time. It would be more efficient if we can split each route's components into a separate chunk, and only load them when the route is visited.
Combining Vue's async component feature and webpack's code splitting feature, it's trivially easy to lazy-load route components.

vuejs where in the code/lifecycle should data retrieval be triggered

I am working on a vuejs SPA.
I have a view that shows a list of items and another view that shows details for a specific Item.
when I click the item I switch views using:
this.$router.push('/item/' + event.ItemId );
The data is managed using vuex modules.
I would like to allow some temporary display while the item details are being retried (i.e. not to block the rendering of the item details view which should know on its own to indicate that it is still awaiting data).
And I would also have to consider that it should work if the URL is changed (I think I read that there is an issue with the view not being reloaded/recreated when only the item id would change in the URL.
Where would be the appropriate place (code/lifecycle) to trigger the (async) retrieval of the data required for rendering the item details view?
I would like to allow some temporary display while the item details are being retried (i.e. not to block the rendering of the item details view which should know on its own to indicate that it is still awaiting data).
One way to achieve this, is to define a state variable, named e.g. isLoading, in the data context of the Vue component. This variable would then be true while the data is retrieved asynchronously. In the template, you can use v-if to display a spinner while loading, and displaying the content after that.
If you are retrieving the data multiple times (refreshing the view), I would move the retrieving code into a method, e.g. called loadData. In the mounted section of the Vue component you then can just initially call this method once.
Here is some example code:
<template>
<div>
<button #click="loadData" :disabled="isLoading">Refresh</button>
<div class="item" v-if="!isLoading">
{{ item }}
</div>
<div class="spinner" v-else>
Loading...
</div>
</div>
</template>
<script>
import HttpService from '#/services/HttpService';
export default {
name: 'item-details',
data () {
return {
isLoading: false,
item: {}
};
},
methods: {
loadData () {
this.isLoading = true;
HttpService.loadData().then(response => {
this.item = response.data;
this.isLoading = false;
}, () => {
this.item = {};
this.isLoading = false;
});
}
},
mounted () {
this.loadData();
}
};
</script>
And I would also have to consider that it should work if the URL is changed (I think I read that there is an issue with the view not being reloaded/recreated when only the item id would change in the URL.
This issue you mentioned occurs if you are not using the HTML5 history mode, but an anchor (#) in the URL instead. If you are just changing the part after the anchor in the URL, the page is not actually refreshed by the browser. The Vue component won't be reloaded in this case and the state is still old. There are basically two ways around this:
You are switching from anchors in the URL to a real URL with the HTML5 history mode, supported by the Vue Router. This requires some back-end configuration, though. The browser then does not have this faulty behavior, because there is no anchor. It will reload the page on every manual URL change.
You can watch the $route object to get notified on every route change. Depending on if the user is changing the part after the anchor, or before, the behavior is different (it also depends where the cursor is, when you hit enter). If the part after the anchor is changed (your actual Vue route), only the component is notified. Otherwise, a full page refresh is made. Here's some example code:
// ...inside a Vue component
watch: {
'$route' (to, from) {
this.loadData();
}
}

Using one vuex module store in multiple sibling components

I have one global state with some modules.
now i have vue components for various parts of my page.
i have everything setup so /foo uses the foo store (this works).
the created method loads data from an API and writes it to the store
now i have /foo/bar as another (sibling) component, but it needs to access the same store as /foo, but i can't get it to work.
if i enter /foo/bar/ in the URL, there is nothing in the store.
but if i switch to /foo, and then back to /foo/bar, the data is in the store and being output correctly
I've tried registering /foo/bar as a child, which seemed to have no effect (and actually it's not really a child, but just another page with the same data..)
I also tried
state: {
...mapState([
'foo'
)]
}
in /foo/bar, but that doesn't seem to be the right way either
what is the best practice to
load data from API on created on any of a specified set of pages
access said data on any of those pages (i.e. sharing the same store)
i've tried all day to find a solution, but it seems I didn't understand something.
thanks for your help :)
EDIT
actually, while i read my question again, i think my whole problem is the data not being loaded (because the created method is not called). how can i make sure this happens on any page using the store and just once? i can't just write an api call in every created method, can i?
Well, I think just to summarize your problem could be called like you're not being able to access the same state between two different componentes.
What I do normally is that I make an API call from one component inside the method beforeMount, that will guarantee that once my component is created, the data will be available to be used.
Furthermore, after calling the api, I update my state so after that I can call it from everywhere.
One thing that you have to take care with is which component is loaded first?
If A is B's parent, then you should load data inside A.
However, if A and B are siblings, then you should load data inside both of them because you can access first either Component A or B, then you don't know when the data is going to be available. In that case, I would load the data in both of the components.
Also, add cache to your server so you don't need to load the same data again.
For example:
State
{
data: {}
}
Component A
export default {
name: 'Batch',
beforeMount() {
this.getDataFromAPI();
},
methods: {
// getDataFromAPI will store its return inside data with a mutation
...mapActions(['getDataFromAPI']),
randomMethod() {
// Now I can Use my state
const data = this.$store.state.data;
}
}
};
Component B
export default {
name: 'Batch',
methods: {
randomMethodB() {
// If component A was loaded first than component B and A is B's parent, then the state will be accessible in the same manner and it should be populated
const data = this.$store.state.data;
}
}
};
Actions
const getDataFromAPI = ({ commit }) => new Promise((resolve, reject) => {
// Call server
const data = await callServer();
commit('updateMyStateWithData');
resolve(data);
});
export default {
getDataFromAPI
}
Mutations
const mutations = {
updateMyStateWithData(state, newData) {
state.data = newData;
}
}
export default mutations;
Another thing that I do is to define getters, that way is a good approach to load data once, and inside the getter you update the data to return only the things that your UI needs.
I hope that it helps!