Vue-Router doesn't work in a Select Option - vue.js

I I try to create a simple navigation from a select option but without results.
<router-link> does not work inside <select><option>
Here's what I tried :
vue
<div>
<select v-model="selected">
<option v-for="orga in organisations" :key="orga.name">
<router-link :to="{name: 'Box', params: {orga: orga.route}}">
{{ orga.name }}
</router-link>
</option>
</select>
</div>
javascript
data() {
return {
selected: this.$route.params.orga,
organisations: [
{ name: "Abc", route: "abc" },
{ name: "Lmn", route: "lmn" },
{ name: "Xyz", route: "xyz" }
]
};
}

If you want to change your view based on the select option, you can't use a router-link inside an option tag.
However, you can achieve this by a workaround shown below. Here we will be switching the views based on the select option and changing the route.
Vue.component('compA', {
template: '<div>{{name}}</div>',
data() {
return {
name: 'Component A'
}
}
})
Vue.component('compB', {
template: '<div>{{name}}</div>',
data() {
return {
name: 'Component B'
}
}
})
const routes = [{
path: '/a',
component: {
template: '<compA/>'
}
},
{
path: '/b',
component: {
template: '<compB/>'
}
}
];
const router = new VueRouter({
routes
})
new Vue({
el: "#app",
data() {
return {
selected: ''
}
},
router,
methods: {
routeChange: function(e) {
this.$router.push(e.target.value)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<div>
<select #change="routeChange">
<option></option>
<option v-for="(c, i) in ['a', 'b']" :key="i">
{{ c }}
</option>
</select>
</div>
<router-view/>
</div>
But this scenario can be alternatively achieved by dynamic components. The docs explains more about this which can be used to switch between components or dynamic render.
Vue.component('CompA', {
template: '<div>new component A</div>'
})
Vue.component('CompB', {
template: '<div>new component B</div>'
})
new Vue({
el: "#app",
data() {
return {
value: ""
}
},
template: `
<div>
<select v-model="value">
<option v-for="c in ['compA', 'compB']">{{c}}</option>
</select>
<component :is="value" />
</div>
`
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<div id="app"></div>

Related

Vue resets child Component data on props update

I have child component, that has some internal data, that should not be changed from outside. But when I update prop from parent component, this internal data is reset.
Basically in example below, when we change title from outside, value is set back to empty ''. How can I make value persistent with Child component props update?
Child.vue
<template>
<div class="child">
<h2>{{title}}</h2>
<input type="text" :value="value" v-on:change="$emit('change', $event.target.value)">
</div>
</template>
<script>
export default {
props: {
title: {
default: 'Just title'
}
},
data() {
return {
value: ''
}
}
}
</script>
Parent.vue
<template>
<div class="parent">
<Child :title="title" v-on:change="processTitle($event)"></Child>
</div>
</template>
<script>
import Child from './Child';
export default {
data() {
return {
title: 'Title from parent'
}
},
methods: {
processTitle(value) {
this.title = value.reverse();
}
}
}
</script>
You are not setting the value data attribute, :value=value means that "if value changes, the input value should pick up that change". But value doesn't change. Use v-model instead if you want to keep it simple.
Vue.component("Child", {
props: {
title: {
default: 'Just title'
}
},
data() {
return {
value: ''
}
},
template: `
<div class="child">
<h2>{{title}}</h2>
<input type="text" v-model="value" v-on:change="$emit('change', $event.target.value)">
</div>
`
})
new Vue({
el: "#app",
data() {
return {
title: 'Title from parent'
}
},
methods: {
processTitle(value) {
this.title = value.split("").reverse().join("");
}
},
template: `
<div class="parent">
<child :title="title" v-on:change="processTitle($event)"></child>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
EDIT
Also, if you want a continuous effect, don't use #change - use #input instead:
Vue.component("Child", {
props: {
title: {
default: 'Just title'
}
},
data() {
return {
value: ''
}
},
template: `
<div class="child">
<h2>{{title}}</h2>
<input type="text" v-model="value" v-on:input="$emit('change', $event.target.value)">
</div>
`
})
new Vue({
el: "#app",
data() {
return {
title: 'Title from parent'
}
},
methods: {
processTitle(value) {
this.title = value.split("").reverse().join("");
}
},
template: `
<div class="parent">
<child :title="title" v-on:change="processTitle($event)"></child>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

how to use router-link custom navigation instead of `to` prop

<router-link to="/" tag="a" :title="title">
<span class="icon icon-home i-large" />
<span class="class-2">Name</span>
</router-link>
This is what I have...
As you can see , I am using to prop. But I don't want to use it. Whenever someone clicks on this, i want to execute a function and use programatic navigation.
How is this possible? the structure and htmls that I have shouldn't change and it still should work as expected.
Something like this you mean?
<a #click="functionThatExecutesMyCode">
<span class="icon icon-home i-large" />
<span class="class-2">Name</span>
</a>
functionThatExecutesMyCode() {
this.$router.push({ to: 'newRoute'})
}
Else you should use the navigation guards: https://router.vuejs.org/guide/advanced/navigation-guards.html
I don't think it's possible to change :to props to add a function, because it is predefined as only accept a string in its docs. https://router.vuejs.org/api/#to
However, I suggest another way to implement what you want, you can add the logic in beforeEach hook. For example
router.beforeEach( (to, from, next) => {
// implement your logic here, for example if no internet, go to error page
if (!navigator.onLine && to.name !== "NetworkError") {
next({ name: "NetworkError" });
return false;
}
next();
}
For more info, look at their docs: https://router.vuejs.org/api/#router-beforeeach
It depends on what you exactly want.
There are easier solutions, like:
const Root = {
template: '<div>Root</div>'
}
const Route1 = {
template: '<div>Route1</div>'
}
const routes = [{
path: '/',
name: 'root',
component: Root
},
{
path: '/1',
name: 'route1',
component: Route1
}
]
const router = new VueRouter({
routes // short for `routes: routes`
})
new Vue({
el: "#app",
router,
data: {
selected: 'root',
options: ['root', 'route1']
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<select v-model="selected">
<option v-for="(option, i) in options">
{{ option }}
</option>
</select>
<router-view></router-view>
<router-link :to="{ name: selected }">{{ selected }}</router-link>
</div>
In this solution I used a named route, and the "name" can be changed by the select input.
The next solution is to incorporate a function:
const Root = {
template: '<div>Root</div>'
}
const Route1 = {
template: '<div>Route1</div>'
}
const routes = [{
path: '/',
name: 'root',
component: Root
},
{
path: '/1',
name: 'route1',
component: Route1
}
]
const router = new VueRouter({
routes // short for `routes: routes`
})
new Vue({
el: "#app",
router,
data: {
selected: 'root',
options: ['root', 'route1']
},
methods: {
changeRouteName() {
return this.selected
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<select v-model="selected">
<option v-for="(option, i) in options">
{{ option }}
</option>
</select>
<router-view></router-view>
<router-link :to="{ name: changeRouteName() }">{{ selected }}</router-link>
</div>
By adding a method to resolve the name of the component, it's possible to build ANY logic on the to of the <router-link. You could also use path instead of name of course - I just like named components.
It does not remove the click event from the to prop, but you can do your logic before returning the name it's "waiting" for.

How to get data from component

I am new to Vue.js.
list.vue:
<template>
<div class="m-products-list">
<ul #mouseover="over">
<Item
v-for="(item,idx) in parentList"
location="item.location"
:key="idx"
:meta="item"/>
</ul>
</div>
</template>
<script>
export default {
...
methods: {
over: function (e) {
let dom = e.target;
let tag = dom.tagName.toLowerCase();
if (tag === 'dd') {
console.log(dom.getAttribute('location'))
}
}
}
}
</script>
The Item is from its parent component. And I want to get item.location in over() when I mouseover an item, but console.log always returns null. Anyone have an idea?
This is technically possible (but there may be a better alternative shown in the next section) by setting a data-* attribute in the Item.
// Item.vue
<li :data-location="location" class="item" ... >
new Vue({
el: '#app',
data() {
return {
items: [
{id: 1, location: 'New York'},
{id: 2, location: 'Los Angeles'},
{id: 3, location: 'Chicago'},
]
}
},
components: {
Item: {
props: ['location'],
template: `<li :data-location="location" class="item">{{location}}</li>`,
}
},
methods: {
over(e) {
console.log(e.target.dataset.location)
}
}
})
<script src="https://unpkg.com/vue#2.6.7/dist/vue.min.js"></script>
<div id="app">
<ul #mouseover="over">
<Item v-for="item in items"
:key="item.id"
:location="item.location" />
</ul>
</div>
A better solution that doesn't require DOM manipulation would be to use the data model in Vue and to move the mouseover event listener to the Item:
Change the argument of over() to the location name (previously the event object):
methods: {
over(location) {
/* ... */
}
}
Move the #mouseover event-listener annotation from ul to the Item in the template, and pass the item.location as an argument:
<ul>
<Item v-for="item in items" #mouseover="over(item.location)" ... />
</ul>
Edit the Item's template to forward its mouseover event to the parent:
// Item.vue
<li #mouseover="$emit('mouseover', $event)" ... >
new Vue({
el: '#app',
data() {
return {
items: [
{id: 1, location: 'New York'},
{id: 2, location: 'Los Angeles'},
{id: 3, location: 'Chicago'},
]
}
},
components: {
Item: {
props: ['location'],
template: `<li #mouseover="$emit('mouseover', $event)" class="item">{{location}}</li>`,
}
},
methods: {
over(location) {
console.log(location)
}
}
})
<script src="https://unpkg.com/vue#2.6.7/dist/vue.min.js"></script>
<div id="app">
<ul>
<Item v-for="item in items"
:key="item.id"
:location="item.location"
#mouseover="over(item.location)" />
</ul>
</div>

How to wire a conditional block in option HTML element using vue 2

I'm new to vue and have a problem with a conditonal rendering (v-if) in an option element.
when i have an empty array i would like to show an disabled option otherwise it show me the countries. Like in this description https://v2.vuejs.org/v2/guide/conditional.html but unfortunately this is not working on an option HTML element. What i'm missing ?
new Vue({
el: '#app',
data: {
selected: '',
optionAvailable: true,
countries: [
//{ name: 'USA', population: '300M' },
//{ name: 'Canada', population: '100M' },
//{ name: 'Germany', population: '80M' },
]
},
created() {
if(!this.countries.length > 0) {
return this.optionAvailable = false;
}
return true;
},
methods: {
onChange(event) {
this.selected = event.value;
}
}
})
html:
<div id="app">
<select #change="onChange($event.target)">
<option v-if="optionAvailable" v-for="(country,index) in countries" :value="country.population">{{ country.name }}</option>
<option v-else disabled>-</option>
</select>
<br>
<span>{{ selected }}</span>
</div>
here jsfiddle:
https://jsfiddle.net/50wL7mdz/434002/
Try this:
template
<div id="app">
<select #change="onChange($event.target)">
<option v-if="countries.length" v-for="(country,index) in countries" :value="country.population">{{ country.name}}</option>
<option v-else>----</option>
</select>
<span>{{ selected }}</span>
</div>
script
new Vue({
el: '#app',
data: {
selected: '',
optionAvailable: true,
countries: [
{ name: 'USA', population: '300M' },
{ name: 'Canada', population: '100M' },
{ name: 'Germany', population: '80M' } ]
},
methods: {
onChange(event) {
this.selected = event.value;
}
}
})
You don't need the created hook. You can tell v-if to look at the length of countries directly.
https://jsfiddle.net/z4mu8L9e/8/
try this template
<div id="app">
<select #change="onChange($event.target)" :disabled="!countries.length">
<option v-if="!countries.length" value="">No available countries</option>
<option v-for="(country,index) in countries" :value="country.population">{{ country.name }}</option>
</select>
<br>
<span>{{ selected }}</span>
</div>
Script
new Vue({
el: '#app',
data: {
selected: '',
countries: []
},
methods: {
onChange(event) {
this.selected = event.value;
}
}
})

Vue.js directive within a template

Based on this example https://vuejs.org/examples/select2.html
I try to create component for reuse in a future.
Unfortunately, my code doesn't work.
HTML:
<template id="my-template">
<p>Selected: {{selected}}</p>
<select v-select="selected" :options="options">
<option value="0">default</option>
</select>
</template>
<div id="app">
<my-component></my-component>
</div>
Vue:
Vue.component('my-component', {
template: '#my-template'
})
Vue.directive('select', {
twoWay: true,
priority: 1000,
params: ['options'],
bind: function () {
var self = this
$(this.el)
.select2({
data: this.params.options
})
.on('change', function () {
self.set(this.value)
})
},
update: function (value) {
$(this.el).val(value).trigger('change')
},
unbind: function () {
$(this.el).off().select2('destroy')
}
})
var vm = new Vue({
el: '#app',
data: {
selected: 0,
options: [
{ id: 1, text: 'hello' },
{ id: 2, text: 'what' }
]
}
})
https://jsfiddle.net/xz62be63/
Thank you for any help!
your problem has nothing to do with the directive itself.
selected and options is defined in the data of your Vue main instance, not in the data of my-component - so it's not available in its template.
But you can pass it from the main instance to the component using props:
<div id="app">
<my-component :selected.sync="selected" :options="options"></my-component>
<!-- added the .sync modifier to transfer the value change back up to the main instance-->
</div>
and in the code:
Vue.component('my-component', {
template: '#my-template',
props: ['selected','options']
})
updated fiddle: https://jsfiddle.net/Linusborg/rj1kLLuc/