How to Call Function on Each dom-repeat Element - polymer-2.x

I am trying to call a function on each element inside of a dom-repeat template.
<dom-repeat items="[[cart]]" as="entry">
<template>
<shop-cart-item id="item"></shop-cart-item>
</template>
</dom-repeat>
...
checkStatus() {
this.$.item.doSomething();
}
How can I call doSomething on each element?

You can iterate through nodes like:
checkStatus() {
const forEach = f => x => Array.prototype.forEach.call(x, f);
forEach((item) => {
if(item.id == 'cartItem') {
console.log(item);
item.doSomething(); // call function on item
}
})(this.$.cartItems.childNodes)
}

You can add an on-tap event in the loop. In order to observe which item you clicked look into model property :
<dom-repeat items="[[cart]]" as="entry">
<template>
<!-- need to give dynamic id for each item in dome-repeat -->
<shop-cart-item id="[[index]]" on-tap = 'checkStatus'></shop-cart-item>
</template>
</dom-repeat>
...
checkStatus(status) {
console.log(status.model) // you can get index number or entry's properties.
this.$.item.doSomething();
}
EDIT:
So as per comment of #Matthew, if need to call a function in one of element's function in dom-repeat first give a dynamic id name as above then:
checkStatus(status) {
this.shadowRoot.querySelector('#'+ status.model.index).doSomething();
}

Related

Vue dynamic form elements in for loop

In a vue component I want to generate dynamic input elements in a for loop and bind them to the data.form element.
data() {
return {
form: {}
}
}
<b-form-checkbox v-model="form[key]['amount']" type="checkbox" :value="key"></b-form-checkbox>
How can I deal with this, what I tried:
form.amount[key] // is working, but not really the best data structure, and the array is populated with null values for each for loop entry on init ...
form[key]['amount'] // not working
// the working example I mentioned is structured this way
form: {
amount: [],
}
Maybe someone can help me with this ...
Simply define an empty array for the form fields, e.g. return { formFields: [] }. Then add as many values in this array as input fields you want to render on the screen:
<template>
<form>
<input v-for="(field,index) in formFields" :key="index" v-model="fields[index]" type="text">
</form>
</template>
<script>
export default
{
data()
{
return {
formFields: []
};
},
mounted()
{
for (let i = 1; i <= 10; i++) this.formFields.push('');
}
}
</script>
If you need different types of input fields - you will need to use array of objests instead of strings, and encode the field type as a property inside each of these objects.

Changes made to a computed array won't show after a prop binding

I'm quite new to vue and right now I'm trying to figure out how to make changes to a computed array and make an element react to this change. When I click the div element (code section 4), I want the div's background color to change. Below is my failed code.
Code section 1: This is my computed array.
computed: {
arrayMake() {
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
Code section 2: This is where I send it as a prop to another component.
<test-block v-for="(obj, index) in arrayMake" v-bind:obj="obj" v-on:act="act(obj)"></card-block>
Code section 3: This is a method in the same component as code section 1 and 2.
methods: {
act(obj){
obj.check = true;
}
Code section 4: Another component that uses the three sections above.
props: ["obj"],
template: /*html*/`
<div v-on:click="$emit('act')">
<div v-bind:style="{ backgroundColor: obj.check? 'red': 'blue' }">
</div>
</div>
Easiest way to achieve this, store the object into another data prop in the child component.
child component
data() => {
newObjectContainer: null
},
onMounted(){
this.newObjectContainer = this.obj
},
methods: {
act(){
// you don't need to take any param. because you are not using it.
newObjectContainer.check = !newObjectContainer.check
}
}
watch: {
obj(val){
// updated if there is any changes
newObjectContainer = val
}
}
And if you really want to update the parent component's computed data. then don't use the computed, use the reactive data prop.
child component:
this time you don't need watcher in the child. you directly emit the object from the method
methods: {
act(){
newObjectContainer.check = !newObjectContainer.check
this.emits("update:modelValue", nextObjectContainer)
}
}
parent component:
data() => {
yourDynamicData: [],
},
onMounted(){
this.yourDynamicData = setAnArray()
},
methods(){
setAnArray(){
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
}
}
okay above you created a reactive data property. Now you need the update it if there is a change in the child component,
in the parent first you need object prop so, you can update that.
<test-block v-for="(obj, index) in arrayMake" v-model="updatedObject" :obj="obj"></card-block>
data() => {
yourDynamicData: [],
updatedObject: {}
},
watch:{
updatedObject(val){
const idx = val.index
yourDynamicData[idx] = val
}
}

Getting the value of button (vuejs)

i want to get the value of the button when click i already add a method. but whenever i click theres nothing. the value not passing. can anyone help me? Thank you.
Here is the code:
<button
class="button buttonvalue"
v-for="(p,index) in buttonvalue"
:key="index"
#click="webcamSendRequestButton($event)"
>{{p}}</button>
Method:
methods: {
webcamSendRequestButton: function(e) {
// const buttonValue = e.target.value;
// console.log(e.target.value)
alert(e.target.value);
}
}
DATA (Json) of button value
array data: small,medium,large
this.buttonvalue= this.item[0].buttonvalue.split(',');
To get value button value you can either bind a :value attribute first like:
<button
class="button buttonvalue"
v-for="(p,index) in buttonvalue"
:key="index"
#click="webcamSendRequestButton($event)"
:value="p"
>{{p}}</button>
Then e.target.value will work fine.
Or, if you don't want to add another attribute, then you can simply use:
methods: {
webcamSendRequestButton(e) {
console.log(e.target.textContent)
}
}
instead of using e.target.value.
Or, if the value p is required, then you can simply pass that to click handler like:
#click="webcamSendRequestButton(p)"
and then access it like:
methods: {
webcamSendRequestButton(buttonvalue) {
console.log(buttonvalue)
}
}
If you simply need to pass a value of p, for example then you would write:
#click="webcamSendRequestButton(p)"
then in your method:
methods: {
webcamSendRequestButton(val) {
alert(val);
}
}
I am assuming that buttonvalue is some kind of array that you are looping through.
Hope it helps.

Vuejs Updating v-for by reference

So I have a simple v-for and each item in the v-for has a #click on it
<result-index>
<li v-for="(result, index) in results" #click="getReult(results[index])">
{{ result.name }}
</li>
</result-index>
Now, my method for getResult just assigns that result to a result data:
methods: {
getResult: function(result) {
// when the child <result-update> updates this, it updates fine, but it doesn't update the v-for reference of this.
this.result = result;
}
}
Now I have another component that get the data for that result and display it:
<result-index>
<li v-for="(result, index) in results" #click="getReult(results[index])">
{{ result.name }}
</li>
<result-update v-if="result" v-model="result">
//... here is a form to access the result and update it
</result-update>
</result-index>
In my result-update I am updating via the index and value like so:
methods: {
update(e) {
this.$emit("input", //data here...);
},
}
watch: {
value: function() {
this.form = this.value;
},
},
created() {
this.form = __.cloneDeep(this.value);
}
Which update the parent result fine (the one we used the #click on), but not the v-for reference of that result, so how can I update the v-for reference of the result when it changes in , also please note, it is not possible for me to put the inside the v-for due to the css design of this, it needs to be seperate from the ...
When this.result = result, this.result points to one address of the memory.
When <result-update v-if="result" v-model="result"> receives input event then assign new value to this.result, it will make this.result = newValue (actually point to another address of the memory for newValue), so it will not change the value for result[index] as you expected.
Check below demo:
const test = {result: []}
let value1 = ['a']
console.log('org', test)
test.result = value1 // assign with new array
console.log('Replace the whole array', test)
value1[0] = 'b' // assign new value to first element of value1
console.log('Update one item of the array', test) //test.result and value1 point to same address of the memory
The solution:
You can save the index for current <result-index>, then change the value by this.results[index].
So adjust your codes to below then should work fine.
For the template of component <result-index>, change it to:
<result-index>
<li v-for="(result, index) in results" #click="getReult(index)">
{{ result.name }}
</li>
</result-index>
For the method=getResult inside component <result-index>, change it to:
methods: {
getResult: function(index) {
this.selected = index;
}
}
Inside the parent component, change the template to:
<result-update v-if="selected >= 0" v-model="results[selected]">
//... here is a form to access the result and update it
</result-update>

Aurelia `click` attribute that requires event target to be same as element

I'm aware of click.trigger as well as click.delegate which work fine. But what if I want to assign a click event that should only trigger when the exact element that has the attribute gets clicked?
I'd probably do something like this were it "normal" JS:
el.addEventListener('click', function (e) {
if (e.target === el) {
// continue...
}
else {
// el wasn't clicked directly
}
});
Is there already such an attribute, or do I need to create one myself? And if so, I'd like it to be similar to the others, something like click.target="someMethod()". How can I accomplish this?
Edit: I've tried this which doesn't work because the callback function's this points to the custom attribute class - not the element using the attribute's class;
import { inject } from 'aurelia-framework';
#inject(Element)
export class ClickTargetCustomAttribute {
constructor (element) {
this.element = element;
this.handleClick = e => {
console.log('Handling click...');
if (e.target === this.element && typeof this.value === 'function') {
console.log('Target and el are same and value is function :D');
this.value(e);
}
else {
console.log('Target and el are NOT same :/');
}
};
}
attached () {
this.element.addEventListener('click', this.handleClick);
}
detached () {
this.element.removeEventListener('click', this.handleClick);
}
}
And I'm using it like this:
<div click-target.bind="toggleOpen">
....other stuff...
</div>
(Inside this template's viewModel the toggleOpen() method's this is ClickTargetCustomAttribute when invoked from the custom attribute...)
I'd also prefer if click-target.bind="functionName" could instead be click.target="functionName()" just like the native ones are.
Just use smth like click.delegate="toggleOpen($event)".
$event is triggered event, so you can handle it in toggleOpen
toggleOpen(event) {
// check event.target here
}
Also you can pass any other value available in template context to toggleOpen.