Binding to array fields in vue.js - vue.js

I want to make multiple 'in-place' editable date fields in table rows.
An example for a single field below works.
I show the currentdate (oldDate) as a label. User clicks on 'Change', an input field appears, after editing the user can Accept or Cancel.
https://jsfiddle.net/asrajan55/qv6crg84/
<div id="root">
<label>Test Date:</label>
<span v-show="!makeEditable"> {{ oldDate }} </span>
<span v-show = "makeEditable">
<input type="date" v-model="newDate" required=""/>
<button #click="acceptClicked">Accept</button>
<button name="cancel" #click="makeEditable=false">Cancel</button>
</span>
<button v-show="!makeEditable" #click="makeEditable=true" >Change</button>
</div>
new Vue({
el: "#root",
data: {
oldDate: '2019-02-04',
newDate: '2019-02-04',
makeEditable: false,
},
methods: {
acceptClicked(){
if (this.newDate!='') {
this.oldDate=this.newDate;
this.makeEditable=false;
}
}
}
});
However if I try multiple(2) fields the click event fires (sometimes) but nothing seems to happen. No errors in console. Also the Vue debugger in the browser does not immediately update the changed fields. Please help. I am desperate and pulling my hair out!
https://jsfiddle.net/asrajan55/9uhkr4w0/3/
<div id="root">
<div v-for="(item,index) in oldDates">
<label for="">Test Date:</label>
<span v-show="!editables[index]">{{item}}</span>
<input v-show="editables[index]" type="date" v-model="oldDates[index]"/>
<button v-show="editables[index]">Accept</button>
<button v-show="editables[index]" #click="editables[index]=false">Cancel</button>
<button v-show="!editables[index]" #click="makeEditable(index)">Change</button>
<hr />
</div>
</div>
new Vue({
el: "#root",
data: {
oldDates: ['2019-01-04', '2019-02-04'],
newDates: ['2019-01-04', '2019-02-04'],
editables: [false, false]
},
methods: {
makeEditable(index) {
alert(index);
this.editables[index] = true;
}
}
});

The problem was that you were mutating the array in place,
creating a new array reference and passing it would solve the issue
fixed here : https://jsfiddle.net/e3L2zcna/
makeEditable(index) {
this.editables = this.editables.map((val,i) => i===index || val);
}

Try using this.$set(this.editables, index, true);
Vue can't detect changes to arrays if you directly access an element using []. Read about it here:
https://vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating

Related

Vue-JS v-show Percitence in radio

v-show appears not be percitent when whit radio (v-model)
Please find example: https://jsfiddle.net/Lngocxrj/1/
<div id="helloWorldApp">
<input type="radio" v-model="visible" value="true" name="optradio">hide
<input type="radio" v-model="visible" value="false" name="optradio">show
<div v-show="visible">
Hello World
</div>
<p>
{{visible}}
</p>
</div>
new Vue({
el: "#helloWorldApp",
data: {
visible: true
},
methods: {
show: function() {
this.visible = !this.visible;
}
}
});
It works, if you use a method to toggle the data.
HTML:
<div id="helloWorldApp">
<label>hide<input type="radio" value="false" #click="inputClick(false)" name="optradio" /></label>
<label>show<input type="radio" value="true" #click="inputClick(true)" name="optradio" /></label>
<div v-show="visible">
Hello World
</div>
<p>
{{visible}}
</p>
</div>
JavaScript:
new Vue({
el: "#helloWorldApp",
data: {
visible: false
},
methods: {
inputClick(val) {
this.visible = val;
}
}
});
Added a new property to differentiate input changes and show/hide div
<div id="helloWorldApp">
<input type="radio" v-model="visible" value="true" name="optradio">hide
<input type="radio" v-model="visible" value="false" name="optradio">show
<div v-if="showDiv">
Hello Worlds
</div>
<p>
{{visible}}
</p>
</div>
new Vue({
el: "#helloWorldApp",
data: {
visible: false,
showDiv: true
},
watch: {
visible(val) {
this.showDiv = val;
}
}
});
As per my comment: the reason why your element is showing regardless of the v-show directive is because the values from the checkboxes are being stored as strings and not booleans. And since "false" is actually truthy because it is a string of non-zero length, your div will always be visible.
Quick solution: Perform string comparison
If you want to keep your code as-is, and understanding that you are looking at string values instead of boolean stored in visible, updating your template to use v-show="visible === 'true'" will work.
Note: I do not encourage this method though, because this is a code smell (see further below for a better solution).
new Vue({
el: "#helloWorldApp",
data: {
visible: 'true'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="helloWorldApp">
<input type="radio" v-model="visible" value="true" name="optradio">hide
<input type="radio" v-model="visible" value="false" name="optradio">show
<div v-show="visible === 'true'">
Hello World
</div>
<p>
{{visible}}
</p>
</div>
A better solution: use checkbox for binary state toggling
This brings us to another issue: since you are toggling a property, a radio button is not the best UI to do that. A checkbox is more appropriate: in this case, you don't need to do dirty strict comparisons:
new Vue({
el: "#helloWorldApp",
data: {
visible: true
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="helloWorldApp">
<input type="checkbox" v-model="visible" checked>visible
<div v-show="visible">
Hello World
</div>
<p>
{{visible}}
</p>
</div>

Vue.js adding a toggle and method to a button

I have a button that should toggle and also call a method. How do I achieve this? Seems like it can be only one or the other.
new Vue({
el: "#app",
data: {
iExist:false,
iDoNotExist: true,
},
methods: {
iSignedUpforThis: function(){
console.log("step X");
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p v-show="iExist"> i EXISTS </p>
<p v-show="iDoNotExist">
<strong> You are not found: </strong>
<form >
First name:<br>
<input type="text" name="firstname" value="Mickey">
<br>
Last name:<br>
<input type="text" name="lastname" value="Mouse">
<br><br>
</form>
<BUTTON v-on:click="iExists = iDoNotExist">
TOGGLE MY EXISTENCE
</BUTTON>
</div>
Move
iExists = iDoNotExist to a method:
methods: {
iSignedUpforThis: function(){
this.iExist = this.iDoNotExist
console.log("step X");
}
}
<button v-on:click="iSignedUpForThis">
TOGGLE MY EXISTENCE
</button>
First off to accomplish your desired result you need only one Boolean variable. Then in your method just switch between true and false. Also you have an invalid markup - there is closing tap p but no closing. That's why your example does not work.
Notice: it's bad idea to nest form tag inside p tag, so use div instead. It's considered a good practice to associate your input with it's label using label tag. Also there is shortcut for v-on:click - #click. data should be an function that returns an object, this will prevent . multiple instance to share the same object.
If you follow above recommendations you will make your code much clear and bug-less.
new Vue({
el: '#app',
data: {
isExist: false,
},
methods: {
method() {
this.isExist = !this.isExist
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-show="isExist">I exist</div>
<div v-show="!isExist">
<strong>You are not found:</strong>
<form>
<label>First name:<br>
<input type="text" name="firstname" value="Mickey">
</label>
<br>
<label>Last name:<br>
<input type="text" name="lastname" value="Mouse">
</label>
</form>
</div>
<button #click="method">Toggle</button>
</div>
It might be late but I am sure it will help others. Create a component ToggleButton.js and paste the below codes.
<template>
<label for="toggle_button">
<span v-if="isActive" class="toggle__label">On</span>
<span v-if="! isActive" class="toggle__label">Off</span>
<input type="checkbox" id="toggle_button" v-model="checkedValue">
<span class="toggle__switch"></span>
</label>
</template>
<script>
export default {
data() {
return {
currentState: false
}
},
computed: {
isActive() {
return this.currentState;
},
checkedValue: {
get() {
return this.defaultState
},
set(newValue) {
this.currentState = newValue;
}
}
}
}
</script>
Take a look at the article to learn more https://webomnizz.com/create-toggle-switch-button-with-vue-js/

Binding vue components to class name

Alright so I am trying to bind this vue components to a class name so it triggers on every element that has this class but what happens is that it only works with the first element and not with other ones
<div class="__comment_post">
<textarea></textarea>
<input type="submit" v-on:click="submitComment" /> <!-- submit comment being only triggered on this one -->
</div>
<div class="__comment_post">
<textarea></textarea>
<input type="submit" v-on:click="submitComment" />
</div>
<div class="__comment_post">
<textarea></textarea>
<input type="submit" v-on:click="submitComment" />
</div>
As you can see above, I've got 3 divs with class __comment_post so naturally submitComment should be bound to all these 3 divs but what happens is that submitComment is being triggered only on the first one
var app = new Vue({
el:".__comment_post",
data: {
comment: ""
},
methods: {
submitComment: function() {
console.log("Test");
}
}
});
Here is a little example you and others can follow in order to bind vue instance to class names.
Lets say, you would like to bind Vue to multiple existing <div class="comment"> element in HTML.
HTML:
<div class="comment" data-id="1">
<div>
<div class="comment" data-id="2">
<div>
Now, you can try the following logic/code to your example.
JS:
var comments = {
"1": {"content": "Comment 1"},
"2": {"content": "Comment 2"}
}
$('.comment').each(function () {
var $el = $(this)
var id = $el.attr('data-id')
var data = comments[id]
new Vue({
el: this,
data: data,
template: '<div class="comment">{{ content }}<div>'
})
})
I hope this will answer your question :)
The vue instance is mounted on the first found DOM element with the css selector passed to the el option. So the rest two div have no vue instances mounted on them.
So wrap your divs with a wrapper div and mount the vue instance on that wrapper
<div id="app">
<div class="__comment_post">
<textarea></textarea>
<input type="submit" v-on:click="submitComment" /> <!-- submit comment being only triggered on this one -->
</div>
<div class="__comment_post">
<textarea></textarea>
<input type="submit" v-on:click="submitComment" />
</div>
<div class="__comment_post">
<textarea></textarea>
<input type="submit" v-on:click="submitComment" />
</div>
script
var app = new Vue({
el:"#app",
data: {
comment: ""
},
methods: {
submitComment: function() {
console.log("Test");
}
}
});

Vuejs - Proper way to clone an element, and append to DOM

I have a HTML input field to enter some information:
<div id="fruitForm">
<div class="inputArea">
<label for="fruit0">Enter Fruit Name</label>
<input id="fruit0"></input>
</div>
</div>
<button #click="newInputField">Add More Fruit Input Fields</button>
<button #click="submit">Submit Fruit</button>
And then I handle that click event:
export default {
data() {
return {
}
},
methods: {
newInputField() {
//Create another input area for users to input fruits
},
submit() {
//Do something here
}
}
}
When a user selects the Add More Fruit Input Fields button, it should create a new input area so that the HTML looks like this:
<div id="fruitForm">
<div class="inputArea">
<label for="fruit0">Enter Fruit Name</label>
<input id="fruit0"></input>
</div>
<div class="inputArea">
<label for="fruit1">Enter Fruit Name</label>
<input id="fruit1"></input>
</div>
</div>
<button #click="newInputField">Add More Fruit Input Fields</button>
<button #click="submit">Submit Fruit</button>
Now, I've been using traditional DOM manipulation methods via vanilla Javascript to accomplish this... stuff like this:
const inputArea = document.getElementsByClassName('inputArea');
And then I change the id's of the input field, and then I use appendChild to add the new input field to the DOM.
So my question is: how should I be cloning this element with vuejs? I feel I'm not approaching this in the vuejs way. Should I be approaching this like a list and using v-for? Or something else?
Avoid direct DOM manipulations with Vue. You can use data property as a model for your template. The answer would be yes, you can and probably should use v-for for what you call cloning:
var demo = new Vue({
el: '#demo',
data: {
counter: 0,
inputs: [{
id: 'fruit0',
label: 'Enter Fruit Name',
value: '',
}],
},
methods: {
addInput() {
this.inputs.push({
id: `fruit${++this.counter}`,
label: 'Enter Fruit Name',
value: '',
});
}
}
});
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="demo">
<div class="inputArea" v-for="input in inputs" :key="input.id">
<label :for="input.id">{{input.label}}</label>
<input :id="input.id" v-model="input.value"></input>
</div>
<button #click="addInput">Add input</button>
</div>

Vue: Binding radio to boolean

I'm having trouble binding radiobuttons to boolean values in model.
In this example: https://jsfiddle.net/krillko/npv1snzv/2/
On load, the radio radio button is not checked, and when I try to change them, the 'primary' value in model is becomes empty.
I've tried:
:checked="variation.primary == true"
but with no effect.
To bind radio buttons to boolean values instead of string values in Vue, use v-bind on the value attribute:
<input type="radio" v-model="my-model" v-bind:value="true">
<input type="radio" v-model="my-model" v-bind:value="false">
I'll leave it to you to figure out how to match these values with your backend data.
Checkboxes are not so good for this scenario; the user could leave them both blank, and you don't get your answer. If you are asking a yes/no or true/false question where you want only one answer, then you should be using radio buttons instead of checkboxes.
What you are looking for is a checkbox. Here is an updated jsfiddle.
Your use case is not how radio buttons are supposed to work.
Look at this example.
new Vue({
el: '#app',
data: {
picked: 'One',
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.js"></script>
<div id="app">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br><br>
<span>Picked: {{ picked }}</span>
</div>
I ran into this myself too, the thing to remember is that the value attribute actually shouldn't change for the radio button, what changes (and what you need to bind to) is the checked attribute.
And then you need to handle the change event to set the correct item's value in your data.
Based on your jsFiddle, I think this is what you're looking for:
<div id="l-main">
<div v-for="(variation, key) in variations">
<label>
{{ variation.name }}
<input
type="radio"
name="Test"
:value="key"
:checked="variation.primary"
#change="onChange"
/>
</label>
</div>
<br>Output:<br>
<div v-for="(variation, key) in variations">
{{ variation.name }} {{ variation.primary }}
</div>
</div>
var vm = new Vue({
el: '#l-main',
data: {
variations: {
'41783' : {
'name': 'test1',
'primary': false
},
'41785' : {
'name': 'test2',
'primary': true
}
}
},
methods: {
onChange($event) {
// the primary variation is the one whose key
// matches the value of the radio button that got checked
for (const key in this.variations) {
this.variations[key].primary = key === $event.target.value;
}
}
}
});