Access v-for model used within Vue 2 custom directive - vuejs2

Question
How can I update the model data used within a Vue 2 custom directive?
Setup:
<ul>
<li v-for="item in items">
<select my-directive="item">...</select>
</li>
</ul>
So let's say I have a directive with a hook like this:
Vue.directive('chosenjs', {
inserted: function (el, binding, vnode) {
// Here, I'm setting up a callback with the jQuery Chosen library, but this could be any callback.
jQuery(el).chosen().change(function(event, change) {
// CODE HERE...
});
}
});
In the CODE HERE... section, if binding.value is a pointer (array/object), then this is straight-forward. For example, for an array, I'd do e.g. binding.value.push(someValue), and Vue's observable will handle it. But if the value is a primitive, then what can be done?
If the directive is not used within a v-for loop, you can use the vnode to modify the data in the component. It works great as I show here.
But if it is in a v-for, it seems there's no way. Even with access to the binding.expression, there's no way to get at the v-for item.
Background
I'm trying to work with Vue and the ChosenJS jQuery library. I got most of the way there with this answer, but there is a bug when the directive is used within a v-for loop.

Related

How to call a JavaScript function from a Vue.js v-bind

I want to call a plain JavaScript function from inside a Vue.js v-bind attribute. I can do it by channeling it through a Vue.js data variable, but calling a plain JavaScript function directly from v-bind produces an error.
http://jsfiddle.net/edwardtanguay/2st0fruh
How can I call a JavaScript function directly without having to map to a variable or method in Vue.js?
HTML
<div id="app">
<div>
The URL is: {{url}}
</div>
<div>
<a target="_blank" v-bind:href="url">goto URL</a>
</div>
<div>
<a target="_blank" v-bind:href="url2">goto URL2</a>
</div>
<div>
<a target="_blank" v-bind:href="getUrl()">goto URL2 using javascript function</a>
</div>
</div>
JavaScript
function getUrl() {
return 'http://www.amazon.com';
}
var vm = new Vue({
el: '#app',
data: {
url: 'http://www.google.com',
url2: getUrl()
}
});
In order for Vue to be able to bind itself, it needs to be part of the Vue instance. Whilst your function might be accessible under normal circumstances, it's not part of the vue instance and in turn can't be accessed.
As you have already stated, using a data prop moves it into scope of the vue instance. You could also use a computed prop for this if the value is likely to mutate.
As per this post from the vue author(Evan You) regarding using global functions:
Because implicitly falling back to globals will be a maintainability nightmare. When you look at a template you will have no idea whether a variable belongs to the component, or some globals defined by someone you have no idea where.
It's worth noting that if you feel this is something you want to do a lot, you could have a global mixin and specify some methods on there to get the Urls.
There is a way to do this but I don't recommend it:
See this fiddle
Basically, you add your own function to the Vue.prototype (this is how things like Vue Router and Vuex work):
Vue.prototype.$myFunc = function getUrl() {
return 'http://www.amazon.com';
}
Make sure you declare it before you create your var vm = new Vue() call.
Then you can access it via $myFunc() from the template or this.$myFunc() from the vue instance itself.

Using multiple filters in v-for directive in Vue 2.0

Just following a tutorial about Vue filter on v-for directive in Vue 1.x, the author was using multiple filters in one v-for directive, e.g.
<ul v-for="post in posts | filterBy nameFilter in 'name' | filterBy catFilter in 'cat'"></ul>
As far as I know from the official documentation, this syntax is no longer being used in Vue 2.0, I tried to rewrite this line of code into
<ul v-for="post in nameFilter | catFilter"></ul>
I also created two computed functions nameFilter and cateFilter to filter out the posts by name and category. My code is not working, it only listens to the first filter which is nameFilter, cateFilter has no effect at all.
So I was wondering in Vue 2.0, is that still possible to add multiple filters in v-for directive? If it is possible, could you guys please advise the syntax?
If that's impossible, does that mean that I need to create one filter function to do all the filter logic in that single function?
Thanks in advance.
Found the answer from here
Basically, we only need one computed function to return the filtered data for v-for directive, e.g.
<ul v-for="post in filteredPosts"></ul>
If we want to add multiple, just chain all your javascript native .filter functions in that function and return the data. In my case, it would be something like this:
computed: {
filteredPost: function () {
var self = this
return self.posts
.filter(function (post) {
return post.title.indexOf(self.searchQuery) !== -1;
})
.filter(function(post) {
return (post.category.includes(self.selectedCategory))
})
}
}
self.searchQuery and self.selectedCategory are just data properties for this Vue instance in my example, the key takeaway is one computed function with multiple .filter() functions chaining together.
Hope that will help.

Vue2 Custom Directive modifying component call

I am attempting to write a Vue directive that updates the properties of a component before the component is evaluated.
For example, consider the following.
<b-modal v-i18n:title="'i18n.key'">
Hello
</b-modal>
b-modal is a Vue Component and it takes a property called 'title'. I would like to have a custom directive that can set the property title after translating the supplied key.
That is, I would like the above code to get rewritten by the directive to:
<b-modal title="Translated Text">
Hello
</b-modal>
So far I have read the following resources and found no reference on how to do this.
https://css-tricks.com/power-custom-directives-vue/
http://optimizely.github.io/vuejs.org/guide/directives.html
My current attempt looks like this:
Vue.directive('i18n', {
inserted: function (el,binding) {
const i18nKey = binding.value;
const attrName = binding.arg;
el.setAttribute(attrName, i18nKey);
}
})
This attempt sadly falls short. It results in a change to the final DOM element and has no affect on the property being past to the Vue component.
How can I can the above directive be modified to change the properties being past to the b-modal component?

When v-for array created by computed option changs, the DOM doesn't change accordingly

Recently, I've encountered a problem that caused by the computed option of vuejs.
Firstly, I use v-for to loop for an array (soloColImgs) which is created by the computed option.
my HTML
<div class="show-box" v-for="item in soloColImgs" track-by="$index">
<img v-bind:src="item.imgUrl"/>
<a v-bind:href="item.itemUrl" target="_blank"></a>
</div>
my JS
//...
computed: {
soloColImgs :function(){
//....
},
methods: {
change:function(){
this.soloColImgs.pop();
}
}
Secondly, I change the array (soloColImgs) by using pop() or splice() etc...When I look into the console, the array can change accordingly, however, the DOM does't change at all. It would be greatful if anyone can help me out of this.
The point of a computed property is that its determined solely by the function that defines it. You cannot change it directly, you must change it by acting on the dependencies. The dependencies are the properties that are used to calculate the returned value.

Use cases for vue.js directive vs component?

When should I use a directive vs a component in vue.js? I'm implementing some stuff from Bootstrap and it looks like I could do it either way (I'm starting with the dropdown menu).
I get the feeling that a directive is more for manipulating the dom on a single element, while components are for packaging a bunch of data and/or dom manipulation. Is this a good way to look at it?
This Stack Overflow question is the #1 result to the Google query "vue directive vs component". Saurshaz’s answer is currently the accepted one and it’s very wrong in Vue 2.0. I imagine this is leading a lot of people astray so I'm going to weigh in here.
The answer to “should I use a directive or a component in Vue” is almost always a component.
Do you want to have reusable HTML? I.e. reusable widgets? Then use a component. Do you want two of these widgets to have discrete data? Then use a component. The data of one will NOT override the data of another. Maybe that was true in Vue 1.0, I don't know. But it's absolutely not true in Vue 2.0. In Vue 2.0, your components have a data function that returns a unique set of data. Consider this real-life of a Vue dropdown that has an HTML markup similar to the UI Bootstrap dropdown:
<template>
<span class="dropdown sm-dropdown" #click="toggle" :class="{'open': isOpen}">
<a class="dropdown-toggle">
<span class="special-field">{{ label }}</span>
</a>
<ul class="dropdown-menu">
<li v-for="choice in choices">
<a #click.prevent="click(choice)">{{ choice.label }}</a>
</li>
</ul>
</span>
</template>
<script>
export default {
name: 'Dropdown',
props: ['label', 'options', 'onChange'],
data() {
return {
choices: this.options,
isOpen: false
}
},
methods: {
click(option) {
this.onChange(option);
},
toggle() {
this.isOpen = !this.isOpen;
}
}
}
</script>
Now in a parent component, I can do something like this:
<template>
<div class="container">
<dropdown
label="-- Select --"
:options="ratingChoices"
:onChange="toggleChoice"
>
</dropdown>
<dropdown
label="-- Select --"
:options="ratingChoices"
:onChange="toggleChoice"
>
</dropdown>
</div>
</template>
<script>
import Dropdown from '../dropdown/dropdown.component.vue';
export default {
name: 'main-directive',
components: { Dropdown },
methods: {
toggleChoice(newChoice) {
// Save this state to a store, e.g. Vuex
}
},
computed: {
ratingChoices() {
return [{
value: true,
label: 'Yes'
}, {
value: false,
label: 'No'
}]
}
}
}
</script>
There's a decent amount of code here. What's happening is we're setting up a parent component and inside that parent component we have two dropdowns. In other words, the dropdown component is being called twice. The point I'm trying to make in showing this code is this: when you click on the dropdown, the isOpen for that dropdown changes for that directive and for that directive only. Clicking on one of the dropdowns does not affect the other dropdown in any way.
Don't choose between components or directives based on whether or not you're wanting discrete data. Components allow for discrete data.
So when would you want to choose a directive in Vue?
Here are a couple of guidelines that'll hopefully get you thinking in the right direction.
You want to choose a directive when you're wanting to extend the functionality of HTML components and you suspect that you’re going to need this extendability across multiple components and you don't want your DOM to get deeper as a result. To understand what I mean by this, let's look at the directives that Vue provides out of the box. Take its v-for directive for instance. It allows you to loop through a collection. That's very useful and you need to be able to do that in any component you want, and you don't want the DOM to get any deeper. That's a good example of when a directive is the better choice.[1]
You want to choose a directive when you want a single HTML tag to have multiple functionality. For example, an element that both triggers an Ajax request and that has a custom tooltip. Assuming you want tooltips on elements other than Ajax-triggering elements, it makes sense to split these up into two different things. In this example I would make the tooltip a directive and the Ajax feature driven by a component so I could take advantage of the built-in #click directive that’s available in components.
1 A footnote for the more curious. In theory v-for could have been made as a component, but doing so would have required a deeper-than-necessary DOM every time you wanted to use v-for as well as a more awkward syntax. If Vue had chosen to make a component out of it, instead of this:
<a v-for="link in links" :href="link.href">link.anchor</a>
The syntax would have had to have been this:
<v-for items="link in links">
<a :href="link.href">link.anchor</a>
</v-for>
Not only is this clumsy, but since the component code would have needed to implement the <slot></slot> syntax in order to get the innerHTML, and since slots cannot be immediate children of a <template> declaration (since there's no guarantee that slot markup has a single node of entry at its top level), this means there would have to be a surrounding top-level element in the component definition for v-for. Hence the DOM would get deeper than necessary. Directive was unequivocally the right choice here.
I think of it this way:
Components define widgets - these are sections of html that have behavior associated with them.
Directives modify behavior of sections of html (which may or may not be widgets).
I think this difference is better explained with two examples.
Components: are wrappers that are best suited when you need to insert (or add) your own HTML tags over something to render it. E.g. a widget, a custom button, etc where you would need to add some HTML tags to show it properly.
Directives: don't add tags but rather give you direct access to the HTML tag (to which you have added the directive). This gives you access to modify the attributes of that HTML element directly. E.g. initializing a tooltip, set css styles, bind to an event, etc.
Reusability is a reason for using directives,
While Components are also creating reusable 'widgets', two components in the same html system would overwrite the previous ones 'data', So think of directives in a case like this.
Another point worth thinking of is - Can user be using it via HTML only after some instructions ?