Getting element (button) data attribute with vue3 - vue.js

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.

Related

What's the difference in Vue #click between calling a method directly and calling in inside a function

I'm learning nuxt and the tutor has a function to add an item called updateTodo and attached it to a button as the follwoing
script
<script setup>
const updateTodo = async (id) => {
if (!input) return;
await $fetch(`/api/todo/${id}`, { method: 'PUT' });
};
</script>
template
<template>
<div class="container">
<input type="text" placeholder="Add a new todo..." v-model="input" />
<NButton #click="() => updateTodo(todo.id)">Add</NButton>
</div>
</template>
I dont know why he didn't call it directly (eg. #click="updateTodo(todo.id)). I tried to do it and it worked. Is there a reason i dont know or it's just a preference?
Both are allowed.
https://vuejs.org/guide/essentials/event-handling.html#listening-to-events
The usage would be v-on:click="handler" or with the shortcut, #click="handler". The handler value can be one of the following:
Inline handlers: Inline JavaScript to be executed when the event is triggered (similar to the native onclick attribute).
Method handlers: A property name or path that points to a method defined on the component.
However, note that this isn't necessarily always the case. In some libraries/frameworks, something like onclick="yourFunction()" might call the method immediately, and use the returned value as an event listener. This is usually not what you want, unless you're returning a function.
in this case you call directly, you don't require event parmas here.
<NButton #click="updateTodo(todo.id)">Add</NButton>
but in a certain case, you need event parament to get the value of the input (e.target.value) or want to prevent the default of that event(e.preventDefault) i.e
a. you call anyFunction in each time, then
<input #input="anyFunction">
b. but you want to pass function if enter value is greater than 100 or anything else then you need to pass as function so you get event as params like
<input #input="(e) => e.target.value > 100 && anyFunction">
In other words, if there is no argument in the function for the particular event then you don't need to pass the function. else you need to pass the function.

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?).

Convert from jQuery to Vue?

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

Toggle in loop?

I wish to toggle (show/hide) a list when clicking on a title, but cant get the following to work
I have this:
<!-- Title -->
<div v-for="(subitem, index) in item" v-if="index === 0" #click="toggle(subitem)">
{{subitem.system_name}} - ({{item.length}})
</div>
<!-- All title items that should expand on click "Title" -->
<div v-if="subitem.clicked">
<p>{{subitem.system_name}}</p>
</div>
When clicking on the im triggering a toggle function called toggle, that sets a property on the item "clicked" to true or false (I should mention that this property does not already exist on the object, and I haven't got the possiblity add it, as we get the JSON from an API)
The toggle function is this:
toggle: function (data) {
data.clicked = !data.clicked;
},
Now, when I do this above, I get an error saying: "Property or method "subitem" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option"
Im guessing I get this because the "clicked" property doesnt exist in the object... So how do I work around this? Can't see any real solution ?
You initialize subitem in the v-for as a single item in the loop, but you are using it outside the element which has v-for loop on it. That's the reason you get that warning:
Property or method "subitem" is not defined on the instance but referenced during render.
Make sure to declare reactive data properties in the data option"
So move the div you want to toggle inside the div which has the v-for loop on it
<!-- Title -->
<div v-for="(subitem, index) in item" v-if="index === 0" #click="toggle(subitem)">
{{subitem.system_name}} - ({{item.length}})
<!-- All title items that should expand on click "Title" -->
<div v-if="subitem.clicked">
<p>{{subitem.system_name}}</p>
</div>
</div>
And coming yo the 2nd issue, as you mention the subitem obj does not have clicked property when you fetch the json from api.
You cannot add root level reactive properties after the vue instance is created.
Since you want to toggle the appeance of the div based on the property clicked which is not available at the time vue instance is created you should use vm.$set() to add reactive properties or Object.assign() to add properties to existing object. See Reactivity in depth
So in your case
toggle: function (data) {
if(data.hasOwnProperty('clicked')){
data.clicked = !data.clicked;
}else{
//since its the first time , set the value pf clicked to true to show the subitem
data = Object.assign({}, data, {clicked: true});
}
},

When does binding to ref attribute become valid in Aurelia?

This is a follow up to this question: Access a DOM element in Aurelia
Is there a hook in the Screen Activation Lifecycle which allows me to run code after ref bindings have been set up? Currently it seems like there is a period of time after the activate hook is called when the ref bindings are not set up yet and then at some point they get activated. I tested this by adding a <div ref="myDiv"></div> to near the bottom of welcome.html in a cloned version of the latest (v0.13.0) skeleton-navigation repo and testing the existence of the reference in the view-model like this:
export class Welcome{
heading = 'Welcome to the Aurelia Navigation App!';
firstName = 'John';
lastName = 'Doe';
testMyDiv() {
console.log("Getting my div")
console.log(this.myDiv)
}
get fullName(){
this.testMyDiv()
return `${this.firstName} ${this.lastName}`;
}
welcome(){
alert(`Welcome, ${this.fullName}!`);
}
}
A snippet of the bottom of the template...
<button type="submit" class="btn btn-default">Submit</button>
</form>
<div ref="myDiv"></div>
</section>
</template>
This is a snapshot of what I see in the console...
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 <div ref=​"myDiv" class=​"au-target">​</div>​
welcome.js:10 Getting my div
welcome.js:11 <div ref=​"myDiv" class=​"au-target">​</div>​
(continues)
The print outs like this goes on indefinitely. You can see that fullName() is being called regularly to update the screen if the name changes (I assume this is the dirty checking)... but you can see that at the beginning there is a period when the referenced div is NOT valid as a property of the view-model, and then it IS valid. Can someone explain this? Is there a way to hook into the view-model after the ref becomes valid?
In general, bindings are processed and available after the bind callback. However, in this case since you need to access the DOM element, you will need the ViewModel to be bound and attached to the view, so use the attached callback.
class ViewModel {
bind() {
this.refItem == undefined; // true
}
attached() {
this.refItem == undefined; // false
}
}
As you noted in the comments, more information on the activator callbacks is available here: http://aurelia.io/docs.html#extending-html