I am using a custom v-directive "v-clip" and it requires a value. In my case i want to use 'data binding'. Which means the value for v-clip can change on the fly from other interactions on the website.
In this case I've implemented a simple example where every time a user clicks a button called 'Counter' it increments the value by 1. How can I retrieve the most up to date value when the user clicks on the custom directive which prints to the console.
Is there a way i can use vnode or something to retrieve the value. I would imagine the directive would be able to someone get the updated value.
clip directive
Vue.directive('clip', {
bind: (el, binding, vnode) => {
const clickEventHandler = (event) => {
console.log(binding.value)
}
el.addEventListener('click', clickEventHandler)
},
})
It's used like this where the variable counter is dynamically changed when a button is clicked in the ui.
<div v-clip="counter">Clip A {{ counter }}</div>
From Vue.js 2 guide:
If you need to share information across hooks, it is recommended to do so through element’s dataset.
So, if I understand correctly your question, you can try in the following way:
Vue.directive('clip', {
bind: (el, binding) => {
const clickEventHandler = () => {
console.log(el.getAttribute('data-clipvalue'))
}
el.addEventListener('click', clickEventHandler)
el.setAttribute('data-clipvalue',binding.value)
},
update: (el,binding) => {
el.setAttribute('data-clipvalue',binding.value)
}
})
Related
I am trying to create a code to fill a form in which the options of some selectors depend on what is selected in a previous selector. I am using the jQuery library to create the events.
I would like to be able to make the event be able to observe the variables even when the code that creates the event has already finished.
Below is an example of what I need to do.
function preValForm() {
App.get('params').then((params) => {
let devices = []
params.devices.forEach(i => { // Here is the params Object { devices: [[...]] , stock: [[...]] }
if (i[0] == "Mobile" && devices.indexOf(i[1]) == -1) { // Not insert duplicates to the array
devices.push(i[1]) // Insert value to array if isn't duplicate
}
})
$('#listDevices').html(devices.map(d => {
return `<option value="${d}">${d}</option>`
}).join('')) // Insert option values to a select element in DOM
// Here is the problem ...
$('#listDevices').on('change', (ev) => { // Create the event when selector #listDevices changes
let stock = [] // Declare variable stock
params.stock.forEach(s => { // ! params is not defined **! -- This is the error**
if (s[1] == ev.target.value && stock.indexOf(s[1]) == -1) {
stock.push(s[1])
}
})
$('#stockOptions').html(stock.map(k => {
return `<option value="${k}">${k}</option>`
}).join(''))
})
})
}
I have seen that this can be done, for example with the SweetAlert2 library you see this behavior where you define an event called preConfirm and when executed the variables are still visible for that event. I would like to be able to do something similar to that behavior, that when I make a change in the selection the event is executed and that event recognizes my variable 'params'.
Thanks
Since there's no IntersectionObserver library for Vue 3, I'd like to implement my own little solution.
I read about directives, and from what I understand it's the right direction. (?)
My local directive:
directives: {
'lazyload': {
mounted(el) {
if ('IntersectionObserver' in window) {
let intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const lazyImage = entry.target;
// set data-srcset as image srcset
lazyImage.srcset = lazyImage.getAttribute('data-srcset');
// add class after image has loaded
lazyImage.addEventListener('load', () => {
lazyImage.classList.add('is-lazyloaded');
};
// unobserve after
lazyLoadItemObserver.unobserve(lazyImage);
}
});
});
// observe every image
const lazyLoadItems = document.querySelectorAll('[lazyload]')
lazyLoadItems.forEach((lazyImage) => {
lazyLoadItemObserver.observe(lazyImage);
});
}
}
}
}
The vanilla way would be to make an array out of every <IMG> element that has the attribute lazyload for instance. The thing I don't get is how to make an array out of every <IMG> that has the v-lazyload binding.
Something like "if this image has the v-lazyload binding, put it into the IntersectionObserver's array." I might understand it wrong though.
So I'd like to come up with a directive which sets one single IntersectionObserver which observes an array of all the images that have the v-lazyload binding.
The v-lazyload directive will be on the target elements already, so no need to query the document.
In the directive's mounted hook, you can attach an IntersectionObserver instance to the parent node if it doesn't already exist. Then, use that instance to observe the target element (also unobserve the element in unmounted):
directives: {
mounted(el) {
el.parentNode.lazyLoadItemObserver = el.parentNode.lazyLoadItemObserver || new IntersectionObserver(/*...*/)
el.parentNode.lazyLoadItemObserver.observe(el)
},
unmounted(el) {
el.parentNode.lazyLoadItemObserver.unobserve(el)
},
}
demo
In Vue.js, is there a way to register an event if any component updates its data?
My usecase: I am modeling a RPG character via a set of Javascript classes. The TCharacter class has several attributes that can be modified: name, level, HP, magic. While "name" is a simple string, "HP" and "magic" is a custom class TResource which has its own consumption and refill rules.
Instance of the TCharacter class is a source of truth, and I created some Vue components that are views of it.
I created a character component and a resource component in Vue, vaguely like this:
<div class=template id=character>
<input v-model="ch.name">
<resource :attr="ch.magic"></resource>
<resource :attr="ch.hp"></resource>
</div>
<div class="template" id="resource">
you have {{ attr.available }} points
<button #click="attr.consume">X</button>
</div>
<div id="main">
<character :ch="lancelot"></character>
</div>
and the javascript:
class TCharacter {
constructor() {
this.name = "Lancelot"
this.hp = new Resource(20)
this.magic = new Resource(10)
}
}
class TResource {
constructor(limit) {
this.available = limit
this.limit = limit
}
consume() {
if (this.available > 0) this.available--;
}
}
let lancelot = new TCharacter()
Vue.component('character', {
template: '#character',
props: ['ch'],
})
Vue.component('resource', {
template: '#resource',
props: ['attr'],
})
new Vue({
el: "#main",
data() { return { lancelot } }
})
(I'm not sure the code works exactly as written, but hopefully the intent is clear. Something very similar to this is already working for me.)
Now, I'd like to save the character object to localstorage every time the user makes a modification: changes its name, clicks on a button that consumes a point of magic, etc.
So for instance, I want to be notified that the value of ch.name changed because the user typed something into the input box. Or that a magic point was lost because the user clicked a button for that.
I could detect changes to the character component by installing an updated() handler, which notifies me whenever a DOM is modified (viz). However, this won't trigger when the child component resource is modified. I'd need to add a separate updated() handler to all other components. This gets tedious very fast.
I'm imagining something like a global updated() handler that would fire any time any component has registered a change. Or better, a way to specify that update should fire on component's children changes as well.
edit: I have reworded parts of the question to clarify what I'm trying to accomplish.
Some of you already suggested Vuex. But, from what I understood, Vuex enforces being the single source of truth -- I already have a single source of truth. How is Vuex different / better?
You're going to need a serialized version of lancelot to write out. You can do that with a computed. Then you can watch the computed to see when anything changes.
Alternatively, you could watch each individual trait, and write it out as it changes.
class TCharacter {
constructor() {
this.name = "Lancelot"
this.hp = new TResource(20)
this.magic = new TResource(10)
}
}
class TResource {
constructor(limit) {
this.available = limit
this.limit = limit
}
consume() {
if (this.available > 0) this.available--;
}
}
let lancelot = new TCharacter()
Vue.component('character', {
template: '#character',
props: ['ch'],
})
Vue.component('resource', {
template: '#resource',
props: ['attr'],
})
const vm = new Vue({
el: "#main",
data() {
return {
lancelot
}
},
computed: {
serializedLancelot() {
return JSON.stringify(this.lancelot);
}
},
watch: {
serializedLancelot(newValue) {
console.log("Save update:", newValue);
}
}
});
setTimeout(() => {
vm.lancelot.hp.consume();
}, 500);
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="main">
</div>
Am not sure I understand the use case in entirety, but if my assumption is right, you need to update components based on an object's update (updates to properties of an object), for that you could use Vuex . Although am not sure if you are restricted to use an additional library
Here as an example, you could add a state value named character which is an object, something along the lines of
const state = {
character = {};
}
And now you can mutate this using vuex mutations.
commit('set_character', your_new_value)
Now since you said you need to update all or some components based on any mutation to character, use vuex plugins to listen to any mutation to that object, and update the state of the components.
store.subscribe(mutation => {
if (mutation.type === 'set_character') {
// do whatever you want here
}
})
All of the above is just an outline based on what you mentioned, but this is just a starter, you may or may not want to add character into the store's state but simply the properties such as magic or hp.
How do I receive a function with params in directive like vue v-on?
<div v-example="test(id)"></div>
and this script
methods: {
test(id) {
console.log(id);
}
}
the test function will auto exec, but v-on will not?
You may also do the following:
<div v-example="test(id)"></div>
script:
directives: {
'example': {
bind(el, binding, vnode) {
el.addEventListener(binding.arg, (e) => {
if(binding.value instanceof Function) {
binding.value();
}
})
}
}
},
methods: {
test(id) {
let local_id = id;
return () => {
console.log(local_id);
}
}
}
Don't know how to mimic the built-in v-on style, there's too many problems includes auto run.
Below is the most concise and simple solution I can think of.
html
<div v-example="function(){ test(id) }"></div>
or more concise
<div v-example="()=>test(id)"></div>
js
directives:{
example: {
bind(el, binding){
binding.value()
}
}
},
methods: {
test(id) {
console.log(id);
}
}
It depends on what you are trying to achieve.
If your goal is to pass the directive a function pointer (so you can execute the function later on inside your directive), you should listen to what gonnavis said and use an arrow function.
What you tried to do will not work because when Vue will mount the component, it will bind the directive to the element (<div>) and create a binding object. This object contains a "value" function which is assigned to return the argument you passed to the directive. So, while the directive is being bound, it calculates the value you passed to it, and if it is a function invocation, it invokes the function immediately.
You can imagine that when the element with your directive is bound, the following line will run:
const binding = {
value: YOUR_ARG
}
And, in your case, it will be translated to:
const binding = {
value: test(id)
}
Which will calculate test(id) immediately.
It is all about functional programming.
You can simply pass a function and the parameters separately to your directive like demonstrated on this fiddle. Since passing the function like test('something') will cause it to be evaluated immediately.
Just replace the inserted hook with the one you need: https://v2.vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments
<div v-demo="{myFunction: test, param: 'something'}"></div>
The Code:
my-view-model.html
<input on-scan.bind="onAirbillScanned" value.bind="airbill"/>
on-scan.ts
attached() {
var scannerOptions = { onComplete: this.onScan.bind(this), preventDefault: true };
(<any>$(this.element)).scannerDetection(scannerOptions);
-- Code to add a signal to the value binding.
}
onScan(scannedValue, data) {
if (typeof (this.value) == 'function') {
let updatedScanValue = this.value(scannedValue);
if (updatedScanValue !== undefined)
this.element.value = updatedScanValue;
else
this.element.value = scannedValue;
-- Code to Call the Signal
}
}
The Problem:
I have a custom attribute that allows me to detect a scan, modify the scanned-in data and set it to be the value of the input element.
However, I need to update aurelia with the updated value.
I can just fire off an 'input' event to make this happen. But I have found side effects when random 'input' events are fired.
I would rather use the signal system outlined here: http://aurelia.io/docs.html#/aurelia/binding/1.0.0-beta.1.1.3/doc/article/binding-binding-behaviors
But the problem is that I need the signal to be on the value.bind binding.
The Question:
Is there a way (using my access to the element that the binding is on) to update the value.binding to have a signal that I can call to get the binding to update?
Basically I am looking for something like this:
addSignal(element, property, signal) {...}
..
addSignal(this.element, 'value', 'scanFinished');
And it will update the input's value binding to look like this:
<input on-scan.bind="onAirbillScanned" value.bind="airbill & signal: 'scanFinished'"/>
But more than just re-writing the html, it would have to update Aurelia to know about the signal.
Or is there a signal value that Aurelia adds to all bindings for scenarios like this?
NOTE: It would be awesome if all aurelia bindings had a AureliaBinding signal defined so you could target that control and send an event that will have no side effects other than to update the binding.
I think you're having trouble because you're at the tipping point where it's time to switch from a custom attribute to an approach that uses a custom element.
You can circumvent the whole issue by doing something like this:
scanner.html
<template>
<input ref="input" value.bind="value">
</template>
scanner.js
import {bindingMode} from 'aurelia-framework';
export class Scanner {
#bindable({ defaultBindingMode: bindingMode.twoWay }) value;
#bindable scanModifier = x => x;
input: HTMLInputElement;
attached() {
let scannerOptions = {
onComplete: value => this.value = this.scanModifier(value),
preventDefault: true
};
(<any>$(this.input)).scannerDetection(scannerOptions);
}
detached() {
(<any>$(this.input)).scannerDetection('destroy');
}
}
usage:
<require from="./scanner"></require>
<scanner value.bind="airbill" scan-modifier.call="onAirbillScanned($event)"></scanner>
This could still be done with a custom attribute but it seems more natural to me this way. What do you think?