How to using Array Loop on validation Form using Vue? - vue.js

I would like to create a simple web app that can validation form using Vue?
I have two input fields, firstname[1] and firstname[2]
data: {
firstname: ['',''],
}
I want to use the following code to validate the form, but finally not suessful.
computed: {
missfirstname(){
for(var i=1;i<this.firstname.length;i++){
if(this.firstname[i] =='' && this.attemptSubmit) {
this.firstname_ErrMsg[i] = 'Not be empty';
return true;
}
return false;
}
}
},
methods: {
validateForm: function (e) {
this.attemptSubmit = true;
if(this.missfirstname){
e.preventDefault();
}else{
return true;
}
}
},
Is it possible to use array Loop on the validation form??
here it my code I am using Vue 2
my full code
script.js
var app = new Vue({
el: '#app',
data: {
firstname: ['',''],
firstname_ErrMsg: ['',''],
attemptSubmit: false
},
mounted () {
var self = this;
},
computed: {
missfirstname(){
for(var i=1;i<this.firstname.length;i++){
if(this.firstname[i] =='' && this.attemptSubmit) {
this.firstname_ErrMsg[i] = 'Not be empty';
return true;
}
return false;
}
}
},
methods: {
validateForm: function (e) {
this.attemptSubmit = true;
if(this.missfirstname){
e.preventDefault();
}else{
return true;
}
}
},
})
index.html
<div id="app">
<form action='process.php' method="post" autocomplete="off" name="submit_form" v-on:submit="validateForm">
firstname1 : <input type='text' id='firstname1' name='firstname1' alt='1' v-model='firstname[1]'>
<div v-if="missfirstname">{{firstname_ErrMsg[1]}}</div>
<br><br>
firstname2 :
<input type='text' id='firstname2' name='firstname2' alt='2' v-model='firstname[2]'>
<div v-if="missfirstname">{{firstname_ErrMsg[2]}}</div>
<br><br>
<input id="submit" class="preview_button" name="submit_form" type="submit">
</form>
</div>
<script src='https://cdn.jsdelivr.net/npm/vue#2.7.8/dist/vue.js'></script>
<script src='js/script.js'></script>

Observations :
missfirstname property should be separate for each field else it will be difficult to assign the error for a specific field.
Instead of iterating over a this.firstname everytime in computed property, you can use #blur and check the value for that particular field which user touched.
v-model properties should be unique else it will update other one on changing current one.
Your user object should be like this and you can use v-for to iterate it in HTML for dynamic fields creation instead of hardcoding.
data: {
user: [{
name: 'firstName1',
missfirstname: false
}, {
name: 'firstName2',
missfirstname: false
}]
}
Now, In validateForm() you can just pass the index of the iteration and check the model value. If value is empty, you can assign missfirstname as true for that particular index object in user array.
Update : As per author comment, assigning object in users array via for loop.
data: {
users: []
},
mounted() {
for(let i = 1; i <= 2; i++) {
this.users.push({
name: 'firstName' + i,
missfirstnam: false
})
}
}

The array of javascript start from index 0.
Which means in your missfirstname(), i should be defined with 0
missfirstname(){
for(var i=0;i<this.firstname.length;i++){
if(this.firstname[i] =='' && this.attemptSubmit) {
this.firstname_ErrMsg[i] = 'Not be empty';
return true;
}
return false;
}
}

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: 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.

Vue watch value from parent is in reverse

I have a custom component that lets users type in text and sends it to the backend where I do some computation and spit the new text back out with html in it.
My problem is when the user types into this textarea, it reverses all the text and keeps the cursors at the beginning of the textarea. So now 'foo bar' becomes 'rab oof'... This only has happened since I added in watch. I could delete the watcher, but I need it (or need another way) to apply my updates to the textarea, via the foo variable when I set foo equal to something from the parent.
console.log(v) writes out the reverse text.
Any idea how to change this?
Custom componet:
<template>
<div contenteditable="true" #input="updateHTML" class="textareaRoot"></div>
</template>
<script>
export default {
name: 'htmlTextArea',
props:['value'],
mounted: function () {
this.$el.innerHTML = this.value;
},
watch: {
value(v) {
this.$el.innerHTML = v; //v is the reverse text.
}
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML);
}
}
}
</script>
Parent that uses custom component:
<htmlTextArea id="textarea" v-model="foo"></htmlTextArea>
...
<script>
...
methods: {
triggerOnClick() {
this.foo = 'something';//Without the watcher, when I change this.foo to something the actual textarea does not display the new data that I assigned to foo. But in Vue dev tools I can see the new change.
}
UPDATE:
Vue.component('html-textarea',{
template:'<div contenteditable="true" #input="updateHTML"></div>',
props:['value'],
mounted: function () {
this.$el.innerHTML = this.value;
},
watch: {
value(v) {
this.$el.innerHTML = v;
}
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML);
}
}
});
new Vue({
el: '#app',
data () {
return {
foo: '',
}
}
});
<script src="https://unpkg.com/vue"></script>
<div id="app">Type here:
<html-textarea spellcheck="false" id="textarea" v-model="foo"> </html-textarea>
</div>
The problem is that when you set the innerHTML of a contenteditable element, you lose the selection (cursor position).
So you should perform the following steps when setting:
save the current cursor position;
set the innerHTML;
restore the cursor position.
Saving and restoring is the tricky part. Luckily I got these two handy functions that do the job for latest IE and newer. See below.
function saveSelection(containerEl) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
}
}
function restoreSelection(containerEl, savedSel) {
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl],
node, foundStart = false,
stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
Vue.component('htmltextarea', {
template: '#hta',
name: 'htmlTextArea',
props:['value'],
mounted: function () {
this.$el.innerHTML = this.value;
},
watch: {
value(v) {
if (v === 'yes') {
let selection = saveSelection(this.$el);
this.$el.innerHTML = 'no!';
this.$emit('input', 'no!');
restoreSelection(this.$el, selection);
}
}
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML);
}
}
});
new Vue({
el: '#app',
data: {
foo: 'Clear this and type "yes" (without the quotes). It should become "no!".'
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<htmltextarea id="textarea" v-model="foo"></htmltextarea>
<hr>
Result: <pre>{{ foo }}</pre>
</div>
<template id="hta">
<div contenteditable="true" #input="updateHTML" class="textareaRoot"></div>
</template>
In your app, I recommend you place them in a dedicated .js file, just for better organization.

Filtering a list of objects in Vue without altering the original data

I am diving into Vue for the first time and trying to make a simple filter component that takes a data object from an API and filters it.
The code below works but i cant find a way to "reset" the filter without doing another API call, making me think im approaching this wrong.
Is a Show/hide in the DOM better than altering the data object?
HTML
<button v-on:click="filterCats('Print')">Print</button>
<div class="list-item" v-for="asset in filteredData">
<a>{{ asset.title.rendered }}</a>
</div>
Javascript
export default {
data() {
return {
assets: {}
}
},
methods: {
filterCats: function (cat) {
var items = this.assets
var result = {}
Object.keys(items).forEach(key => {
const item = items[key]
if (item.cat_names.some(cat_names => cat_names === cat)) {
result[key] = item
}
})
this.assets = result
}
},
computed: {
filteredData: function () {
return this.assets
}
},
}
Is a Show/hide in the DOM better than altering the data object?
Not at all. Altering the data is the "Vue way".
You don't need to modify assets to filter it.
The recommended way of doing that is using a computed property: you would create a filteredData computed property that depends on the cat data property. Whenever you change the value of cat, the filteredData will be recalculated automatically (filtering this.assets using the current content of cat).
Something like below:
new Vue({
el: '#app',
data() {
return {
cat: null,
assets: {
one: {cat_names: ['Print'], title: {rendered: 'one'}},
two: {cat_names: ['Two'], title: {rendered: 'two'}},
three: {cat_names: ['Three'], title: {rendered: 'three'}}
}
}
},
computed: {
filteredData: function () {
if (this.cat == null) { return this.assets; } // no filtering
var items = this.assets;
var result = {}
Object.keys(items).forEach(key => {
const item = items[key]
if (item.cat_names.some(cat_names => cat_names === this.cat)) {
result[key] = item
}
})
return result;
}
},
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<button v-on:click="cat = 'Print'">Print</button>
<div class="list-item" v-for="asset in filteredData">
<a>{{ asset.title.rendered }}</a>
</div>
</div>

how to do filters in vue.js

i am new to vue.js.so i want to apply a filter thing in my project.i tried to do this from past 2 to 3 days..but i couldn't..can any one help me how to do this..
I have 3 input boxes one is experience,expected_ctc,and profile_role depending on these 3 i want to display the results..
Here is my js page:
const app = new Vue({
el: '#app',
data() {
return {
page: 0,
itemsPerPage: 3,
}
},
components: { Candidate },
methods: {
//custom method bound to page buttons
setPage(page) {
this.page = page-1;
this.paginedCandidates = this.paginate()
},
paginate() {
return this.filteredCandidates.slice(this.page * this.itemsPerPage, this.page * this.itemsPerPage + this.itemsPerPage)
},
},
computed: {
//compute number of pages, we always round up (ceil)
numPages() {
console.log(this.filteredCandidates)
return Math.ceil(this.filteredCandidates.length/this.itemsPerPage);
},
filteredCandidates() {
//filter out all candidates that have experience less than 10
const filtered = window.data.candidates.filter((candidate) => {
if(candidate.experience === 5) {
return false;
}
return true;
})
console.log(filtered);
return filtered;
},
paginedCandidates() {
return this.paginate()
}
}
});
here is my buttons from view page:
<div class="container">
<b>Experience:</b><input type="text" v-model="experience" placeholder="enter experience">
<b>Expected CTC:</b><input type="text" v-model="expected_ctc" placeholder="enter expected ctc">
<b>Profile Role:</b><input type="text" v-model="profile_role_id" placeholder="enter role">
<input type="button" name="search" value="search" >
</div>
Can anyone help me..
Thanks in advance..
Ok let's start with a smaller example. Lets say you have "Candidates" one one candidate might look like
{
name: 'John Doe',
expected_ctc: 'A',
experience: 'B',
profile_role_id: 1
}
From your current state I'd say you have all candidates in an array returned by laravel. Let's say we're in your current template where you have your vue app
<div id="app">
<!-- lets start with one filter (to keept it clean) -->
<input type="text" v-model="experienceSearch"/>
<ul class="result-list">
<li v-for="result in candidatelist">{{ result.name }}</li>
</ul>
</div>
And now to the script
// always init your v-model bound props in data
// usually you wouldn't take the candidates from the window prop. However, to keep it easy for you here I will stick to this
data: function() {
return {
experienceSearch: '',
candidates: window.data.candidates
}
},
// the list that is displayed can be defined as computed property. It will re-compute everytime your input changes
computed: {
candidatelist: function() {
// now define how your list is filtered
return this.candidates.filter(function(oCandidate) {
var matches = true;
if (this.experienceSearch) {
if (oCandidate.experience != this.experienceSearch) {
matches = false;
}
}
// here you can define conditions for your other matchers
return matches;
}.bind(this));
}
}
General steps:
all candidates are in data -> candidates
relevant (filtered) candidates are represented by the computed prop candidatelist
inputs are bound with v-model to EXISTING data prop definitions
Fiddle https://jsfiddle.net/sLLk4u2a/
(Search is only exact and Case Sensitive)