Dynamically show components depending on the current route, vuejs - vue.js

In vue how do I dynamically show components based on the current route?
I only want the custom-componentto be visible if not on the homepage. And by default, I set that the user is on the homepage so the component doesn't show on load.
I've tried several router methods with no luck: https://router.vuejs.org/api/#router-instance-methods.
app.vue:
<template>
<div id="app" :class="$style.app">
<navbar/>
<custom-component v-bind:is="homePage"></custom-component>
<router-view :class="$style.content"/>
<footer/>
</div>
</template>
data() {
return {
homePage: true
}
},
methods: {
homePage() {
if(this.$route.path("/") || this.$route.path("/home")) {
this.homePage = true
} else {
this.homePage = false
}
}
}
This is close, but doesn't achieve the desired results: VueJS - Load child components dynamically.
Also would this be the best way to do it in app.vue as I am trying now. Or should I have this logic in the custom-component instead?

Why not using a computed property instead of a method ?
export default {
computed: {
homePage() {
if(this.$route.path == "/" || this.$route.path == "/home" ) {
return true
} else {
return false
}
}
}
}

You can achieve your goal by using v-if or v-show directives
<custom-component v-show="homePage"></custom-component>
Or
<custom-component v-if="homePage"></custom-component>
If i were you i would watch the route object for further changes like this and use one of the option above according to this statement
Generally speaking, v-if has higher toggle costs while v-show has
higher initial render costs. So prefer v-show if you need to toggle
something very often, and prefer v-if if the condition is unlikely to
change at runtime.
You can check out further details about conditional rendering from vue.js doc
Also would this be the best way to do it in app.vue as I am trying
now.
No, you shouldn't bloat your app.vue file whilst you can handle the same
problem with different components in more modular way.
Or should I have this logic in the custom-component instead?
In general if you can assume that this chunk of code can be used in different parts of the application, it's better to implement as different component.

The easiest option to show some component on the page is a method that return true or false. When we use v-if, component will be ignored during rendering.
<template>
<div id="app" :class="$style.app">
<navbar/>
<custom-component v-if="homePage()"></custom-component>
<router-view :class="$style.content"/>
<footer/>
</div>
</template>
<script>
export default {
methods: {
homePage() {
if(this.$route.path == "/" || this.$route.path == "/home" ) {
return true
} else {
return false
}
}
}
}
</script>

Use the v-if binding in your component
<custom-component v-if="homePage" v-bind:is="homePage"></custom-component>
where homePage is a bool value.

Related

Vue: how to call 3 different v-on: click functions

I have a main page with 3 buttons that lead to the other 3 pages. All 4 pages (components) are connected through App.vue.
I use :click="goPage('pageName') at the main page with 3 different pageNames. And method:
goPage: function (status) {
this.$emit(status)
}
I am trying to pass my "pageName" to App.vue
<main-page v-if="status === 'mainPage'"
v-on:goPage="goPage($event)"
and
goPage: function(status){
console.log(status)
this.status = status
}
I used $emit approach and it worked for a single page. But I have no idea how to work with multiple calls.
Do $emit or should use something other?
You need to pass method as prop to your main page component. on main page component, get chageStatus function from prop then for each button set an #click to change your status to desire page name. on top component you need to define a method to change status for you:
<template>
<div>
<main-page v-if="status === 'mainPage'" :changeStatus ="this.changeStatus" />
<other-page v-else-if="status === 'otherPage'"/>
<another-page v-else-if="status === 'anotherPage'"/>
<div>
</template>
<script>
export default {
data(){
return {
status: 'mainPage'
}
}
methods: {
changeStatus(dst) {
this.status = dst
}
}
}
</script>

Vue- best practice for loops and event handlers

I am curious if it is better to include methods within loops instead of using v-if. Assume the following codes work (they are incomplete and do not)
EX: Method
<template >
<div>
<div v-for="(d, i) in data" v-bind:key="i">
<span v-on:click="insertPrompt($event)">
{{ d }}
</span>
</div>
</div>
</template>
<script>
export default {
data() {
data:[
.....
]
},
methods:{
insertPrompt(e){
body.insertBefore(PROMPT)
}
}
}
</script>
The DOM would be updated via the insertPrompt() function which is just for display
EX: V-IF
//Parent
<template >
<div>
<div v-for="(d, i) in data" v-bind:key="i">
<child v-bind:data="d"/>
</div>
</div>
</template>
<script>
import child from './child'
export default {
components:{
child
},
data() {
data:[
.....
]
},
}
</script>
//Child
<template>
<div>
<span v-on:click="display != display">
{{ d }}
</span>
<PROMPT v-if="display"/>
</div>
</template>
<script>
import child from './child'
export default {
components:{
child
},
data(){
return {
display:false
}
},
props: {
data:{
.....
}
},
}
</script>
The PROMPT is a basic template that is rendered with the data from the loop data click.
Both methods can accomplish the same end result. My initial thought is having additional conditions within a loop would negatively impact performance?!?!
Any guidance is greatly appreciated
Unless you are rendering really huge amounts of items in your loops (and most of the times you don't), you don't need to worry about performance at all. Any differences will be so small nobody will ever notice / benefit from having it a tiny touch faster.
The second point I want to make is that doing your own DOM manipulations is often not the best idea: Why do modern JavaScript Frameworks discourage direct interaction with the DOM
So I would in any case stick with the v-if for conditionally rendering things. If you want to care about performance / speed here, you might consider what exactly is the way your app will be used and decide between v-if and v-show. Citing the official documentation:
v-if is “real” conditional rendering because it ensures that event
listeners and child components inside the conditional block are
properly destroyed and re-created during toggles.
v-if is also lazy: if the condition is false on initial render, it
will not do anything - the conditional block won’t be rendered until
the condition becomes true for the first time.
In comparison, v-show is much simpler - the element is always rendered
regardless of initial condition, with CSS-based toggling.
Generally speaking, v-if has higher toggle costs while v-show has
higher initial render costs. So prefer v-show if you need to toggle
something very often, and prefer v-if if the condition is unlikely to
change at runtime.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
There are numerous solutions to solving this issue, but let's stick to 3. Options 2 and 3 are better practices, but option 1 works and Vue was designed for this approach even if hardcore developers might frown, but stick yoru comfort level.
Option 1: DOM Manipulation
Your data from a click, async, prop sets a condition for v-if or v-show and your component is shown. Note v-if removes the DOM element where v-show hides the visibility but the element is still in the flow. If you remove the element and add its a complete new init, which sometimes works in your favor when it come to reactivity, but in practice try not to manipulate the DOM as that will always be more expensive then loops, filters, maps, etc.
<template >
<div>
<div v-for="(d, i) in getData"
:key="i">
<div v-if="d.active">
<child-one></child-one>
</div>
<div v-else-if="d.active">
<child-two></child-two>
</div>
</div>
</div>
</template>
<script>
import ChildOne from "./ChildOne";
import ChildTwo from "./ChildTwo";
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
data: [],
}
},
computed: {
getData() {
return this.data;
},
},
mounted() {
// assume thsi woudl come from async but for now ..
this.data = [
{
id: 1,
comp: 'ChildOne',
active: false
},
{
id: 2,
comp: 'ChildTwo',
active: true
},
];
}
}
</script>
Option 2: Vue's <component> component
Always best to use Vue built in component Vue’s element with the is special attribute: <component v-bind:is="currentTabComponent"></component>
In this example we pass a slug or some data attribute to activate the component. Note we have to load the components ahead of time with the components: {}, property for this to work i.e. it has to be ChildOne or ChildTwo as slug string. This is often used with tabs and views to manage and maintain states.
The advantage of this approach is if you have 3 form tabs and you enter data on one and jump to the next and then back the state / data is maintained, unlike v-if where everything will be rerendered / lost.
Vue
<template >
<div>
<component :is="comp"/>
</div>
</template>
<script>
import ChildOne from "./ChildOne";
import ChildTwo from "./ChildTwo";
export default {
components: {
ChildOne,
ChildTwo
},
props: ['slug'],
data() {
return {
comp: 'ChildOne',
}
},
methods: {
setComponent () {
// assume prop slug passed from component or router is one of the components e.g. 'ChildOne'
this.comp = this.slug;
}
},
mounted() {
this.nextTick(this.setModule())
}
}
</script>
Option 3: Vue & Webpack Async and Dynamic components.
When it comes to larger applications or if you use Vuex and Vue Route where you have dynamic and large number of components then there are a number of approaches, but I'll stick to one. Similar to option 2, we are using the component element, but we are using WebPack to find all Vue files recursively with the keyword 'module'. We then load these dynamically / asynchronous --- meaning they will only be loaded when needed and you can see this in action in network console of browser. This means I can build components dynamically (factory pattern) and render them as needed. Example, of this might be if a user adds projects and you have to build and config views dynamically for projects created e.g. using vue router you passed it a ID for a new project, then you would need to dynamically load an existing component or build and load a factory built one.
Note: I'll use v-if on a component element if I have many components and I'm unsure the user will need them. I don't want to maintain state on large collections of components because I will end up memory and with loads of observers / watches / animations will most likely end up with CPU issues
<template >
<div>
<component :is="module" v-if="module"/>
</div>
</template>
<script>
const requireContext = require.context('./', true, /\.module\.vue$/);
const modules = requireContext.keys()
.map(file =>
[file.replace(/(.*\/(.+?)\/)|(\.module.vue$)/g, ''), requireContext(file)]
)
.reduce((components, [name, component]) => {
// console.error("components", components)
components[name] = component.default || component
return components
}, {});
export default {
data() {
return {
module: [],
}
},
props: {
slug: {
type: String,
required: true
}
},
computed: {
getData() {
return this.data;
},
},
methods: {
setModule () {
let module = this.slug;
if (!module || !modules[module]) {
module = this.defaultLayout
}
this.module = modules[module]
}
},
mounted() {
this.nextTick(this.setModule())
}
}
</script>
My initial thought is having additional conditions within a loop would negatively impact performance?
I think you might be confused by this rule in the style guide that says:
Never use v-if on the same element as v-for.
It's only a style issue if you use v-if and v-for on the same element. For example:
<div v-for="user in users" v-if="user.isActive">
But it's not a problem if you use v-if in a "child" element of a v-for. For example:
<div v-for="user in users">
<div v-if="user.isActive">
Using v-if wouldn't have a more negative performance impact than a method. And I'm assuming you would have to do some conditional checks inside your method as well. Remember that even calling a method has some (very small) performance impact.
Once you use Vue, I think it's a good idea not to mix it up with JavaScript DOM methods (like insertBefore). Vue maintains a virtual DOM which helps it to figure out how best to update the DOM when your component data changes. By using JavaScript DOM methods, you won't be taking advantage of Vue's virtual DOM anymore.
By sticking to Vue syntax you also make your code more understandable and probably more maintainable other developers who might read or contribute to your code later on.

(VueJS) Update component whenever displayed

I want a way to run a function (which talks to the backend) whenever a component is re-displayed.
I understand that the mounted hook will fire if the component is re-added to the DOM by a v-if directive. But, if the component is hidden and re-shown via a v-show directive, this will not fire. I need to update the component regardless of what directive is in control of it's visibility.
I looked at the updated hook but this seems to not be the indented use case.
How do I run a function whenever a component is displayed (not only for the first time)?
updated fires whenever data passed to your component changes. Therefore it will work if you pass in whatever condition controls your v-show, as a prop.
Generic example:
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('child', {
props: {
shown: {
type: Boolean,
default: true
}
},
template: '<div>{{shown}}</div>',
mounted() {
console.log('child mounted');
},
updated() {
// runs whenever any prop changes
// (optional condition) only run when component is shown
if (this.shown) {
console.log('child updated');
}
}
});
new Vue({
el: '#app',
data: () => ({
showChild: true
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<label><input type="checkbox" v-model="showChild" /> Show child</label>
<child v-show="showChild" :shown="showChild" />
</div>
Now updated hook works properly, because it fires everytime :shown changes its value, which maps precisely on your show/hide logic.
maybe you can achieve it in two ways
1.use :key
whenever you want to rerender your component whether it is shown, change the value of key can rerender it.
<template>
<h1 :key="key">Text</h1>
</template>
<script>
export default{
data(){
return {
key:this.getRandomString()
}
},
methods(){
getRandomString(length = 32) {
  let chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
  let max_pos = chars.length;
  let random_string = '';
  for (let i = 0; i < length; i++) {
    random_string += chars.charAt(Math.floor(Math.random() * max_pos));
  }
  return random_string;
},
yourMethod(){
// communicate with backend
let data = await axios.get(...);
this.key = this.getRandomString();
}
}
}
</script>
use vm.$forceUpdate()
...
yourMethod(){
// communicate with backend
let data = await axios.get(...);
this.$forceUpdate();
}
...
you could implement this in a couple of ways. However since you would like to got the v-show way, here is how I would suggest you go about it.
v-show (v-show, watcher):
The v-show is definitely dependent on a variable (data, or computed). Create a watcher, to watch that data/computed property change. Depending on the value of the data/computed property, execute whatever function you intend to on the watcher.

Vue: render <script> tag inside a variable (data string)

I'm new to Vue.js
I want to render a script tag inside a variable (data string).
I tried to us a v-html directive to do so, but it doesn't work Nothing is rendered
Any way I can achieve this?
I'd place a v-if directive on the script tag and put the content of it in a variable.
<script v-if="script">
{{script}}
</scrip>
If I understand you correctly, my answer is:
<template>
<div>
{{ strWithScriptTag }}
</div>
</template>
<script>
export default {
name: 'Example',
methods: {
htmlDecode(input) {
const e = document.createElement('div')
e.innerHTML = input
return e.childNodes[0].nodeValue
},
},
computed: {
strWithScriptTag() {
const scriptStr = '<script>https://some.domain.namet</script>'
return this.htmlDecode(scriptStr)
}
},
}
</script>
I think that by safety vue is escaping your <script> automatically and there is no way to avoid this.
Anyway, one thing you can do is eval(this.property) on created() lifecycle hook.
data: {
script: 'alert("this alert will be shown when the component is created")'
},
created() {
eval(this.script)
}
Use it with caution, as stated in vue js docs, this may open XSS attacks in your app

How to set keyup on whole page in Vue.js

Is it possible to set a v-on:keyup.enter on the whole page, not only for an input element in javascript framework Vue.js ?
Perhaps a better way to do this is with a Vue component. This would allow you to control when you listen to events by including or not including the component. Then you could attach event listeners to Nuxt using the no-ssr component.
Here is how you create the component:
<template>
<div></div>
</template>
<script>
export default {
created() {
const component = this;
this.handler = function (e) {
component.$emit('keyup', e);
}
window.addEventListener('keyup', this.handler);
},
beforeDestroy() {
window.removeEventListener('keyup', this.handler);
}
}
</script>
<style lang="stylus" scoped>
div {
display: none;
}
</style>
Then on the page you want to use that component you'd add this HTML:
<keyboard-events v-on:keyup="keyboardEvent"></keyboard-events>
And then you'll have to add your event handler method:
methods: {
keyboardEvent (e) {
if (e.which === 13) {
// run your code
}
}
}
Short answer is yes, but how depends on your context. If you are using vue-router as I am on a current project, you would want to add to the outer-most element you want that applied to. In my case I'm using the actual app.vue entry point's initial div element.
There is one catch that I believe is a hard requirement, the element has to be within the potentially focusable elements. The way I'm dealing with that is setting a -1 tabindex and just declaring my super-hotkeys (mostly for debug purposes right now) on the parent element in my app.
<template>
<div
id="app-main"
tabindex="-1"
#keyup.enter="doSomething"
>
<everything-else></everything-else>
</div>
</template>
EDIT:
As a side note, I also added a touch of additional configuration to my vue-router to make sure the right element is focused when I transition pages. This allows the pageup/pagedown scrolling to already be in the right section based on the content area being the only scrollable section. You'd also have to add the tabindex="-1" to the app-content element as well.
router.afterEach(function (transition) {
document.getElementById('app-content').focus();
});
and the basis of my app-content component:
<template>
<div id="app-content" tabindex="-1">
<router-view
id="app-view"
transition="app-view__transition"
transition-mode="out-in"
></router-view>
</div>
</template>
I created a small npm module that takes care of global keypress events in Vue, hope it makes someone's life easier:
https://github.com/lupas/vue-keypress
My simplest approach:
Add into your root Vue component (or any other component):
new Vue({
//...
created() {
window.addEventListener('keypress', this.onKeyPress);
},
beforeDestroy() {
window.removeEventListener('keypress', this.onKeyPress);
},
methods: {
onKeyPress(e) {
console.log('KEYPRESS EVENT', e)
//... your code
}
}
//...
})
In Vue 3 composition API, you can do it with a composable:
import { onMounted, onUnmounted } from "vue";
export function useKeyupEvent(handler) {
onMounted(() => document.addEventListener("keyup", handler));
onUnmounted(() => document.removeEventListener("keyup", handler));
}
and then in your component setup:
useKeyupEvent( event => console.log(event))