Multiple instances of a vue.js component and a hidden input file - vue.js

I am experiencing a weird behavior using Vue.js 2.
I have a component that I reference twice in a single html page.
This component contains an input file control called attachment_file. I hide it using the Bootstrap class hidden and I open the file selection using another button. When a file is selected, I put in a variable called attachment_filename a certain string just like so:
<template>
<div>
<button #click="selectAttachement"><span class='glyphicon glyphicon-upload'></span></button>
<input id="attachment_file" type="file" class="hidden" #change="attachmentSelected">
{{attachment_filename}}
</div>
</template>
<script>
export default {
data () {
return: {
attachment_filename: null,
}
},
methods: {
selectAttachement () {
$('#attachment_file').click();
},
attachmentSelected () {
this.attachment_filename = 'some file here';
},
}
}
</script>
Problem With the class hidden and when a file is selected from the 2nd instance of the component, the value of this.attachment_filename is updated but in the data of 1st instance of the component!
If I remove the class hidden, it updates the value in the correct instance.
Possible solution use css opacity or width instead of the class hidden.
But is there a reason for this behavior?

Not sure why it is not working specifically with .hidden, but you have an inherent problem in the code that i think is the cause of the problem.
You are selecting the input using jquery with an id, that creates a couple of problems:
When you use the component twice or more, all these inputs generated by these components will have the same id, which is not what you want since id should be unique
Even if you change it to a class instead of id, it won't work properly since you are selecting the element using jquery, and that will select all the elements with this class, while you want to select just the input in that component.
The solution is to use refs:
<input id="attachment_file" type="file" class="hidden" #change="attachmentSelected" ref="fileInput">
selectAttachement () {
$(this.$refs.fileInput).click();
},

Related

Vue JS - Trigger Method if input lost focus

Quick question about Vue JS.
On my website, I have got a shopping cart and the user can enter any quantity. I am saving this quantity to the database table when he enters it...
issue is, the input field keeps firing save method every single digit of user types. For example, if the user types 123, the save method called 3 times. 1 and 12 and 123
I just want to call this save method when this input loses the focus, so I can save only once, not every single digit.
Component
Vue.component('product-counter', {
props: ['quantity'],
data: function () {
return {
count: this.quantity
}
},
template: `
<div class="input-group ">
<input v-bind:value="quantity" v-on:input.focus="$emit('input', $event.target.value)" type="text" class="form-control col-2">
</div>
`
})
Component Call
<product-counter
v-bind:productcode="item.productCode"
v-bind:quantity="item.quan"
v-on:input="item.quan=updateItemQuantity($event,item.productCode)"
v-on:increment-quantity="item.quan=updateItemQuantity(item.quan,item.productCode)"
v-on:decrement-quantity="item.quan=updateItemQuantity(item.quan,item.productCode)"></product-counter>
Vue: Method
"updateItemQuantity": function (totalquantity, pcode) {
if (totalquantity != '') {
... Update Database...
}
}
You're listening to the input event, which is triggered every time the value of the input changes, so every time a character is typed or removed.
Instead, you should listen to the blur event, which only fires when an input loses focus.
You can pass this along through your component, the same way you pass through the input event.
TLDR: Couple UpdateItemQuantity to v-on:blur instead of v-on:input, and make sure to $emit the blur event from your products-counter component.
Tip: Separate the client-side state (item.quan) and your server-side 'state' (your database) into two different methods. You want the value to reflect what the user is typing in real-time (input), which conflicts with what you want for updating the database (not real-time, only on blur). Otherwise you may get cases where users can't see what they type, as item.quan is never updated.
I think you just need to use #change instead of #input
It could be that you should use blur event.
Vue:
new Vue({
el: "#app",
data: {
quantity: ''
},
methods: {
printConsole: function () {
console.log('blured');
}
}
})
html:
<div id='app'>
<input v-bind:value="quantity" v-on:blur="printConsole" type="text" class="form-control col-2">
</div>
see jsfiddle for reference: https://jsfiddle.net/erezka/h8g62xfr/11/
blur will emit your change only after you focus out of the input

v-model not always updating in Vue

Short question
The v-model which binds a string to an input field won't update in some cases.
Example
I am using Vue within a Laravel application. This is the main component which contains two other components:
<template>
<div>
<select-component
:items="items"
#selectedItem="updateSelectedItems"
/>
<basket-component
:selectedItems="selectedItems"
#clickedConfirm="confirm"
#clickedStopAll="stopAll"
/>
<form ref="chosenItemsForm" method="post">
<!-- Slot for CSRF token-->
<slot name="csrf-token"></slot>
<input type="text" name="chosenItems" v-model="selectedItemsPipedList" />
</form>
</div>
</template>
<script>
export default {
props: ["items"],
data: function() {
return {
selectedItems: [],
selectedItemsPipedList: ""
};
},
methods: {
updateSelectedItems: function(data) {
this.selectedItems = data;
this.selectedItemsPipedList = this.selectedItems
.map(item => item.id)
.join("|");
},
confirm() {
this.$refs.chosenItemsForm.submit();
},
stopAll() {
this.updateSelectedItems([]);
this.confirm();
}
}
};
</script>
The method updateSelectedItems is called from the select-component and it works fine. In the end, the selectedItemsPipedList contains the selected items from the select-component, which looks like "1|2|3" and this value is bound to the input field in the chosenItemsForm. When the method confirm is called from the basket-component, this form is posted to the Laravel backend and the post request contains the chosen items as piped list. So far, so good.
The method stopAll is called from the basket-component and it will remove all the selected items from the array. Therefore it will call the method updateSelectedItems with an empty array, which will clear the selectedItems array and then clear the selectedItemsPipedList. After that, confirm is called which will post the form again. But, the post value still contains the selected items (e.g. '1|2|3'), instead of "". It looks like the v-model in my form is not updated, which is strange because it does work when selecting items. Why is it working when adding items, and doesn't when removing all items?
I believe you have a timing issue here. The value of the properties haven't been propagated to the DOM yet, so the form submission is incorrect. Try this instead:
stopAll() {
this.updateSelectedItems([]);
//NextTick waits until after the next round of UI updates to execute the callback.
this.$nextTick(function() {this.confirm()});
}

vue.js : click a button, look through dom, find all classes with a specific class name and append a new class to that existing class list

So, i'm using Axios to pull an html file, and then take everything in that html file past the <body> tag and appending it to {{htmlData}} inside of a div in my template
looks like this:
<template>
<somebutton> I click on</somebutton>
<div id="some-container-name" v-html="htmlData">
{{ htmlData }}
</div>
</template>
data: function() {
return {
htmlData:''
};
},
methods: {
pullView: function(html) {
this.axios.get('http://someUrl.html').then(response => {
let corsHTML = response.data;
let htmlDoc = (new DOMParser()).parseFromString(corsHTML, "text/html");
this.htmlData = htmlDoc.documentElement.getElementsByTagName('body')[0].innerHTML;
})
},
The user has an option to click on a button - the code then searches through the dom and then appends a classname to every existing you-can-edit-me class name from the html document that is pulled in via axios.
Does this make sense?
Because of how I'm pulling this content in, I don't really have the chance to bind anything to this content using Vue's :bind directive. My Google-fu has failed me and need some suggestions. Is it possible to edit this.htmlData and make the transformation in that object and then i guess re-render the data??
I don't want to pollute my work with jQuery and wondering if anyone else has done something like this?
Since you already have a parsed htmlDoc:
for (const element of htmlDoc.querySelectorAll('.you-can-edit-me')) {
element.classList.add('additional-css-class');
}
inside your pullView method, before assigning it to this.htmlData.

Why my code is receiving an empty or blank value form my input?

Currently I am learning Ionic using Vue (with previous knowledge od Vue), I am doing some basic activities and was trying to pass the value from an input to the javascript side and show the input text or value inside an alert or in the console, but when I call the method that does it, I only get an empty or blank string as it the input field was empty.
I am using Ionic 4 and Vue.js, in the past I used Vue alone and had no problems with this kind of things.
<template>
<div>
<ion-item>
<ion-label position="floating">Floating Label</ion-label>
<ion-input v-model="input" #keyup.enter="show"></ion-input>
</ion-item>
</div>
</template>
<script>
export default {
data() {
return {
input: ""
}
},
methods: {
show() {
alert(this.input)
}
}
}
</script>
I am expecting the output to be the same value as the input, but in the console I get a blank or empty string.
Put the alert in a $nextTick block to make sure the model updates. The keyup event is fired before the update happens.
In vue you can also add a watch. Much better than using key bindings.
watch:{
input(value){
alert(value);
}
}
And if you only want the value on enter or blur, use the lazy modifier on v-model.

Is it posible to delete `div` from template?

I have a component myHello:
<temlate>
<div>
<h2>Hello</h1>
<p>world</p>
</div>
</template>
And main component:
<h1>my hello:</h1>
<my-hello><my-hello>
After rendering shows this:
<h1>my hello:</h1>
<div>
<h2>Hello</h1>
<p>world</p>
</div>
How to delete <div> ?
With VueJS, every component must have only one root element. The upgrade guide talks about this. If it makes you feel better, you are not alone. For what it's worth the components section is a good read.
With the myriad of solutions to your problem, here is one.
component myHello:
<temlate>
<h2>Hello</h1>
</template>
component myWorld:
<temlate>
<p>world</p>
</template>
component main
<h1>my hello:</h1>
<my-hello><my-hello>
<my-world><my-world>
Vue gives you the tools to do so by creating templates or you can do it by having a parent div with two parent divs as children. Reset the data from the data function. Stick with convention (create templates). It's hard to get used to use Vue when you have a jQuery background. Vue is better
Ex.
data () {
message: 'My message'
}
When you click a button to display a new message. Clear the message or just set the message variable with a new value.
ex. this.message = 'New Message'
If you like to show another div. you should used the if - else statement and add a reactive variable. Ex. showDivOne
data () {
message: 'My message'
showDivOne: true,
showDivTwo: false
}
Add this reactive variables to the parent divs that corresponds to the div.
When clicking the button, you should have a function like...
methods: {
buttonClick () {
this.showDivOne = false
this.showDivTwo = true
}
}
I think you can use v-if directive to controll. Add a flag to controll status to show or hide