Inserting a set of input fields and button from data-id of element - javascript

I currently have a form that shows itself after I clicked on a button, and the save button would allow me to save the input values into an object.
What would be a more efficient way to write this function if I need to have 9 more of the same button with different data-id(e.g. data-id="2" and so on), without having to duplicate the function into saveConditionOne,saveConditionTwo,saveConditionThree.
const patient = {}
// Save conditions to object
function saveConditions() {
const missing = document.getElementById('missingVal').value
const movement = document.getElementById('movementVal').value
const tilt = document.getElementById('tiltVal').value
patient.one = {
missing: missing,
movement: movement,
tilt: tilt
}
console.log(patient)
}
<button data-id="1" id="showForm" class="btn">1</button>
<div id="conditionSelect">
<h5>Form</h5>
<div class="field">
<label for="">Missing Tooth</label>
<input type="text" id="missingVal">
</div>
<div class="field">
<label for="">Movement</label>
<input type="text" id="movementVal">
</div>
<div class="field">
<label for="">Tilt</label>
<input type="text" id="tiltVal">
</div>
<button id="saveCondition" onclick="saveConditions()" class="btn btn-primary">Save</button>
</div>
Now
{
"one": {
"missing": "tooth",
"movement": "1mm",
"tilt": "10deg"
}
}
Expected
{
"one": {
"missing": "tooth",
"movement": "1mm",
"tilt": "10deg"
},
"two": {
"missing": "tooth",
"movement": "1mm",
"tilt": "10deg"
},
"three": {
"missing": "tooth",
"movement": "1mm",
"tilt": "10deg"
}
}

I wouldn't use an object for patient (or at least, not for this part of the patient information), I'd use an array. Then you simply push the data onto the array:
patient.push({
missing: missing,
movement: movement,
tilt: tilt
});
...or if you make this a property on the patient object (perhaps there is other, patient-wide information):
patient.entries.push({
missing: missing,
movement: movement,
tilt: tilt
});
Live Example:
const patient = {
entries: []
}
// Save conditions to object
function saveConditions() {
const missing = document.getElementById('missingVal').value
const movement = document.getElementById('movementVal').value
const tilt = document.getElementById('tiltVal').value
patient.entries.push({
missing: missing,
movement: movement,
tilt: tilt
})
console.log(patient)
}
.as-console-wrapper {
max-height: 100% !important;
}
<button data-id="1" id="showForm" class="btn">1</button>
<div id="conditionSelect">
<h5>Form</h5>
<div class="field">
<label for="">Missing Tooth</label>
<input type="text" id="missingVal">
</div>
<div class="field">
<label for="">Movement</label>
<input type="text" id="movementVal">
</div>
<div class="field">
<label for="">Tilt</label>
<input type="text" id="tiltVal">
</div>
<button id="saveCondition" onclick="saveConditions()" class="btn btn-primary">Save</button>
</div>

How about this approach. I've loaded your default form into a template which you can loop over. This means you don't have to hardcode each form directly into the page. I've then added one button which when clicked calls the saveConditions function. This loops over the forms extracting their data and adding it to the data object using a patient id.
const patientCount = 3;
const data = {};
function getFormHTML(id) {
return (
`<div class="conditionSelect" data-id="patient${id}">
<h5>Form</h5>
<div class="field">
<label>Missing Tooth</label>
<input type="text" name="missingVal">
</div>
<div class="field">
<label>Movement</label>
<input type="text" name="movementVal">
</div>
<div class="field">
<label>Tilt</label>
<input type="text" name="tiltVal">
</div>
</div>`
)
}
const html = [];
for (let i = 0; i < patientCount; i++) {
html.push(getFormHTML(i));
}
document.body.insertAdjacentHTML('beforeend', html.join(''));
document.body.insertAdjacentHTML('beforeend', '<button class="saveCondition">Save</button>')
const button = document.querySelector('.saveCondition');
button.addEventListener('click', saveConditions, false);
function getVal(form, name) {
return form.querySelector(`[name=${name}]`).value
}
function saveConditions(e) {
const forms = document.querySelectorAll('.conditionSelect');
forms.forEach(form => {
const patientId = form.dataset.id;
data[patientId] = {
missing: getVal(form, 'missingVal'),
movement: getVal(form, 'movementVal'),
tilt: getVal(form, 'tiltVal'),
};
});
console.log(data);
}

Related

How can I validate form fields using JavaScript in Multi-step HTML Form?

I'm working on registration form that has three sections. A user moves to the next section of the form when the button "Next" is clicked. Everything is working well except that validation errors are only showing on the last section of the Form. I would like to validate the form before moving to the next section. For now, when "Next" button is clicked, the user can move to the next section even without filling the fields. I'm not so experienced in JavaScript, please help.
HTML:
<section>
<div class="container">
<form>
<div class="step step-1 active">
<div class="form-group">
<label for="firstName">First Name</label>
<input type="text" id="firstName" name="first-name">
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input type="text" id="lastName" name="last-name">
</div>
<div class="form-group">
<label for="nickName">Nick Name</label>
<input type="text" id="nickName" name="nick-name">
</div>
<button type="button" class="next-btn">Next</button>
</div>
<div class="step step-2">
<div class="form-group">
<label for="email">Email</label>
<input type="text" id="email" name="email">
</div>
<div class="form-group">
<label for="phone">Phone</label>
<input type="number" id="phone" name="phone-number">
</div>
<button type="button" class="previous-btn">Prev</button>
<button type="button" class="next-btn">Next</button>
</div>
<div class="step step-3">
<div class="form-group">
<label for="country">country</label>
<input type="text" id="country" name="country">
</div>
<div class="form-group">
<label for="city">City</label>
<input type="text" id="city" name="city">
</div>
<div class="form-group">
<label for="postCode">Post Code</label>
<input type="text" id="postCode" name="post-code">
</div>
<button type="button" class="previous-btn">Prev</button>
<button type="submit" class="submit-btn">Submit</button>
</div>
</form>
</div>
</section>
JavaScript:
const steps = Array.from(document.querySelectorAll("form .step"));
const nextBtn = document.querySelectorAll("form .next-btn");
const prevBtn = document.querySelectorAll("form .previous-btn");
const form = document.querySelector("form");
nextBtn.forEach((button) => {
button.addEventListener("click", () => {
changeStep("next");
});
});
prevBtn.forEach((button) => {
button.addEventListener("click", () => {
changeStep("prev");
});
});
form.addEventListener("submit", (e) => {
e.preventDefault();
const inputs = [];
form.querySelectorAll("input").forEach((input) => {
const { name, value } = input;
inputs.push({ name, value });
});
console.log(inputs);
form.reset();
});
function changeStep(btn) {
let index = 0;
const active = document.querySelector(".active");
index = steps.indexOf(active);
steps[index].classList.remove("active");
if (btn === "next") {
index++;
} else if (btn === "prev") {
index--;
}
steps[index].classList.add("active");
}
If you want to validate one section of the form before moving on to the next one you should do something like this:
nextBtn.forEach(button => {
button.addEventListener("click", () => {
handleEvent("next")
})
})
prevBtn.forEach(button => {
button.addEventListener("click", () => {
handleEvent("prev")
})
})
where handleEvent is:
function handleEvent(btn) {
if (!handleFormValidation(btn)) return "error message here";
changeStep(btn)
}
Here handleFormValidation would be a function that checks weather the input is correct and returns true if it is and false if it isn't
If you want to make sure the user fills in the first form first before going to the second you can do it by making the second form appear only after the next button is pressed, but that would require a major rework of your system. (i do however advise it because when i copied your code to test it i noticed quite a lot of bugs)
Here are some mdn articles describing how to make, delete and append elements using javascript:
making an element
removing an element
removing an element
appending an element to another element
appending an element to another element
I highly encourage you to do your own research as well.
I also just want to apologise if anything in my answer isn't understandable. I'm new at contributing to this community so there will likely be mistakes I've made.

VUE JS - Some of my methods are being called without me calling them

I'm having an annoying issue with Vue JS >.< My methods are being called without my approval. I basically have a button that execute a method, but this method execute when other methods are executed, making it very annoying...
Here is my form
<form class="row">
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12 pr-xl-0 pr-lg-0 pr-md-0 m-b-30">
<div class="product-slider">
<img class="d-block" :src="image" alt="First slide" width="285" height="313">
Image URL <input type="text" #focusout="showPicture" id="imageLink">
</div>
</div>
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12 pl-xl-0 pl-lg-0 pl-md-0 border-left m-b-30">
<div class="product-details">
<div class="border-bottom pb-3 mb-3">
<h2 class="mb-3">
<input type="text" value="Product Name" minlength="4" id="name" required/>
</h2>
<h3 class="mb-0 text-primary">$<input type="number" value="1.00" step="0.01" id="price" required></h3>
</div>
<div class="product-size border-bottom">
<h4>Provider</h4>
<input type="text" value="Pro Inc." minlength="3" id="provider" required>
<div class="product-qty">
<h4>Quantity</h4>
<div class="quantity">
<input type="number" value="1" id="quantity" required>
</div>
</div>
</div>
<div class="product-description">
<h4 class="mb-1">Description</h4>
<textarea rows="4" cols="50" minlength="50" id="description" required>Sample Text</textarea>
<button :onclick="addProduct()" class="btn btn-primary btn-block btn-lg">Add to inventory</button>
</div>
</div>
</div>
</form>
and here is my full script
<script>
const DB_NAME = 'DBInventory';
const DB_VERSION = 1;
export default {
data: function() {
return {
db:null,
ready:false,
addDisabled:false,
image: "https://i.imgur.com/O9oZoje.png",
};
},
async created() {
this.db = await this.getDb();
this.ready = true;
},
methods: {
showPicture() {
let link = document.getElementById("imageLink").value;
if(link !== "")
this.image = link;
},
async getDb() {
return new Promise((resolve, reject) => {
let request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = e => {
console.log('Error opening db', e);
reject('Error');
};
request.onsuccess = e => {
resolve(e.target.result);
};
request.onupgradeneeded = e => {
console.log('onupgradeneeded');
let db = e.target.result;
let objectStore = db.createObjectStore("products", { autoIncrement: true, keyPath:'id' });
console.log(objectStore);
};
});
},
async addProduct() {
this.addDisabled = true;
let product = {
name: document.getElementById("name").value,
provider: document.getElementById("provider").value,
price: document.getElementById("price").value,
quantity: document.getElementById("quantity").value,
description: document.getElementById("description").value,
image: document.getElementById("imageLink").value,
};
console.log('about to add '+JSON.stringify(product));
await this.addProductToDb(product);
this.addDisabled = false;
},
async addProductToDb(product) {
return new Promise((resolve, reject) => {
//delete me
console.log(reject);
let trans = this.db.transaction(['products'],'readwrite');
trans.oncomplete = e => {
//delete me
console.log(e);
resolve();
};
let store = trans.objectStore('products');
store.add(product);
});
},
}
}
</script>
One of my method execute when you are not focused on the image input field. It works, but also execute the addProduct(), which push my item to my indexDB, something that I want to happen only when I press the button "Add to inventory".
This is very confusing and I'm kinda a noob on Vue JS ^^' (I use Vue 3)
You have the wrong syntax
:onclick="addProduct()"
:onclick should be #click or v-on:click
https://v2.vuejs.org/v2/guide/events.html
To avoid the function to be autoexecuted, you need to provide the onclick method with the function declaration. Try the code below. I don't use Vue, but it works in React. Javascript anyway.
:onclick="() => addProduct()"
Correct syntax for click event in vue
full syntax
<a v-on:click="addProduct"></a>
shorthand syntax
<a #click="addProduct"></a>
Then call method addProduct
addProduct: function () {
...
}

How to checked radio button in Vue.js using V-Model?

Im new to laravel and vue.js. I'm developing a simple online examination and Im having a hard time in showing the answer of the user if he/she will click the previous button.
This my template I used v-for to loop the questions:
<div class="myQuestion" v-for="(question, index) in questions" :key="index + uuid">
<div class="row">
<div class="col-md-6">
<blockquote >
Total Questions {{ index+1 }} / {{questions.length}}
</blockquote>
<h2 class="question">Q. {{question.question}}</h2>
<form class="myForm" action="/quiz_start" v-on:submit.prevent="createQuestion(question.question_id, question.answer, auth.id, question.topic_id)" method="post">
<input class="radioBtn" v-bind:id="'radio'+ index" type="radio" v-model="result.user_answer" value="A" aria-checked="false" > <span>{{question.a}}</span><br>
<input class="radioBtn" v-bind:id="'radio'+ index+1" type="radio" v-model="result.user_answer" value="B" aria-checked="false"> <span>{{question.b}}</span><br>
<input class="radioBtn" v-bind:id="'radio'+ index+2" type="radio" v-model="result.user_answer" value="C" aria-checked="false"> <span>{{question.c}}</span><br>
<input class="radioBtn" v-bind:id="'radio'+ index+3" type="radio" v-model="result.user_answer" value="D" aria-checked="false"> <span>{{question.d}}</span><br>
<div class="row">
<div class="col-md-6 col-xs-8">
<button type="submit" class="btn btn-wave btn-block nextbtn">Next</button>
</div>
</div>
<div class="row">
<div class="col-md-6 col-xs-8">
<button type="submit" class="btn btn-wave btn-block prebtn">Previous</button>
</div>
</div>
</form>
</div>
</div>
</div>
This is my script to fetch the data and insert the data array to questions variable.
<script>
import { v4 as uuidv4 } from 'uuid';
export default {
props: ['topic_id'],
data () {
return {
questions: [],
answers: [],
uuid:0,
result: {
question_id: '',
answer: '',
user_id: '',
user_answer:0,
topic_id: '',
},
auth: [],
}
},
created () {
this.fetchQuestions();
},
methods: {
fetchQuestions() {
this.$http.get(`${this.$props.topic_id}/quiz/${this.$props.topic_id}`).then(response => {
this.questions = response.data.questions;
this.auth = response.data.auth;
this.uuid=uuidv4();
console.log(this.questions);
}).catch((e) => {
console.log(e)
});
},
createQuestion(id, ans, user_id, topic_id) {
this.result.question_id = id;
this.result.answer = ans;
this.result.user_id = user_id;
this.result.topic_id = this.$props.topic_id;
this.$http.post(`${this.$props.topic_id}/quiz`, this.result).then((response) => {
console.log(response.data.message);
let newdata=response.data.newdata;
this.questions.splice(newdata[0]["index"],1,newdata[0]);
}).catch((e) => {
console.log(e);
});
this.result.topic_id = '';
this.result.user_answer =0;
}
}
}
</script>
I used jQuery next() and prev() for the next and previous buttons. In questions variable, I store my array of objects from database which contains the questions and choices so after the user click next it will update the element of questions to insert the answer chosen by the user. My problem is how can I checked the answer by default chosen by the user if the question showed was answered already by the user. This is the time when the user wants to review his/her answers before finishing the exam.

is there a way to add a textarea to function with a floating label in this case?

I have a simple for with 3 fields using vue.js, I am setting up "floating labels" on focus, as of now I have the inputs working nicely adding and removing class "active" as expected. When I try to add "textarea" my app is breaking and returning "cannot read value of null", is there a way to add the textarea as the inputs are setup?
html:
<form #submit.prevent="submitForm()">
<div class="form-group">
<div id="floatWrapper" class="float-wrapper">
<label for="floatField">Enter Your Name</label>
<input
id="floatField"
v-model="contact_name"
type="text"
class="form-control form-control--email"
#input="$v.contact_name.$touch"
/>
</div>
<p v-if="$v.contact_name.$dirty">
<span v-if="!$v.contact_name.required" class="form__alert">
Please enter your name.
</span>
</p>
<div id="floatWrapper" class="float-wrapper">
<label for="floatField">Enter Your Email</label>
<input
v-model="contact_email"
type="email"
class="form-control form-control--email"
/>
</div>
<p v-if="$v.contact_email.$dirty">
<span v-if="!$v.contact_email.required" class="form__alert">
Email is required.
</span>
<span v-if="!$v.contact_email.email" class="form__alert">
Please enter a valid email
</span>
</p>
<div id="floatWrapper" class="float-wrapper">
<label for="floatField">Enter Your Message</label>
<textarea
v-model="contact_message"
class="form-control form-control--textarea"
rows="5"
/>
</div>
<p v-if="$v.contact_message.$dirty">
<span
v-if="!$v.contact_message.required"
class="form__alert"
>
Message must be over 10 characters
</span>
</p>
</div>
<button
type="submit"
data-cursor="hover"
class="btn btn-primary"
>
Send Message
<font-awesome-icon far icon="arrow-right" />
</button>
</form>
js:
mounted() {
const labelFloating = (() => {
const isFocus = (e) => {
const target = e.target
target.parentNode.classList.add('active')
}
const isBlur = (e) => {
const target = e.target
if (!target.value) {
target.parentNode.classList.remove('active')
}
}
const bindEvents = (element) => {
const field = element.querySelector('input')
field.addEventListener('focus', isFocus)
field.addEventListener('blur', isBlur)
const text = element.querySelector('textarea')
text.addEventListener('focus', isFocus)
text.addEventListener('blur', isBlur)
}
const initialize = () => {
const floatWraps = document.querySelectorAll('.float-wrapper')
floatWraps.forEach((element) => {
if (element.querySelector('input').value) {
element.classList.add('active')
} if (element.querySelector('textarea').value) {
element.classList.add('active')
}
bindEvents(element)
})
}
return {
init,
}
})()
labelFloating.init()
},
The if (element.querySelector('input').value) { will throw that error because you're chaining .value off the selector, but it doesn't exist and returns null. try this instead:
const selector = element.querySelector('input') || element.querySelector('textarea')
if (selector && selector.value) {
element.classList.add('active')
bindEvents(element)
}
And use the same refactor for the bindEvents method:
const bindEvents = (element) => {
const field = element.querySelector('input') || element.querySelector('textrea')
field.addEventListener('focus', isFocus)
field.addEventListener('blur', isBlur)
}

Aurelia issue with setting element class based on obj.id === $parent.selectedId

I completed the contact-manager tut from Aurelia.io and am incorporating it into as task manager tut I'm putting together. The markup below sets the li class based on task.id === $parent.id.
task-list.html
<template>
<div class="task-list">
<ul class="list-group">
<li repeat.for="task of tasks" class="list-group-item ${task.id === $parent.selectedId ? 'active' : ''}">
<a route-href="route: tasks; params.bind: {id:task.id}" click.delegate="$parent.select(task)">
<h4 class="list-group-item-heading">${task.name}</h4>
<span class="list-group-item-text ">${task.due | dateFormat}</span>
<p class="list-group-item-text">${task.isCompleted}</p>
</a>
</li>
</ul>
</div>
task-list.js
#inject(WebAPI, EventAggregator)
export class TaskList {
constructor(api, ea) {
this.api = api;
this.tasks = [];
ea.subscribe(TaskViewed, x => this.select(x.task));
ea.subscribe(TaskUpdated, x => {
let id = x.task.id;
let task = this.tasks.find(x => x.id == id);
Object.assign(task, x.task);
});
}
created() {
this.api.getList().then( x => this.tasks = x);
}
select(task) {
this.selectedId = task.id;
return true;
}
}
If I edit the current task, represented by
task-detail.html
<template>
<require from="resources/attributes/DatePicker"></require>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Edit Task Profile</h3>
</div>
<div class="panel-body">
<form role="form" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">Name</label>
<div class="col-sm-10">
<input type="text" placeholder="name" class="form-control" value.bind="task.name">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Description</label>
<div class="col-sm-10">
<input type="text" placeholder="description" class="form-control" value.bind="task.description">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Due Date</label>
<div class="col-sm-10">
<div class="input-group date">
<input type="text" datepicker class="form-control" value.bind="task.due | dateFormat:'L'"><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Urgency</label>
<div class="col-sm-10">
<input type="range" min="1" max="5" step="1" class="form-control" value.bind="task.urgency">
</div>
</div>
</form>
</div>
</div>
<div class="button-bar">
<button class="btn btn-info" click.delegate="addTask(task)" >Add New</button>
<button class="btn btn-success" click.delegate="save()" disabled.bind="!canSave">Save Edit</button>
</div>
</template>
task-detail.js
#inject(WebAPI, EventAggregator, Utils, DialogService)
export class TaskDetail {
constructor(api, ea, utils, dialogService) {
this.api = api;
this.ea = ea;
this.utils = utils;
this.dialogService = dialogService;
}
activate(params, routeConfig) {
this.routeConfig = routeConfig;
return this.api.getTaskDetails(params.id).then(task => {
this.task = task;
this.routeConfig.navModel.setTitle(task.name);
this.originalTask = this.utils.copyObj(task);
this.ea.publish(new TaskViewed(task));
});
}
get canSave() {
return this.task.name && !this.api.isRequesting;
}
save() {
console.log(this.task);
this.api.saveTask(this.task).then(task => {
this.task = task;
this.routeConfig.navModel.setTitle(task.name);
this.originalTask = this.utils.copyObj(task);
this.ea.publish(new TaskUpdated(this.task));
});
}
canDeactivate() {
if (!this.utils.objEq(this.originalTask, this.task)) {
let result = confirm('You have unsaved changes. Are you sure you wish to leave?');
if (!result) {
this.ea.publish(new TaskViewed(this.task));
}
return result;
}
return true;
}
addTask(task) {
var original = this.utils.copyObj(task);
this.dialogService.open({viewModel: AddTask, model: this.utils.copyObj(this.task)})
.then(result => {
if (result.wasCancelled) {
this.task.name = original.title;
this.task.description = original.description;
}
});
}
}
If a value has changed, navigation away from the current task is not allowed, and that works -- that is, the contact-detail part of the UI doesn't change. However, the task <li>, that one tries to navigate to still gets the active class applied. That's not supposed to happen.
If I step along in dev tools, on the Aurelia.io contact-manager, I see that the active class is briefly applied to the list item, then it goes away.
from the contact-manager's contact-list.js This was run when clicking an <li> and no prior item selected.
select(contact) {
this.selectedId = contact.id;
console.log(contact);
return true;
}
This logs
Object {__observers__: Object}
Object {id: 2, firstName: "Clive", lastName: "Lewis", email: "lewis#inklings.com", phoneNumber: "867-5309"}
The same code on my task-manager's (obviously with "contact" replaced by task") task-list.js logs
Object {description: "Meeting With The Bobs", urgency: "5", __observers__: Object}
Object {id: 2, name: "Meeting", description: "Meeting With The Bobs", due: "2016-09-27T22:30:00.000Z", isCompleted: falseā€¦}
My first instinct is to say it's got something to do with this.selectedId = contact.id
this there would refer to what I assume is a function called select (looks like the function keyword is missing in your example?) or the global object (i.e. window)
select(contact) {
this.selectedId = contact.id;
console.log(contact);
return true;
}
Fixed it. It wasn't working because I had pushstate enabled. That clears things up. Thanks therealklanni.

Categories