Vue.js directive within a template - vue.js

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/

Related

Vue-Router doesn't work in a Select Option

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>

Is it possible to use inline an $emitted parameter?

In the following code, clicking on the component emits a signal to the parent, who modifies its state inline (in the sense - not via a handler):
Vue.component('my-component', {
template: '<div v-on:click="emitit">click on the component</div>',
methods: {
emitit: function() {
this.$emit('mysignal', 7)
}
}
})
new Vue({
el: "#root",
data: {
from: 0
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="root">
<my-component v-on:mysignal="from=5"></my-component>
from component: {{ from }}
</div>
Is it possible to access the parameter provided via the $emit directly in v-on:mysignal="..."?
I know that I can use a handler defined in the main Vue component but I would like to simplify my code and avoid to have several handlers in methods.
Yes, like this:
<my-component v-on:mysignal="value => from = value"></my-component>
Vue.component('my-component', {
template: '<div v-on:click="emitit">click on the component</div>',
methods: {
emitit: function() {
this.$emit('mysignal', 7)
}
}
})
new Vue({
el: "#root",
data: {
from: 0
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="root">
<my-component v-on:mysignal="value => from = value"></my-component>
from component: {{ from }}
</div>
#click="val => $emit('mysignal', val)"
<div v-on:click="val => $emit('mysignal', val)">click the component</div>'
or even
#click.prevent="e => $emit('mysignal', e.target.value)"

vuejs2 and chosen select issue

Good day, pls have a look at this bin. It was written Vue 0.12 version and chosen js. How can i make it work with vue2 version. i really need this as a directive not as a component.
`<div id='search`-results'>
Vue model value <br>
{{city | json}}
<hr>
Select value:
<!-- note the `v-model` and argument for `v-chosen` -->
<select class="cs-select" v-model="city" options="cities" v-chosen="city"></select>
<select v-model="city" options="cities"></select>
Vue.directive('chosen', {
twoWay: true, // note the two-way binding
bind: function () {
$(this.el)
.chosen({
inherit_select_classes: true,
width: '30%',
disable_search_threshold: 999
})
.change(function(ev) {
this.set(this.el.value);
}.bind(this));
},
update: function(nv, ov) {
// note that we have to notify chosen about update
$(this.el).trigger("chosen:updated");
}
});
var vm = new Vue({
data: {
city: 'Toronto',
cities: [{text: 'Toronto', value: 'Toronto'},
{text: 'Orleans', value: 'Orleans'}]
}
}).$mount("#search-results");
Here it is implemented as a wrapper component that supports v-model and a slot for the options. This makes it a drop-in replacement for a standard select widget, at least as far as basic functionality. The updated(), happily, will notice changes to the options list as well as to the value.
Since two-way directives are not supported in Vue2, I do not believe there is a way to implement this as a directive. If you really need that, you will want to use Vue1.
var vm = new Vue({
el: '#search-results',
data: {
city: 'Toronto',
cities: [{
text: 'Toronto',
value: 'Toronto'
}, {
text: 'Orleans',
value: 'Orleans'
}]
},
components: {
'chosenSelect': {
template: '<select class="cs-select" v-model="proxyValue" ><slot></slot></select>',
props: ['value', 'options'],
computed: {
proxyValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue);
}
}
},
mounted() {
$(this.$el)
.chosen({
inherit_select_classes: true,
width: '30%',
disable_search_threshold: 999
})
.change((ev) => {
this.proxyValue = ev.target.value;
});
},
updated() {
$(this.$el).trigger('chosen:updated');
}
}
}
});
setTimeout(() => { vm.cities.push({text: 'Houston', value: 'Worth it'}); }, 1000);
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/chosen/1.4.2/chosen.proto.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/chosen/1.4.2/chosen.jquery.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/chosen/1.4.2/chosen.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id='search-results'>
Vue model value
<br> {{city | json}}
<hr> Select value:
<chosen-select v-model="city">
<option v-for="item in cities" :value="item.value">{{item.text}}</option>
</chosen-select>
<select v-model="city">
<option v-for="item in cities" :value="item.value">{{item.text}}</option>
</select>
</div>

Vue.js Component with v-model

I have been able to accomplish a single level deep of v-model two-way binding on a custom component, but need to take it one level deeper.
Current working code:
<template lang="html">
<div class="email-edit">
<input ref="email" :value="value.email" #input="updateInput()"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
</div>
</template>
<script type="text/javascript">
import LineEditor from './LineEditor.vue'
export default {
components: {
LineEditor
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input',{
email: this.$refs.email.value,
body: this.$refs.body.value
})
}
},
data: function(){
return {}
},
props: {
value: {
default: {
email: "",
body: ""
},
type:Object
}
}
}
</script>
Used like this: <email-edit-input v-model="emailModel" />
However, if I add this piece, the value no longer propagates upwards:
<div class="email-edit">
<line-editor ref="email" :title="'Email'" :value="value.email" #input="updateInput()"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
</div>
</template>
<script type="text/javascript">
import LineEditor from './LineEditor.vue'
export default {
components: {
LineEditor
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input',{
email: this.$refs.email.value,
body: this.$refs.body.value
})
}
},
data: function(){
return {}
},
props: {
value: {
default: {
email: "",
body: ""
},
type:Object
}
}
}
</script>
Using this second custom component:
<template lang="html">
<div class="line-edit">
<div class="line-edit__title">{{title}}</div>
<input class="line-edit__input" ref="textInput" type="text" :value="value" #input="updateInput()" />
</div>
</template>
<script type="text/javascript">
export default {
components: {
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input', this.$refs.textInput.value)
}
},
data: function(){
return {}
},
props: {
title:{
default:"",
type:String
},
value: {
default: "",
type: String
}
}
}
</script>
The first code-block works fine with just an input. However, using two custom components does not seem to bubble up through both components, only the LineEditor. How do I get these values to bubble up through all custom components, regardless of nesting?
I've updated your code a bit to handle using v-model on your components so that you can pass values down the tree and also back up the tree. I also added watchers to your components so that if you should update the email object value from outside the email editor component, the updates will be reflected in the component.
console.clear()
const LineEditor = {
template:`
<div class="line-edit">
<div class="line-edit__title">{{title}}</div>
<input class="line-edit__input" type="text" v-model="email" #input="$emit('input',email)" />
</div>
`,
watch:{
value(newValue){
this.email = newValue
}
},
data: function(){
return {
email: this.value
}
},
props: {
title:{
default:"",
type:String
},
value: {
default: "",
type: String
}
}
}
const EmailEditor = {
components: {
LineEditor
},
template:`
<div class="email-edit">
<line-editor :title="'Email'" v-model="email" #input="updateInput"/>
<input :value="value.body" v-model="body" #input="updateInput"/>
</div>
`,
watch:{
value(newValue){console.log(newValue)
this.email = newValue.email
this.body = newValue.body
}
},
methods: {
updateInput: function(value){
this.$emit('input', {
email: this.email,
body: this.body
})
},
},
data: function(){
return {
email: this.value.email,
body: this.value.body
}
},
props: {
value: {
default: {
email: "",
body: ""
},
type: Object
}
}
}
new Vue({
el:"#app",
data:{
email: {}
},
components:{
EmailEditor
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<email-editor v-model="email"></email-editor>
<div>
{{email}}
</div>
<button #click="email={email:'testing#email', body: 'testing body' }">change</button>
</div>
In the example above, entering values in the inputs updates the parent. Additionally I added a button that changes the parent's value to simulate the value changing outside the component and the changes being reflected in the components.
There is no real reason to use refs at all for this code.
In my case, having the passthrough manually done on both components did not work. However, replacing my first custom component with this did:
<line-editor ref="email" :title="'Email'" v-model="value.email"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
Using only v-model in the first component and then allowing the second custom component to emit upwards did the trick.

Focus input of freshly added item

So I have a list of items and list of inputs linked to each item via v-for and v-model.
I click a button and add new item to that list. I want to focus input which is linked to newly added item.
Can't figure out how to achieve this goal.
<div id="app">
<div v-for="item in sortedItems">
<input v-model="item">
</div>
<button #click="addItem">
add
</button>
</div>
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
Here's fiddle with sorted list
https://jsfiddle.net/sfL91r95/1/
Thanks
Update: inspired by pkawiak's comment, a directive-based solution. I found that calling focus in the bind section didn't work; I had to use nextTick to delay it.
Vue.directive('focus-on-create', {
// Note: using Vue 1. In Vue 2, el would be a parameter
bind: function() {
Vue.nextTick(() => {
this.el.focus();
})
}
})
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app">
<div v-for="item in sortedItems">
<input v-focus-on-create v-model="item">
</div>
<button #click="addItem">
add
</button>
</div>
Original answer:
Make your input a component so that you can give it a ready hook.
const autofocus = Vue.extend({
template: '<input v-model="item" />',
props: ['item'],
ready: function() {
this.$el.focus();
}
})
new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push(Math.random());
}
},
components: {
autofocus
},
computed: {
sortedItems: function() {
return this.items.sort(function(i1, i2) {
return i1 - i2;
})
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app">
<div v-for="item in sortedItems">
<autofocus :item="item"></autofocus>
</div>
<button #click="addItem">
add
</button>
</div>
I Like to extend #Roy's answer.
if you are using any UI framework then it will create DIV and within the DIV input tag will be created so this Snippet will handle that case.
Vue.directive('focus-on-create', {
bind: function(el) {
Vue.nextTick(() => {
el.querySelector('input').focus()
})
}
})