I can't set "disabled" parameter in Vue to be persistent - vue.js

I can't set "disabled" parameter to be persistent. If I set disable: true inside data function, it seems that it doesn't do anything.
You can see inside mounted() that it calls checkCanVote() and in there at first the console.log says it is set to false, then I set it to true but on stars hover (star_over()) it is again false?
http://jsfiddle.net/7unqk49k/1/
Template
<div id="star-app" v-cloak class="col-md-4">
<star-rating value="<?php echo $this->rating_rounded; ?>"></star-rating>
</div>
<template id="template-star-rating">
<div class="star-rating">
<label class="star-rating__star" v-for="rating in ratings" :class="{'is-selected': ((value >= rating) && value != null), 'is-disabled': disabled}" #mouseover="star_over(rating)" #mouseout="star_out">
<input class="star-rating star-rating__checkbox" type="radio" :name="name" :disabled="disabled" :id="id" :required="required" v-model="value" #click="set(rating)"> ★
</label>
</div>
</template>
JS
Vue.component('star-rating', {
template: '#template-star-rating',
data: function data() {
return {
value: null,
temp_value: null,
ratings: [1, 2, 3, 4, 5],
disabled: true
};
},
props: {
'name': String,
'value': null,
'id': String,
'disabled': Boolean,
'required': Boolean
},
methods: {
star_over: function star_over(index) {
console.log(this.disabled);
if (this.disabled == true) {
return;
}
this.temp_value = this.value;
this.value = index;
},
star_out: function star_out() {
if (this.disabled == true) {
return;
}
this.value = this.temp_value;
},
set: function set(value) {
if (this.disabled == true) {
return;
}
this.temp_value = value;
this.value = value;
// On click disable - this works
this.disabled = true;
},
checkCanVote: function() {
console.log('Inside checkCanVote');
console.log(this.disabled);
this.disabled = true;
console.log(this.disabled);
}
},
mounted() {
this.checkCanVote();
}
});
new Vue({
el: '#star-app'
});

The actual problem is in naming things.Try to avoid to use same naming for the props that you pass to child component and data properties that you've declared in child component.
To get this working as expected, you should just remove the prop declaration for disabled prop and It should work.
http://jsfiddle.net/9debdkh1/

Related

VueJS toggle password visibilty without mutating the "type" property

I have a basic input component I am working with that has type as a property and up until now has been working very well. However, trying to use it for passwords and implementing obfuscation has been sort of tricky.
How can I toggle hide/show of the password without mutating the prop? I figured making it type = 'password' to type = 'text was the best way, but clearly not.
I've made a Codesandbox to replicate that part of the component, but any advice or direction would be greatly appreciated!
PasswordInput.vue:
<template>
<div>
<input :type="type" />
<button #click="obfuscateToggle" class="ml-auto pl-sm _eye">
<div>
<img :src="`/${eyeSvg}.svg`" alt="" />
</div>
</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
passwordVisible: false,
eyeSvg: "eye-closed",
};
},
props: {
type: { type: String, default: "text" },
},
methods: {
obfuscateToggle() {
if (this.eyeSvg === "eye-closed") {
this.eyeSvg = "eye";
} else this.eyeSvg = "eye-closed";
// this.eyeSvg = "eye-closed" ? "" : (this.eyeSvg = "eye");
if ((this.type = "password")) {
this.type = "text";
} else this.type = "password";
},
},
};
</script>
App.vue
<template>
<div id="app">
<PasswordInput type="password" />
</div>
</template>
The only way to do it is by mutating the type attribute. As that is how the browser decides the render it as either just a textbox or as a password. Therefore you are doing this the right way.
The one issue that you will encounter is that you will have errors thrown in your console because you are attempting to mutate a prop.
This is quick and easy to fix. First, you will create a new data property, and assign it to the default of type
data(){
return{
fieldType:'text'
}
}
Then you will use the on mounted lifecycle hook, and update your data property to match your prop's value`
mounted(){
this.fieldType = this.type;
}
If you know the type prop will change from the parent component you can also use a watcher for changes and assign type
watch:{
type(val){
this.fieldType = val;
}
}
You will then update your obfuscateToggle method to use the fieldtype variable:
obfuscateToggle() {
if (this.eyeSvg === "eye-closed") {
this.eyeSvg = "eye";
} else this.eyeSvg = "eye-closed";
//You can simplify this by using this.fieldType = this.fieldType == "text" ? "password" : "text"
if (this.fieldType == "password") {
this.fieldType = "text";
} else this.fieldType = "password";
}
Finally, in your template, you will want to change type to fieldType
<template>
<div>
<input :type="fieldType" />
<button #click="obfuscateToggle" class="ml-auto pl-sm _eye">
<div>
<img :src="`/${eyeSvg}.svg`" alt="" />
</div>
</button>
</div>
</template>
Putting it all together
<script>
export default {
name: "HelloWorld",
data() {
return {
passwordVisible: false,
eyeSvg: "eye-closed",
fieldType: "text"
};
},
props: {
type: { type: String, default: "text" },
},
methods: {
obfuscateToggle() {
if (this.eyeSvg === "eye-closed") {
this.eyeSvg = "eye";
} else this.eyeSvg = "eye-closed";
//You can simplify this by using this.fieldType = this.fieldType == "text" ? "password" : "text"
if (this.fieldType == "password") {
this.fieldType = "text";
} else this.fieldType = "password";
},
},
watch:{
type(val){
this.fieldType = val;
}
},
mounted(){
this.fieldType = this.type;
},
};
</script>
Here is an example on CodeSandBox
Also, you had a small typo in your obfuscateToggle method.
if(this.type = 'password')
this was assigning type instead of comparing it against a literal :)

vue select all checkboxes with value generated by computed methods

My page has a Select All checkbox at the top where upon clicking it, it should have checked all the checkboxes. Here's my code:
<div class="columns bottom-border">
<div class="column">Student</div>
<div><a v-on:click="revokePoints()">Revoke</a><br/><input type="checkbox" v-model="selectAll">Select All</div>
</div>
<div class="columns" v-for="(behavior) in sortBehaviors(behaviorList)" :key="behavior._id">
<div class="column">{{ behavior.studentID.firstName }} </div>
<div class="column is-1"><center><input type="checkbox" :value="setCheckedValue(behavior.dataType,behavior._id,behavior.studentID._id,behavior.actionDate)" :id="setCheckedValue(behavior.dataType,behavior._id,behavior.studentID._id,behavior.actionDate)" v-model="checkedIDs"></center></div>
</div>
data() {
return {
positiveName: '',
behaviorList: [],
checkedIDs: [],
selected: []
};
},
computed:{
selectAll: {
get: function () {
return this.behaviorList ? this.selected.length == this.behaviorList.length : false;
},
set: function (value) {
var mySelected = [];
let self = this;
if (value) {
this.behaviorList.forEach(function (behavior) {
var getDataType = behavior.dataType
var getID = behavior._id
var getStudentID = behavior.studentID._id
var getActionDate = behavior.actionDate
var getGeneratedID = self.setCheckedValue(getDataType,getID,getStudentID,getActionDate);
mySelected.push(getGeneratedID);
});
}
self.selected = mySelected;
console.log("self selected")
console.log(self.selected)
}
}
},
methods: {
setCheckedValue(dataType,id,studentID,actionDate){
return "1:" + dataType + "|2:" + id + "|3:" + studentID + "|4:" + actionDate
},
revokePoints(){
var pointsToRevoke = this.checkedIDs;
console.log("pointsToRevoke")
console.log(pointsToRevoke)
}
When I click on the Select All checkbox, console will display that self.selected will have the id of all the checkboxes. But the issue is the checkbox for all the values displayed are not checked...
It is difficult to help because your code is not completed. But I would approach that a bit differently. I hope this codepen can help you.
const list = [
{ id: 1, name: 'New York', checked: true },
{ id: 2, name: 'Sydney', checked: false },
{ id: 3, name: 'London', checked: false },
{ id: 4, name: 'Chicago', checked: true }
]
new Vue({
el: '#app',
data() {
return {
list,
isAllChecked: false
};
},
methods: {
checkAll: function() {
this.list = this.list.map(city => ({ ...city,
checked: !this.isAllChecked
}))
this.isAllChecked = !this.isAllChecked
}
},
computed: {
getAllCheckedIDs: function() {
return this.list.filter(city => city.checked).map(city => city.id)
},
getNotAllCheckedIDs: function() {
return this.list.filter(city => !city.checked).map(city => city.id)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="city in list" :key="city.id">
<label>
{{city.name}}
<input type="checkbox" v-model="city.checked" />
</label>
</li>
</ul>
<button #click="checkAll">Check all</button>
<br/>
<div>Checked IDs: {{getAllCheckedIDs}}</div>
<div>Not Checked IDs: {{getNotAllCheckedIDs}}</div>
</div>

Vue: v-model undefined or default/fallback

Is there anyway to set a default value to an input to the value of another input field in case it has been left empty.
Looks like I can't specify this in the data nor in the v-model attribute:
<template>
<div>
<input type="number" v-model="font1 || 14"> <!-- gives error -->
<input type="number" v-model="font2 || font1"> <!-- gives error -->
<input type="number" v-model="font3 || font1"> <!-- gives error -->
</div>
</template>
<script>
export default {
data() {
return {
font1: 14,
font2: this.font2 || this.font1, // this approach also gives error
font3: this.font3 || this.font1 // this approach also gives error
}
}
}
</script>
Any help is welcome.
Thanks.
There is probably 37 different ways of doing it.
The question is what is important, robustness, maintainability, ease of implementation, scalability etc.
Here is a solution that prioritizes ease of implementation. It relies on using #change and :value instead of the v-model magic.
This will leave font2 and font3 as null if they are not changed.
new Vue({
el: '#app',
data: {
font1: 14,
font2: null,
font3: null,
},
methods: {
isNumber(n) { return !isNaN(parseFloat(n)) && !isNaN(n - 0) }
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="number" v-model="font1">
<input type="number" #change="font2 = $event.target.value" :value="isNumber(font2)?font2:font1">
<input type="number" #change="font3 = $event.target.value" :value="isNumber(font3)?font3:font1">
<pre>{{ {font1:font1, font2:font2, font3:font3} }}</pre>
</div>
You will have to use a combination of v-model and computed properties' getters and setters to achieve what you want.
Declare an internal data store for the fonts, let's call the f1, f2, and f3. Give f1 your default value of 14:
data: function() {
return {
f1: 14,
f2: null,
f3: null,
};
}
Now, when the component is mounted, you will want to interpolate the values accordingly. This can be done in the vm.mounted() callback:
mounted: function() {
// f2 takes the value of f1, if itself is falsy
this.f2 = this.f2 || this.f1;
// f3 takes the value of f2, if itself is falsy
this.f3 = this.f3 || this.f2;
}
The last step is to setup your computed properties, so that whenever font1, font2, or font3 changes, they will update the internal font data store appropriately:
computed: {
font1: {
set: function(val) {
if (val)
this.f1 = val;
},
get: function() {
return this.f1;
}
},
font2: {
set: function(val) {
this.f2 = val || this.f1;
},
get: function() {
return this.f2;
}
},
font3: {
set: function(val) {
this.f3 = val || this.f2;
},
get: function() {
return this.f3;
}
}
}
See proof-of-concept below:
Vue.component('test', {
template: '#test',
data: function() {
return {
f1: 14,
f2: null,
f3: null,
};
},
mounted: function() {
this.f2 = this.f2 || this.f1;
this.f3 = this.f3 || this.f2;
},
computed: {
font1: {
set: function(val) {
if (val)
this.f1 = val;
},
get: function() {
return this.f1;
}
},
font2: {
set: function(val) {
this.f2 = val || this.f1;
},
get: function() {
return this.f2;
}
},
font3: {
set: function(val) {
this.f3 = val || this.f2;
},
get: function() {
return this.f3;
}
}
}
});
new Vue({ el: '#app' });
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<test />
</div>
<script type="text/x-template" id="test">
<div>
<input type="number" v-model.number="font1">
<input type="number" v-model.number="font2">
<input type="number" v-model.number="font3">
</div>
</script>
You could use a Boolean set to false for both input 1 and input 2 elements and update it to true when a value is entered.
new Vue({
el: '#app',
data: {
font1: 14,
font2: {
value: 14,
defined: false
},
font3: {
value: 14,
defined: false
}
},
methods: {
Chg(font) {
if (font.value.length) {
font.defined = true;
return;
}
font.value = this.font1;
}
},
watch: {
font1() {
if (this.font2.defined === false) this.font2.value = this.font1
if (this.font3.defined === false) this.font3.value = this.font1
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="number" v-model="font1">
<input type="number" v-model="font2.value" #input="Chg(font2)">
<input type="number" v-model="font3.value" #input="Chg(font3)">
</div>

vue: changes not triggered #input

Below is vue script - the concern method is called notLegalToShip which checks when age < 3.
export default {
template,
props: ['child', 'l'],
created() {
this.name = this.child.name.slice();
this.date_of_birth = this.child.date_of_birth.slice();
},
data() {
return {
edit: false,
today: moment().format('DD/MM/YYYY'),
childUnder3: false
};
},
computed: {
age() {
var today = new Date();
var birthDate = new Date(this.child.date_of_birth);
var age = today.getFullYear() - birthDate.getFullYear();
var m = today.getMonth() - birthDate.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
},
methods: Object.assign(
mapActions(['updateChild']),
{
notLegalToShip() {
if(this.age < 3){
this.childUnder3 = true;
}
this.childUnder3 = false;
},
showForm() {
this.edit = true;
},
hideForm() {
this.edit = false;
},
submitForm() {
this.hideForm();
this.updateChild({
child: this.child,
name: this.name,
dateOfBirth: this.date_of_birth,
childUnder3 : this.childUnder3
});
}
}
)
}
Here's the snippet of my template. The input as below.
I want the notLegalToShip method to be triggered when I click arrow changing the year. A warning will appear when childUnder3 is "true". I've tried #change, #input on my input but my method is not triggered at all:
<div>
{{childUnder3}}
{{age}}
<div class="callout danger" v-if="childUnder3">
<h2>Sorry</h2>
<p>Child is under 3!</p>
</div>
<div v-if="!edit">
<a #click.prevent="showForm()" href="#" class="more-link edit-details edit-child">
<i class="fa fa-pencil" aria-hidden="true"></i>{{ l.child.edit_details }}
</a>
</div>
<form v-show="edit" #submit.prevent="submitForm()">
<div class="input-wrap">
<label for="account__child__date-of-birth__date">{{ l.child.date_of_birth }}</label>
<input id="account__child__date-of-birth__date" type="date" name="date_of_birth" v-on:input="notLegalToShip" v-model="date_of_birth" v-validate="'required'">
<p class="error-message" v-show="errors.has('date_of_birth')">{{ l.child.date_of_birth_invalid }}</p>
</div>
</form>
</div>
Any help checking my code above would be appreciated!
You have a couple of problems...
Initialise the name and date_of_birth properties in the data() initialiser so Vue can react to them. You can even initialise them from your child prop there...
data() {
return {
edit: false,
today: moment().format('DD/MM/YYYY'),
name: this.child.name // no need to use slice, strings are immutable
date_of_birth: this.child.date_of_birth
}
}
Use this.date_of_birth inside your age computed property instead of this.child.date_of_birth. This way, it will react to changes made via your v-model="date_of_birth" input element.
Make childUnder3 a computed property, it will be easier that way
childUnder3() {
return this.age < 3
}
Alternately, ditch this and just use v-if="age < 3"
With the above, you no longer need any #input or #change event listeners.

How do you activate a class for individual elements within an array? [Vue.js]

I want to activate a class for each input individually. I have two inputs bound to the same v-model and class. I have a method that checks for something to be true, and if true enables the bound class. Currently it enables the class on all inputs. (The end goal is to search multiple inputs for an element within an array and if it matches, the class activates only for that element)
<input v-model="highlightTest" id="1" v-bind:class="{ active: active }" v-on:keyup="Highlighting"></input>
<input v-model="highlightTest" id="2" v-bind:class="{ active: active }" v-on:keyup="Highlighting"></input>
Highlighting: function() {
if (this.highlightTest != '') {
this.active = true;
}
else {
this.active = false;
}
How about this:
<template>
<input v-for="(hi,index) of highlights" v-model="highlights[]" v-bind:class="{ active: highlights[index] }" v-on:keyup="highlighting(index)"></input>
</template>
<script>
export default{
data() {
return {
highlights: []
};
},
created() {
this.$http.get('some/api').then(res => {
// map: convert 0,1 to false,true
this.highlights = res.json().map(h => h==1);
});
},
methods: {
highlighting(index) {
if (this.highlights[index]) {
// this.highlights[index] = false won't let vue detect the data change(thus no view change)
this.highlights.splice(index, 1, false);
} else {
this.highlights.splice(index, 1, true);
}
}
}
}
</script>
Here's one way to do it (sorry for the delay btw)
HTML:
<div id="app">
<p :class="{'active': activateWord(word)}" v-for="word in words">#{{ word }}</p>
<input type="text" v-model="inputText">
</div>
CSS:
.active {
color: red;
}
JS:
const app = new Vue({
el: '#app',
data: {
inputText: '',
words: [
'foo',
'bar'
]
},
methods: {
activateWord(word) {
return this.inputText === word
},
},
})
here's a fiddle