I need a form with multiple steps where the first step restricts options in the successive steps.
So below is an example of use:
User selects from 4 global options
If user selects option 1
Then the user can only select 8 options (no more no less) from a multiple choice box.
If user select option 2
Then the user can only select 10 options (no more no less) from a multiple choice box.
Same is the case with options 3 and 4
After all these selections, this form has to show a final price based on the options user selected in the FIRST STEP. No matter what choices were selected in successive steps.
When this form shows final price, user has to click SUBMIT button and send this result with all these options through an email.
Can someone help me in figuring out the javascript required to do this? Most important thing right now for me is to figure out 'restrictions codes' in multiple items choice boxes.
This is the form I was thinking about:
<form action="" id="menuform" onsubmit="return false;">
<fieldset>
<legend>Select one of this four menus</legend>
<label >Menu Option</label>
<input type="radio" name="selectedmenu" value="Menu1"
onclick="calculateTotal()" />
Menu 1 - serves 8 courses ($20)
<input type="radio" name="selectedmenu" value="Menu2"
onclick="calculateTotal()" />
Menu 2 - serves 12 courses ($25)
<input type="radio" name="selectedmenu" value="Menu3"
onclick="calculateTotal()" />
Menu 3 - serves 16 courses ($35)
<input type="radio" name="selectedmenu" value="Menu4"
onclick="calculateTotal()" />
Menu 4 - serves 30 courses ($75)
<label >Filling</label>
<select id="filling" name='filling'
onchange="calculateTotal()">
<option value="dish1">Dish 1 ($1)</option>
<option value="dish2">Dish 2 ($5)</option>
<option value="dish3">Dish 3 ($5)</option>
(more...)
</select>
<br/>
<div id="totalPrice"></div>
</fieldset>
</form>
Then, for Javascript code I was trying with this to store and return some values but my problem is how to force to select an exact number of options in the SECOND STEP:
var menu_prices = new Array();
menu_prices["Menu1"]=20;
menu_prices["Menu2"]=25;
menu_prices["Menu3"]=35;
menu_prices["Menu4"]=75;
function getMenuPrice()
{
var menuPrice=0;
var theForm = document.forms["menuform"];
var selectedMenu = theForm.elements["selectedmenu"];
for(var i = 0; i < selectedMenu.length; i++)
{
if(selectedMenu[i].checked)
{
menuPrice = menu_prices[selectedMenu[i].value];
break;
}
}
return menuPrice;
}
function getTotal()
{
var menuPrice = getMenuPrice() + getOtherOptionsPrices();
document.getElementById('totalPrice').innerHTML =
"Total Price For Menu $"+menuPrice;
}
Structure your HTML markup carefully, which helps you to target elements via JavaScript easily. Especially, using data-attributes on radio buttons for quantity and price which could be then easily retrieved in respective event handlers. Something like this:
<form action="" method="post" id="menuform" name="menuform">
<fieldset>
<legend>Select menu option</legend>
<label>
<input type="radio" name="selectedmenu" checked value="menu01" data-qty='3' data-price='20' />
<span>1 - serves 3 courses ($20)</span>
</label>
...
<fieldset>
<legend id='fillingsPrompt'>Select fillings</legend>
<select id="fillings" name="fillings" size="6" multiple>
<option value="1">Dish 1 ($1)</option>
...
<fieldset>
<legend>Checkout</legend>
<div id="totalPrice"></div>
<input type="submit" value="Submit" />
</fieldset>
<fieldset>
<legend>Messages</legend>
<p id="result"></p>
</fieldset>
</form>
Identify and select all elements that you'll need:
var menuform = document.getElementById('menuform'),
radios = document.getElementsByName('selectedmenu'),
fillings = document.getElementById('fillings'),
fillingsPrompt = document.getElementById('fillingsPrompt'),
totalPrice = document.getElementById('totalPrice'),
result = document.getElementById('result'),
fillingsAllowed = 0, currentSelection = [], currency = '$'
;
Add event listeners to your radio buttons, select, and the submit button:
menuform.addEventListener('submit', handleSubmit);
fillings.addEventListener('change', handleFillings);
for (var i = radios.length; i--; ) {
radios[i].addEventListener('change', handleLimit);
}
Code the actual event handlers:
// When radio buttons are selected, update parameters for limit
function handleLimit(e) { updateParameters(e.target); }
// When options are selected in the dropdown,
// check against the limit and reset the selection if it exceeds
function handleFillings(e) {
var count = getSelectedCount();
if (count > fillingsAllowed) { resetSelect(); }
else { currentSelection = getSelectedValues(); }
}
// When submit button is clicked,
// check the count of selection against the limit, and
// show appropriate error message
function handleSubmit(e) {
var count = getSelectedCount();
e.preventDefault();
if (count != fillingsAllowed) {
result.textContent = 'Must select exactly ' + fillingsAllowed + ' fillings!';
} else {
result.textContent = 'Ok. ';
}
}
And then code all the helper functions used in the handlers above:
function updateParameters(elem) {
// update the limit based on quantity data attribute on radio
fillingsAllowed = elem.getAttribute('data-qty');
// show the amount based on price data-attribute
totalPrice.textContent = 'Amount: ' + currency + elem.getAttribute('data-price');
// show the hint on legend of fieldset for selecting options
fillingsPrompt.textContent = 'Select ' + fillingsAllowed + ' fillings';
}
// iterate options and get count of selected ones
function getSelectedCount() {
var options = fillings.options, count = 0;
for (var i=0; i < options.length; i++) {
if (options[i].selected) count++;
}
return count;
}
// iterate options and get selected values in an array
function getSelectedValues() {
var options = fillings.options, values = [0];
for (var i=0; i < options.length; i++) {
if (options[i].selected) values.push(options[i].value);
}
return values;
}
// remove selection from all options, and
// re-select based on the array used in the previous function
function resetSelect() {
var options = fillings.options;
for (var i=0; i < options.length; i++) {
options[i].selected = false;
if (currentSelection.indexOf(options[i].value) != -1) {
options[i].selected = true;
}
}
}
Everything put together, the demo looks like this:
Fiddle: https://jsfiddle.net/abhitalks/L813qudw/
Snippet:
var menuform = document.getElementById('menuform'),
radios = document.getElementsByName('selectedmenu'),
fillings = document.getElementById('fillings'),
fillingsPrompt = document.getElementById('fillingsPrompt'),
totalPrice = document.getElementById('totalPrice'),
result = document.getElementById('result'),
fillingsAllowed = 0, currentSelection = [], currency = '$'
;
// listen to events
menuform.addEventListener('submit', handleSubmit);
fillings.addEventListener('change', handleFillings);
for (var i = radios.length; i--; ) {
radios[i].addEventListener('change', handleLimit);
}
// event handlers
function handleLimit(e) { updateParameters(e.target); }
function handleFillings(e) {
var count = getSelectedCount();
if (count > fillingsAllowed) { resetSelect(); }
else { currentSelection = getSelectedValues(); }
}
function handleSubmit(e) {
var count = getSelectedCount();
e.preventDefault();
if (count != fillingsAllowed) {
result.textContent = 'Must select exactly ' + fillingsAllowed + ' fillings!';
} else {
result.textContent = 'Ok. ';
}
}
// fire initial update based on the first radio
updateParameters(radios[0]);
// helper functions
function updateParameters(elem) {
fillingsAllowed = elem.getAttribute('data-qty');
totalPrice.textContent = 'Amount: ' + currency + elem.getAttribute('data-price');
fillingsPrompt.textContent = 'Select ' + fillingsAllowed + ' fillings';
}
function getSelectedCount() {
var options = fillings.options, count = 0;
for (var i=0; i < options.length; i++) {
if (options[i].selected) count++;
}
return count;
}
function getSelectedValues() {
var options = fillings.options, values = [0];
for (var i=0; i < options.length; i++) {
if (options[i].selected) values.push(options[i].value);
}
return values;
}
function resetSelect() {
var options = fillings.options;
for (var i=0; i < options.length; i++) {
options[i].selected = false;
if (currentSelection.indexOf(options[i].value) != -1) {
options[i].selected = true;
}
}
}
fieldset {
margin: 1vw; font-family: monospace;
display: inline-block; width: 40vw; vertical-align: top;
}
legend { color: #d33; padding: 0px 4px; }
legend::before { content: '[ '; }
legend::after { content: ' ]'; }
fieldset > label { display: block; margin: 4px 0px; }
fieldset input, fieldset span { vertical-align: middle; }
fieldset > select { width: 100%; font-family: monospace; }
input[type=submit] { margin-top: 12px; }
#totalPrice, #result {
height: 24px; line-height: 24px;
background-color: #dde; padding: 4px;
font-family: monospace;
}
#result { color: #d33; font-family: monospace; }
<form action="" method="post" id="menuform" name="menuform">
<fieldset>
<legend>Select menu option</legend>
<label>
<input type="radio" name="selectedmenu" checked
value="menu01" data-qty='3' data-price='20' />
<span>1 - serves 3 courses ($20)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu02" data-qty='4' data-price='25' />
<span>2 - serves 4 courses ($25)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu03" data-qty='5' data-price='35' />
<span>3 - serves 5 courses ($35)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu04" data-qty='6' data-price='75' />
<span>4 - serves 6 courses ($75)</span>
</label>
</fieldset>
<fieldset>
<legend id='fillingsPrompt'>Select fillings</legend>
<select id="fillings" name="fillings" size="6" multiple>
<option value="1">Dish 1 ($1)</option>
<option value="2">Dish 2 ($5)</option>
<option value="3">Dish 3 ($5)</option>
<option value="4">Dish 4 ($1)</option>
<option value="5">Dish 5 ($5)</option>
<option value="6">Dish 6 ($5)</option>
</select>
</fieldset>
<fieldset>
<legend>Checkout</legend>
<div id="totalPrice"></div>
<input type="submit" value="Submit" />
</fieldset>
<fieldset>
<legend>Messages</legend>
<p id="result"></p>
</fieldset>
</form>
<hr>
...how can I change <option> and use <input type="checkbox">
instead for the SECOND STEP?
In order to use checkboxes instead of select, no major changes are required.
Changed mark-up:
<fieldset>
<legend id='fillingsPrompt'>Select fillings</legend>
<label>
<input type='checkbox' name='fillings' value="1" />
<span>Dish 1 ($5)</span>
</label>
...
JavaScript changes:
Adding the event-handlers for checkboxes instead of select, would require just iterating over those:
(just like the radios already done)
for (var i = fillings.length; i--; ) {
fillings[i].addEventListener('change', handleFillings);
}
In all the helper functions, remove the variable declaration for options:
(as it is now no longer required)
var options = fillings.options
And, In all the helper functions,
change: options.length and options[i].selected
to, fillings.length and fillings[i].checked respectively.
That's it.
Fiddle 2: https://jsfiddle.net/abhitalks/hp88wdfc/
Snippet 2:
var menuform = document.getElementById('menuform'),
radios = document.getElementsByName('selectedmenu'),
fillings = document.getElementsByName('fillings'),
fillingsPrompt = document.getElementById('fillingsPrompt'),
totalPrice = document.getElementById('totalPrice'),
result = document.getElementById('result'),
fillingsAllowed = 0, currentSelection = [], currency = '$'
;
// listen to events
menuform.addEventListener('submit', handleSubmit);
for (var i = fillings.length; i--; ) {
fillings[i].addEventListener('change', handleFillings);
}
for (var i = radios.length; i--; ) {
radios[i].addEventListener('change', handleLimit);
}
// event handlers
function handleLimit(e) { updateParameters(e.target); }
function handleFillings(e) {
var count = getSelectedCount();
if (count > fillingsAllowed) { resetSelect(); }
else { currentSelection = getSelectedValues(); }
}
function handleSubmit(e) {
var count = getSelectedCount();
e.preventDefault();
if (count != fillingsAllowed) {
result.textContent = 'Must select exactly ' + fillingsAllowed + ' fillings!';
} else {
result.textContent = 'Ok. ';
}
}
// fire initial update based on the first radio
updateParameters(radios[0]);
// helper functions
function updateParameters(elem) {
fillingsAllowed = elem.getAttribute('data-qty');
totalPrice.textContent = 'Amount: ' + currency + elem.getAttribute('data-price');
fillingsPrompt.textContent = 'Select ' + fillingsAllowed + ' fillings';
}
function getSelectedCount() {
var count = 0;
for (var i=0; i < fillings.length; i++) {
if (fillings[i].checked) count++;
}
return count;
}
function getSelectedValues() {
var values = [0];
for (var i=0; i < fillings.length; i++) {
if (fillings[i].checked) values.push(fillings[i].value);
}
return values;
}
function resetSelect() {
for (var i=0; i < fillings.length; i++) {
fillings[i].checked = false;
if (currentSelection.indexOf(fillings[i].value) != -1) {
fillings[i].checked = true;
}
}
}
fieldset {
margin: 1vw; font-family: monospace;
display: inline-block; width: 40vw; vertical-align: top;
}
legend { color: #d33; padding: 0px 4px; }
legend::before { content: '[ '; }
legend::after { content: ' ]'; }
fieldset:first-of-type > label { display: block; margin: 4px 0px; }
fieldset:nth-of-type(2) > label {
display: inline-block; width: 45%;
}
fieldset input, fieldset span { vertical-align: middle; }
input[type=submit] { margin-top: 12px; }
#totalPrice, #result {
height: 24px; line-height: 24px;
background-color: #dde; padding: 4px;
font-family: monospace;
}
#result { color: #d33; font-family: monospace; }
<form action="" method="post" id="menuform" name="menuform">
<fieldset>
<legend>Select menu option</legend>
<label>
<input type="radio" name="selectedmenu" checked
value="menu01" data-qty='3' data-price='20' />
<span>1 - serves 3 courses ($20)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu02" data-qty='4' data-price='25' />
<span>2 - serves 4 courses ($25)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu03" data-qty='5' data-price='35' />
<span>3 - serves 5 courses ($35)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu04" data-qty='6' data-price='75' />
<span>4 - serves 6 courses ($75)</span>
</label>
</fieldset>
<fieldset>
<legend id='fillingsPrompt'>Select fillings</legend>
<label>
<input type='checkbox' name='fillings' value="1" />
<span>Dish 1 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="2" />
<span>Dish 2 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="3" />
<span>Dish 3 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="4" />
<span>Dish 4 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="5" />
<span>Dish 5 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="6" />
<span>Dish 6 ($5)</span>
</label>
</fieldset>
<fieldset>
<legend>Checkout</legend>
<div id="totalPrice"></div>
<input type="submit" value="Submit" />
</fieldset>
<fieldset>
<legend>Messages</legend>
<p id="result"></p>
</fieldset>
</form>
<hr>
Related
How can I divide "tasks" from input into categories? The filtered gallery is fine, I found a lot of tutorials on W3school, but I did not find anywhere how I can add them from a direct input, probably using option-id.
filterSelection("all")
function filterSelection(c) {
var x, i;
x = document.getElementsByClassName("filterDiv");
if (c == "all") c = "";
for (i = 0; i < x.length; i++) {
w3RemoveClass(x[i], "show");
if (x[i].className.indexOf(c) > -1) w3AddClass(x[i], "show");
}
}
function addClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
if (arr1.indexOf(arr2[i]) == -1) {
element.className += " " + arr2[i];
}
}
}
function removeClass(element, name) {
var i, arr1, arr2;
arr1 = element.className.split(" ");
arr2 = name.split(" ");
for (i = 0; i < arr2.length; i++) {
while (arr1.indexOf(arr2[i]) > -1) {
arr1.splice(arr1.indexOf(arr2[i]), 1);
}
}
element.className = arr1.join(" ");
}
// Add active class to the current button (highlight it)
var btnContainer = document.getElementById("myBtnContainer");
var btns = btnContainer.getElementsByClassName("btn");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function() {
var current = document.getElementsByClassName("active");
current[0].className = current[0].className.replace(" active", "");
this.className += " active";
});
}
<form id="FORM">
<label for="jmeno">Assigned task</label>
<input type="text" id="homework" required />
<label for="category">Category</label>
<select name="selectCategory" id="category">
<option value="">---</option>
<option value="School" id="school">School</option>
<option value="Work" id="work">Work</option>
</select>
<input type="submit" value="Add" />
</form>
<h2>TASK TO</h2>
<div id="myBtnContainer">
<button class="btn active" onclick="filterSelection('all')"> All</button>
<button class="btn" onclick="filterSelection('school')"> To school</button>
<button class="btn" onclick="filterSelection('work')"> To work</button>
</div>
Something like this should work.
let taskForm = document.tasks;
taskForm.onsubmit = e => {
e.preventDefault();
let taskData = new FormData(taskForm);
let category = taskData.get("category");
let result = document.querySelector(".tasks");
let task = document.createElement('div');
let filterTask = document.querySelector('input[name="filter"]:checked');
task.appendChild(document.createTextNode(taskData.get("task")));
task.dataset.group = category;
task.classList.add("task");
task.style.display = ["all", category].includes(filterTask.value) ? "block" : "none";
result.appendChild(task);
}
let showTasks = document.getElementsByName("filter");
showTasks.forEach(el => {
el.addEventListener("click", e => {
tasks = document.querySelectorAll(".task");
tasks.forEach(task => {
task.style.display = ["all", task.dataset.group].includes(e.target.value) ? "block" : "none";
});
});
});
input[type="radio"] {
opacity: 0;
position: fixed;
width: 0;
}
input[type="radio"] ~ label {
display: inline-block;
background-color: #ddd;
padding: 5px 20px;
font-family: sans-serif, Arial;
font-size: 16px;
border: 2px solid #444;
border-radius: 4px;
}
input[type="radio"] ~ label:hover {
background-color: #dfd;
}
input[type="radio"]:checked + label {
background-color: #bfb;
border-color: #4c4;
}
<div>
<form name="tasks">
<label for="name">Assigned Task</label>
<input name="task" type="text" required />
<label for="category">Category</label>
<select name="category">
<option value="school" selected>School</option>
<option value="work">Work</option>
</select>
<input type="submit" value="Add" />
</form>
<h3>Task To</h3>
<input name="filter" id="all" type="radio" value="all" checked />
<label for="all">All</label>
<input name="filter" id="school" type="radio" value="school" />
<label for="school">School</label>
<input name="filter" id="work" type="radio" value="work" />
<label for="work">Work</label>
<h2>Tasks</h2>
<div class="tasks"></div>
</div>
You can style the added tasks using their .task selector in CSS.
Note that if you want to animate while showing and hiding your tasks, instead of changing the display property consider playing with opacity and visibility properties.
I am currently building a window on a website where users can book a boat trip, which requires multiple steps, and 3/5 steps include forms (I only included the first form in the html since it would get too long otherwise - see below).
I am currently handling the validation of the first form, which you can see below (".availability step1"). I've spent quite some time on this validation, however, I can't seem to figure out how to make only the "empty", so the fields that are not valid, take on the error message (.error). Right now it is recognising the ones that are invalid, and I'm getting the CSS connected to invalid (I am getting the red border around the input field), however, I am not getting through the html tag, which is a paragraph that goes underneath the input field.
function init() {
setUpBooking();
}
function setUpBooking(){
formValidation();
}
function formValidation() {
/* ------------ form & elements ----------- */
const form1 = document.querySelector(".availability");
window.form1 = form1;
const elements = form1.elements;
window.elements = elements;
/* --------- delete default validation ------- */
form1.setAttribute("novalidate", true);
/* ------------ custom validation ------------ */
document.querySelector(".next").addEventListener("click", (e) => {
e.preventDefault();
// 1. select all inputs
const formElements = form1.querySelectorAll("input, select");
/* ------------ date ------------ */
if (form1.checkValidity()) {
console.log("form is valid");
// loop through form elements and check if are valid or not
formElements.forEach((el) => {
if (el.checkValidity()) {
el.classList.add("valid");
}
// enable "next" btn when form is valid
var counter = 1, step = "step";
step = ".step" + counter;
if (counter <= 5) {
document.querySelector(step).classList.add("show");
}
counter++;
if (counter > 5) {
counter = 5;
}
step = ".step" + counter; // step is the class and we are appending counter with step so that it looks like the same class in the given class(like counter 1 means step1)
document.querySelector(step).classList.remove("show");
// enable "previous" btn when form is valid
document.querySelector(".previous").addEventListener('click', function () {
if (counter > 1) { // we don't want to remove the first step, it will always be shown
step = ".step" + counter;
document.querySelector(step).classList.add("show");
}
counter--;
if (counter < 1) {
counter = 1;
}
step = ".step" + counter;
document.querySelector(step).classList.remove("show");
});
});
} else {
formElements.forEach((el) => {
if (!el.checkValidity()) {
console.log("form is invalid");
el.classList.add("invalid");
document.querySelector(".error").style.display = "block";
} else {
el.classList.remove("invalid");
}
})
}
})
}
.valid {
border: 1px solid green;
}
.invalid {
border: 1px solid red;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.error {
text-transform: initial;
margin-bottom: 20px;
margin-top: -1px;
border: 1px solid red;
padding: 4px;
z-index: 10;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
display: none;
}
<!-- AVAILABILITY -->
<form class="availability step1">
<label for="date">Choose a date
<input type="date" required>
<p class="error date-err">Please pick a date for your tour</p>
<label for="number">Choose passengers
<input type="number" required>
<p class="error passengers-err">Please pick a number of passengers</p>
</label>
<!-- PERSONAL DATA -->
<form class="personalData step2">
</form>
<!-- ORDER OVERVIEW -->
<div class="orderOverview step3">
</div>
<!-- PAYMENT -->
<form class="payment step4">
</form>
<!-- buttons -->
<button class="previous hide">Previous</button>
<button class="next">Next</button>
Firstly, sorry. I've made some modifications to your forms.
Ideas are as follows:
Appended a span field in every form.
Before going to the next form just check every field is filled in the current form by passing the form number to the validateForm function and check all its input fields are filled.
If yes, return true else return false.
Have a look at the snippet below:
function validateForm(step) {
// console.log(document.forms[step - 1].elements);
var i, l = document.forms[step - 1].elements.length;
for (i = 0; i < l; i++) {
// console.log(document.forms[step - 1].elements[i].value);
if (!document.forms[step - 1].elements[i].value) {
// console.log("All fields should be filled");
document.getElementById("error" + step).textContent = "Fill all the fields please";
document.getElementById("error" + step).style.color = "red";
return false;
}
}
document.getElementById("error" + step).textContent = "Form is completed";
document.getElementById("error" + step).style.color = "green";
return true;
}
var counter = 1,
step = "step";
document.querySelector(".next").addEventListener('click', function() {
step = ".step" + counter;
if (validateForm(counter)) {
if (counter <= 5) {
document.querySelector(step).classList.add("show");
}
counter++;
if (counter > 5) {
counter = 5;
}
step = ".step" + counter; // step is the class and we are appending counter with step so that it looks like the same class in the given class(like counter 1 means step1)
//console.log(step);
document.querySelector(step).classList.remove("show");
}
});
document.querySelector(".previous").addEventListener('click', function() {
if (counter > 1) { // we don't want to remove the first step, it will always be shown
step = ".step" + counter;
//console.log(step);
document.querySelector(step).classList.add("show");
}
counter--;
if (counter < 1) {
counter = 1;
}
step = ".step" + counter;
document.querySelector(step).classList.remove("show");
});
.show {
display: none;
}
<!-- AVAILABILITY -->
<form name="availability" class="availability step1">
<h1>Step1</h1>
<label for="date">Choose a date</label>
<input type="date" name="DATE" required>
<label for="firstname">Enter a firstname</label>
<input type="text" name="FIRSTNAME" required>
<br/>
<span id="error1"> </span>
</form>
<!-- PERSONAL DATA -->
<form class="personalData step2 show">
<h1>Step2</h1>
<label for="date">Choose a date</label>
<input type="date" name="DATE" required>
<label for="firstname">Enter a firstname</label>
<input type="text" name="FIRSTNAME" required>
<br/>
<span id="error2"></span>
</form>
<!-- ORDER OVERVIEW -->
<div class="orderOverview step3 show">
<h1>Step3</h1>
<label for="date">Choose a date</label>
<input type="date" name="DATE" required>
<label for="firstname">Enter a firstname</label>
<input type="text" name="FIRSTNAME" required>
<br/>
<span id="error3"></span>
</div>
<!-- PAYMENT -->
<form class="payment step4 show">
<h1>Step4</h1>
<label for="date">Choose a date</label>
<input type="date" name="DATE" required>
<label for="firstname">Enter a firstname</label>
<input type="text" name="FIRSTNAME" required>
<br/>
<span id="error4"></span>
</form>
<!-- buttons -->
<button class="previous hide">Previous</button>
<button class="next">Next</button>
I'm trying to create a BMR calculation embedded in my site. I have it working except for the gender portion (if male vs female, different calculations)
I understand that I need an adlistener but doesn't seem to be working. I think I am not referencing it properly.
Here's my code:
var theForm = document.forms["RMRform"];
var bmr;
function RMRcalc() {
var weight = document.getElementById("weight").value;
var height = document.getElementById("height").value;
var age = document.getElementById("age").value;
var gender = document.getElementById("gender").value;
var rad = document.myForm.myRadios;
var prev = null;
for (var i = 0; i < rad.length; i++) {
rad[i].addEventListener('change', function() {
(prev) ? console.log(prev.value): null;
if (this !== prev) {
prev = this;
}
console.log(this.value)
});
}
var activitylevel = new Array();
activitylevel["sedentary"] = 1.2;
activitylevel["low"] = 1.3;
activitylevel["moderate"] = 1.5;
activitylevel["high"] = 1.7;
function getActivityLevel() {
var activityLevelamount = 0;
var theForm = document.forms["RMRform"];
var selectedActivity = theForm.elements["activity"];
activityLevelamount = activitylevel[selectedActivity.value];
return activityLevelamount;
}
if (i = this) {
bmr = ((10 * weight) + (6.25 * height) - (5 * age) - 161) * getActivityLevel();
} else {
bmr = ((10 * weight) + (6.25 * height) - (5 * age) + 5) * getActivityLevel();
}
}
document.getElementsByTagName("button")[0].addEventListener("click", function() {
RMRcalc();
document.getElementById('results').innerHTML = bmr;
})
body {
font: 15px gothic, sans-serif;
letter-spacing: 1px;
}
input {
width: 100%;
}
input[type=number] {
border: none;
border-bottom: 1px solid grey;
}
button[type=button] {
background-color: #ddcecb;
border: none;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
color: #95483e;
padding: 16px 32px;
text-decoration: none;
letter-spacing: 1px;
margin: 4px 2px;
}
input[type=text]:focus {
border: 1px solid #555;
}
.form-inline label {
margin: 5px 10px 5px 0;
}
#media (max-width: 800px) {
.form-inline input {
margin: 10px 0;
}
.form-inline {
flex-direction: column;
align-items: stretch;
}
<form action="" id="RMRform">
<label for='gender' class="inlinelabel">
Female </label>
<input type="radio" id="gender" name="gender" value="fem" checked onclick="RMRcalc()" /><br>
<label for='gender' class="inlinelabel">
Male </label>
<input type="radio" id="gender" name='gender' value="masc" onclick="RMRcalc()" /> <br> Weight (kg):<br>
<input type="number" id="weight"><br><br> Height (cm):<br>
<input type="number" id="height"><br><br> Age (years):<br>
<input type="number" id="age"><br><br> Activity Level:
<select id="activity" name="activity">
<option value="sedentary">sedentary (1-2x/week)</option>
<option value="low">low (2-3x/week)</option>
<option value="moderate">moderate (3-4x/week)</option>
<option value="high">high (5x/week)</option>
</select>
</form> <br>
<button type="button" onclick="">
calculate</button>
<p>Your Daily Estimated Requirements</p>
<div id="results"></div>
With this, there's just no calculation showing.
Thanks in advance!
A closing } is missing for the #media query.
There shouldn't be a variable declaration in your if condition: if (var i=this). Remove the var.
Both of your radio input have the same id="gender". Id's should be unique. Consider using a class instead. This will cause problems when you later use the gender variable for your if male vs female calculations because of this selector:
var gender = document.getElementById("gender").value;
For rad to be defined in your loop...
var rad = document.myForm.myRadios;
...you'll need to change myRadios to gender, because that's the name of your radio inputs. You'll also need to give your form the name myForm.
<form action="" id="RMRform" name="myForm">
Perhaps you're looking for something in this region...
function calc () {
const gender = document.querySelector('input[name="gender"]:checked').value,
weight = document.getElementById('weight').value,
height = document.getElementById('height').value,
age = document.getElementById('age').value,
activity = document.getElementById('activity').value;
// calculate base metric value
let metricBase = ((10 * weight) + (6.25 * height) - (5 * age));
// calculate per gender
metricBase = (gender === 'fem') ? metricBase - 161 : metricBase + 5;
// calculate with inline activity values
const bmr = Math.round( metricBase * parseFloat(activity) );
// format caloric intake output
document.getElementById('results').innerHTML = `${bmr.toLocaleString()} calories/day`;
}
// prevents form change listener until after first calculation
let firstCalc = true;
document.getElementById('calc').addEventListener('click', () => {
if (firstCalc) document.querySelector('form').onchange = calc;
firstCalc = false;
calc();
}, false);
body {
}
body, input {
font-family: Arial,"Helvetica Neue",Helvetica,sans-serif;
font-size: 14px;
letter-spacing: 1px;
}
input[type="radio"] {
display: inline-block;
width: 1rem;
}
input[type="number"] {
border: none;
border-bottom: 1px solid #ccc;
}
.group {
padding: .5rem 0;
}
<form>
<div class="group">
<label class="radio">
<input type="radio" name="gender" value="fem" checked />
Female
</label>
<label class="radio">
<input type="radio" name='gender' value="masc" />
Male
</label>
</div>
<div class="group">
<label for="weight">Weight (kg):</label>
<input type="number" id="weight" value="79" />
</div>
<div class="group">
<label for="weight">Height (cm):</label>
<input type="number" id="height" value="172" />
</div>
<div class="group">
<label for="weight">Age (years):</label>
<input type="number" id="age" value="22" />
</div>
<div class="group">
<label for="activity">Activity Level:</label>
<select id="activity">
<option value="1.2" selected>Sedentary (1-2x/week)</option>
<option value="1.3">Low (2-3x/week)</option>
<option value="1.5">Moderate (3-4x/week)</option>
<option value="1.7">High (5x/week)</option>
</select>
</div>
<button type="button" id="calc">Calculate</button>
</form>
<p id="results"></p>
Here is what i see (i am quite new to JS so i may not see everything) :
There is no opening head tag (not sure if that's a problem).
The gender radio buttons both have the same id. IDs should be unique. And because of this my guess is that this code should only give you the value for fem no ?:
html:
<input type="radio" id="gender" name="gender" value="fem" checked
onclick="RMRcalc()" />
<input type="radio" id="gender" name='gender' value="masc"
onclick="RMRcalc()" />
js:
var gender = document.getElementById("gender").value;
In your for-loops i would use let instead of var, otherwise you might get in trouble someday. Checkout this post.
for (let i = 0; i < rad.length; i++) {
rad[i].addEventListener('change', function () {
I didn't understand this in your function RMRcalc : if (var i = this)
Here is a somewhat simplified version of the code I am currently using:
https://jsfiddle.net/2zauty83/8/
Javascript
function checkboxlimit(checkgroup) {
var checkgroup = checkgroup
for (var i = 0; i < checkgroup.length; i++) {
checkgroup[i].onclick = function() {
var checkedcount = 0
for (var i = 0; i < checkgroup.length; i++)
checkedcount += (checkgroup[i].checked) ? 1 : 0
if (checkedcount > 2) {
this.checked = false
}
}
}
}
var sort_form = document.forms.sortus;
var sort_checkboxes = sort_form.elements['squarepick[]'];
checkboxlimit(sort_checkboxes);
CSS
input.square {
margin: 0;
padding: 0;
display: none;
}
.sorttile {
cursor: pointer;
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid #CCCCCC;
margin: 1px;
}
.sorttile:hover {
border: 2px solid #AAAAAA;
}
HTML
<form enctype='multipart/form-data' method='POST' id='sortus'>
<input id='1-1' class='square' type='checkbox' name='squarepick[]' value='1-1' />
<label class='sorttile' for='1-1' style='background-color:#FF0000;'>
<input type='hidden' name='1-1' value='#FF0000' />
</label>
<input id='1-2' class='square' type='checkbox' name='squarepick[]' value='1-2' />
<label class='sorttile' for='1-2' style='background-color:#FFFF00;'>
<input type='hidden' name='1-2' value='#FFFF00' />
</label>
<input id='1-3' class='square' type='checkbox' name='squarepick[]' value='1-3' />
<label class='sorttile' for='1-3' style='background-color:#00FF00;'>
<input type='hidden' name='1-3' value='#00FF00' />
</label>
<br/>
<input id='2-1' class='square' type='checkbox' name='squarepick[]' value='2-1' />
<label class='sorttile' for='2-1' style='background-color:#00FFFF;'>
<input type='hidden' name='2-1' value='#00FFFF' />
</label>
<input id='2-2' class='square' type='checkbox' name='squarepick[]' value='2-2' />
<label class='sorttile' for='2-2' style='background-color:#0000FF;'>
<input type='hidden' name='2-2' value='#0000FF' />
</label>
<input id='2-3' class='square' type='checkbox' name='squarepick[]' value='2-3' />
<label class='sorttile' for='2-3' style='background-color:#FF00FF;'>
<input type='hidden' name='2-3' value='#FF00FF' />
</label>
<br/>
<input id='3-1' class='square' type='checkbox' name='squarepick[]' value='3-1' />
<label class='sorttile' for='3-1' style='background-color:#000000;'>
<input type='hidden' name='3-1' value='#000000' />
</label>
<input id='3-2' class='square' type='checkbox' name='squarepick[]' value='3-2' />
<label class='sorttile' for='3-2' style='background-color:#999999;'>
<input type='hidden' name='3-2' value='#999999' />
</label>
<input id='3-3' class='square' type='checkbox' name='squarepick[]' value='3-3' />
<label class='sorttile' for='3-3' style='background-color:#FFFFFF;'>
<input type='hidden' name='3-3' value='#FFFFFF' />
</label>
</form>
Basically each colored square represents a checkbox, allowing you to select any two boxes. Additionally, each box has a hidden input field associated with it.
For simplified demonstration purposes, the demo uses "background-color", but my actual code uses "background-image".
What I would like to do is make it so that when two squares are picked, upon selecting the second box, the two boxes trade each other's "background-image" values, effectively making it appear like the two squares traded places.
In addition, however, I also need to swap the values of the hidden-type input fields.
Finally, the two checkboxes should be deselected, although that part is easy. What I'm having trouble with is the first two parts. I thought I had some idea of how to make it work, but my experiments haven't been working out (My knowledge of javascript is still fairly crude).
Any ideas on how to make this work?
The first thing you need to do is set up an event listener so you know when a box has been checked. The second thing you need to do is to keep track of what got checked. Finally, you want to perform a series of operations based on what was checked.
var formEl = document.getElementById("sortus");
// Track our checked boxes
var checked = [];
var swap = function () {
var boxA = checked[0];
var boxB = checked[1];
// Get hidden fields using checkbox ID
var hiddenA = document.getElementsByName(boxA.id)[0];
var hiddenB = document.getElementsByName(boxB.id)[0];
var boxAbackgroundImage = boxA.style.backgroundImage;
var hiddenAvalue = hiddenA.value;
// Switch images!
boxA.style.backgroundImage = boxB.style.backgroundImage;
boxB.style.backgroundImage = boxAbackgroundImage;
// Swap hidden input values
hiddenA.value = hiddenB.value;
hiddenB.value = hiddenAvalue;
};
var reset = function () {
for (var i = 0; i < checked.length; i++) {
checked[i].checked = false;
}
checked = [];
};
var isDuplicate = function (el) {
for (var i = 0; i < checked.length; i++) {
if (checked[i] === el) {
return true;
}
}
return false;
};
formEl.addEventListener("click", function (e) {
var el = e.target;
// Was it a checkbox clicked?
if (el.className == "square") {
// Is it already checked?
if (!isDuplicate(el)) {
checked.push(el);
if (checked.length === 2) {
swap();
reset();
}
}
}
});
I didn't test this code but I hope it gives you a clear sense of what you need to do.
Basic function for your above requirement can be found in the below link,
Fiddler Link
function checkboxlimit(checkgroup)
{
var checkgroup = checkgroup
for (var i = 0; i < checkgroup.length; i++) {
checkgroup[i].onclick = function()
{
var checkedcount = 0
for (var i = 0; i < checkgroup.length; i++)
checkedcount += (checkgroup[i].checked) ? 1 : 0
if (checkedcount > 2) {
this.checked = false
}
swapImage();
}
}
}
function swapImage()
{
var prevStyle="";
var prevId="";
for (var i = 0; i < sort_checkboxes.length; i++) {
if(sort_checkboxes[i].checked)
{
if(prevId!="")
{
document.getElementById(prevId).nextElementSibling.style.backgroundColor = sort_checkboxes[i].nextElementSibling.style.backgroundColor;
document.getElementById(prevId).nextElementSibling.children[0].value = sort_checkboxes[i].nextElementSibling.style.backgroundColor;
sort_checkboxes[i].nextElementSibling.style.backgroundColor = prevStyle;
sort_checkboxes[i].nextElementSibling.children[0].value = prevStyle;
prevStyle = "";
prevId = "";
}
else
{
prevId=sort_checkboxes[i].id;
prevStyle = sort_checkboxes[i].nextElementSibling.style.backgroundColor;
}
}
}
}
I am creating a review-control and using radiobutton for grade selection. I wrote some simple js to add a diffrent class when a radiobutton is checked.
The problem is that you can check every radio button, I want the user only to check one value. I writing this in javascript, but jQuery is welcome or a smartare solution.
Demo : http://jsfiddle.net/cbqt8/5/
HTML:
<div class="reviews">
<label class="input-check">
<input onchange="change_state(this)" type="radio" value="1" name="review[rating]" /> Bad
</label>
<label class="input-check">
<input onchange="change_state(this)" type="radio" value="2" name="review[rating]" /> Its okey
</label>
<label class="input-check">
<input onchange="change_state(this)" type="radio" value="3" name="review[rating]" /> Great
</label>
<label class="input-check">
<input onchange="change_state(this)" type="radio" value="4" name="review[rating]" /> Awesome
</label>
<label class="input-check">
<input onchange="change_state(this)" type="radio" value="5" name="review[rating]" />Super
</label>
</div>
JavaScript:
function change_state(obj) {
if (obj.checked) {
//if radiobutton is being checked, add a "checked" class
obj.parentNode.classList.add("checked");
}
else {
//else remove it
obj.parentNode.classList.remove("checked");
}
}
CSS:
/*reviews box*/
.reviews{
padding: 25px;
margin:0;
}
/*this is the style of an radio "button"*/
.input-check {
display: inline-block;
height:20px;
padding:5px 8px;
background:green;
width:90px;
color:white;
text-align: center;
}
/* This is the style for a radio "button" */
.input-check.checked{
background:red;
color:black;
font-weight:bold;
}
/*Hide the radiobutton*/
.input-check input {
display:none;
}
You got to remove the checked class for previously checked item.
jsFiddle: http://jsfiddle.net/P8jB8/
function change_state(obj) {
if (obj.checked) {
var checkedNodes = getElementsByClassName(document, "checked");
for (var i=0;i<checkedNodes.length;i++) {
checkedNodes[i].classList.remove("checked");
}
//if radiobutton is being checked, add a "checked" class
obj.parentNode.classList.add("checked");
}
else {
//else remove it
obj.parentNode.classList.remove("checked");
}
}
function getElementsByClassName(node,classname) {
if (node.getElementsByClassName) { // use native implementation if available
return node.getElementsByClassName(classname);
} else {
return (function getElementsByClass(searchClass,node) {
if ( node == null )
node = document;
var classElements = [],
els = node.getElementsByTagName("*"),
elsLen = els.length,
pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)"), i, j;
for (i = 0, j = 0; i < elsLen; i++) {
if ( pattern.test(els[i].className) ) {
classElements[j] = els[i];
j++;
}
}
return classElements;
})(classname, node);
}
}