Convert from jQuery to Vue? - vue.js

I would like to transform the following code which is in javascript:
$('a').click(function() {
$(this).find('i').toggleClass('fa-heartbeat');
});
in vue.js.
function name: like
javascript test: https://jsfiddle.net/jsk590ep/

In Vue, you typically don't select and manipulate DOM elements directly, you rather bind data to parts of the markup within your Vue components.
That said: You don't even need a function for that.
Simply
add a data element that indicates which state the icon is in (see https://v2.vuejs.org/v2/guide/#Declarative-Rendering)
change its value in the #click handler of the surrounding a, see https://v2.vuejs.org/v2/guide/events.html#Listening-to-Events
conditionally bind the fa classes based on the state to the icon, see https://v2.vuejs.org/v2/guide/class-and-style.html
<a href="#" #click="liked = !liked">
<i :class="['fa', liked ? 'fa-heartbeat' : 'fa-plus-circle']"></i>
</a>
When looking at the vue docs, note that #click in the example is a shortcut for v-on:click and :class for v-bind:class.
Working example here: https://codesandbox.io/s/stack-overflow-q-57403395-ul62e?module=/src/App.vue

Related

Getting element (button) data attribute with vue3

I have the following html div. The {{ }} represent liquid syntax and it renders server side.
<div class="two columns">
<button
data-value='{{ value }}'
class="button-selection"
:class="selectionButtonClass($event)"
#click="selectionButton($event)"
>
<span class="text">{{ value }}</span>
</button>
</div>
In a vue 3 instance I have the following method in the same page.
selectionButtonClass(el) {
console.log('checking val');
console.log(el);
}
My goal is to set a conditional class in the selectionButton method but I can't get the element to get the data attribute. The above appears in the console log as undefined. However the #click does show the event obviously it's recognize the onclick but not the class method check.
$event is only available to event handlers. #click="selectionButton($event)" defines an inline event handler, while :class="selectionButtonClass($event)" is not an event handler.
To get the element, you need to add a ref attribute to the <button>:
<button
ref="selectionButton"
data-value='{{ value }}'
class="button-selection"
:class="selectionButtonClass($event)"
#click="selectionButton($event)"
>
And access it by this.$refs.selectionButton, assuming you are using the options API. However, the ref is available only after the component is mounted. Thus you need to handle the case where the ref is null.
More on template refs: https://vuejs.org/guide/essentials/template-refs.html
Since you are using server side rendering, I think it would be better to render the value as a parameter of the selectionButton function on the server side.

Watching a dynamically rendered field in Laravel Nova Vue component

In Laravel Nova, action modals are rendered in Vue by retrieving a list of fields to display through a dynamic component. I have replaced the action modal with own custom component, but am struggling to achieve the effect I want without also extending the entire set of components for rendering form fields.
I have my CustomResourceIndex.vue, containing a conditionally loaded (via v-if) ActionModal.vue, in which the form fields are rendered like so:
<div class="action" v-for="field in action.fields" :key="field.attribute">
<component
:is="'form-' + field.component"
:resource-name="resourceName"
:field="field"
/>
</div>
where the actual form field component is chosen based on the field.component value.
Those form fields (which I ideally do not want to have to extend and edit) are rendered like so:
<template>
<default-field :field="field" :errors="errors">
<template slot="field">
<input
class="w-full form-control form-input form-input-bordered"
:id="field.attribute"
:dusk="field.attribute"
v-model="value"
v-bind="extraAttributes"
:disabled="isReadonly"
/>
</template>
</default-field>
</template>
I would like to watch the value of specific fields and run methods when they change. Unfortunately due to a lack of ref attribute on the input elements or access to the value that the form element is bound to, I'm not sure how I can accomplish that from within ActionModal.vue.
I am hoping that because I have access to the ids still, there is some potential way for me to emulate this behavior.
Many resources I've found on my own have told me that anything with an ID is accessible via this.$refs but that does not seem to be true. I can only see elements that have an explicitly declared ref attribute in this.$refs, so I am not sure if I've misunderstood something there.
I would recommend looking into VueJS watch property.
You can listen to function calls, value changes etc.
watch: {
'field.component': function(newVal, oldVal) {
console.log('value changed from ' + oldVal + ' to ' + newVal);
},
},
Are those components triggering events? Try looking into the events tab of the Vue DevTools to see if some events are triggered from the default-field component when you update the value.
My guess is that you could write something like:
<div class="action" v-for="field in action.fields" :key="field.attribute">
<component
:is="'form-' + field.component"
:resource-name="resourceName"
:field="field"
#input="doSomething($event)"
/>
</div>
The $event value being the new value of the field.
Hit me on the comments if you have more info on the behavior of the default form fields (Are their complete code accessible somewhere?).

Access v-for model used within Vue 2 custom directive

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.

Vue.js - Typeahead Directive

Vue.js Directive for Typeahead
I am trying to write a Vue Directive for typeahead.js functionality. I was able to instantiate typeahead on the input form control and also assign it options and dataset. Event handling too was not a problem.
The Issue :
Now, I also want to control the typeahead input element from the component. For example, I would like to control these methods $(el).typeahead('destroy') or $(el).typeahead('open'), etc. from the component.
How can i call these methods from the Component? Is it possible ?
Ok, I just figured it out 2 minutes after typing the question.
I add a ref to the input tag.
<!-- bindings is an object which has the "options" and "dataset" -->
<input ref="ttInput" v-typeahead="bindings" />
Then, I have added a method in the component which executes typeahead methods from the component:
...
methods: {
methodHook (action) {
const el = this.$refs.ttInput
$(el).typeahead(action)
}
}
...
So, now, in the html template, i can have a button/buttons:
<button type="button" #click="methodHook('open')" >Open</button>
<button type="button" #click="methodHook('close')">Close</button>
Works like a charm.
Thanks

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 ?