I have the following problem,
I have made a form with alot of input fields. Where if the user types a 1 it will show the price immediately next to it. My code for this is this:
var priceMargherita = 7;
var numPizzaMargheritaInput = document.getElementById('countMargherita');
var priceMargheritaLabel = document.getElementById('totalMargherita');
function onNumPizzaMargheritaInputChange(e){
var totalMargherita = priceMargherita * parseInt(e.target.value);
var formattedPrice = '\u20ac '+totalMargherita.toFixed(2);
priceMargheritaLabel.innerHTML = '';
priceMargheritaLabel.appendChild(document.createTextNode(formattedPrice));
}
numPizzaMargheritaInput.addEventListener('change', onNumPizzaMargheritaInputChange, false);
and it places the price like this:
<td>Pizza Margherita</td>
<td><input type="number" id="countMargherita" class="formnumbers" name="PizzaMargherita" onChange="validateForm(this)" min="1" max="99"/></td>
<td><span id="totalMargherita"></span></td>
Now my problem is that i have 11 products like this Pizza Margherita. And at the moment i have 11 pieces of code like the top one. I think this can be done in an array since the only things that change are some names.
Correct me if i'm wrong since im nowhere experienced in JS nor Arrays.
Any help is greatly appreciated!
For me, the simplest way to do it is to NOT use arrays or ids.
If you can change the html, you should add the price as a data-attribute, and then you can have a generic code :
<td>Pizza Margherita</td>
<td><input type="number" class="formnumbers" name="PizzaMargherita"
onChange="changeTotalFromCount(this)" min="1"
max="99" data-unitPrice="7" /></td>
<td></td>
JS :
function changeTotalFromCount(input) {
var unitPrice = parseFloat(input.getAttribute("data-unitPrice"));
var count = input.value;
var price = unitPrice * count;
var formattedPrice = '\u20ac ' + price.toFixed(2);
var label = input.parentNode.nextElementSibling;
label.innerHTML = '';
label.appendChild(document.createTextNode(formattedPrice));
}
Rather than arrays, I think the word you're looking for is loops. Here is a way to factor your program using an object and a loop:
var prices = {
Margherita: 7,
Hawaiian: 7,
NewYork: 8
};
for (var pizzaType in prices) {
var inputElement = document.getElementbyId('count' + pizzaType);
var labelElement = document.getElementbyId('total' + pizzaType);
inputElement.addEventListener('change', function(event) {
var total = prices[pizzaType] * parseInt(event.target.value);
var formattedPrice = '\u20ac ' + total.toFixed(2);
labelElement.innerHTML = '';
labelElement.appendChild(document.createTextNode(formattedPrice));
}, false);
}
I chose to use an anonymous function instead of a named one, as this is the most common practice with event listeners.
Make a more generic function for calculating your price, like onNumPizzChange which takes the base element name, e.g. 'Margherita'. In this function, extract your element like
var inputElm = document.getElementById('count'+elmBase);
then set up your events by wrapping this function with another like so:
document.getElementById('numPizzaMargheritaCount').addEventListener('change', function(){ onNumPizzaChange('Margherita')}, false);
Does that make sense?
Good luck!
An array isn't the most efficient way to solve the problem. You'd be better off with a more generalised function that has the necessary information (the event, the price, and the ID of the element to display the price) as parameters. Then use an array of objects to initialise everything. Something like this:
var pizzas = [
{
pizza: 'margherita',
price: 7,
input: 'countMargherita',
label: 'totalMargherita'
},
{
pizza: 'pepperoni',
price: 10,
input: 'countPepperoni',
label: 'totalPepperoni'
}
]
function calculatePrice(e, price, label) {
var total = price * parseInt(e.target.value);
var formattedPrice = '\u20ac '+total.toFixed(2);
var outputElement = document.getElementById(label);
outputElement .innerHTML = '';
outputElement .appendChild(document.createTextNode(formattedPrice));
}
Then iterate over that pizzas array to bind everything up:
for(var i = 0, l = pizzas.length; i < l; i++) {
(function(pizza) {
document.getElementById(pizza.input).addEventListener('change', function(e) {
calculatePrice(e, pizza.price, pizza.label);
}, false);
})(pizzas[i]);
}
The above uses a closure to avoid issues due to JavaScript's variable scoping; i is scoped to the function scope since there isn't block-level scoping. Since the event handler is essentially a deferred action, the value of i when that executes will also be the last value of i if you don't use one.
Related
I have small system that needs to track sales. I have a button with a JS function called incrementValue(), this one does a value++ and works fine. But I need to have a function called that takes the price field from JSON and adds that price by the click of the same button (incrementValueRevenue()). The button works fine the with the first click, but after that the incrementValueRevenue function stops.
So for this example when you've clicked the button three times, incrementValue() will show 3 and incrementValueRevenue() should show 105.
This is what i have:
function incrementValue() {
var value = document.getElementById('items-sold').value;
value++;
document.getElementById('items-sold').value = value;
}
function incrementValueRevenue() {
var myJSON = '{ "price":"35" }';
var myObj = JSON.parse(myJSON);
document.getElementById('total-revenue').value = myObj.price;
myObj.price += myObj.price;
}
This is my HTML
<form>
<input type="button" onclick="incrementValue(); incrementValueRevenue();" value="+" />
</form>
Thanks in advance.
PS. Yes, I am very rookie, and I have not been able to find a working solution anywhere.
Do it in only one function, then multiply the total revenue by the items sold:
function incrementValueAndRevenue() {
var value = document.getElementById("items-sold").value;
value++;
document.getElementById("items-sold").value = value;
var myJSON = { price: 35 };
var revenue = myJSON.price * value;
document.getElementById("total-revenue").value = revenue;
}
You initiate myObj on every incrementValueRevenue call so the value is always reset.
You can declare the variable outside the function scope so it is initialized once and have another value parsing the JSON!
var myObj = {};
myObj.price = 0;
function incrementValueRevenue() {
var myJSON = '{ "price":"35" }';
var myObjPrice = JSON.parse(myJSON);
document.getElementById('total-revenue').value = myObj.price;
myObj.price += myObjPrice.price;
}
function incrementValueRevenue() {
var myJSON = '{ "price":35 }';
var myObj = JSON.parse(myJSON);
let revenue = document.getElementById('total-revenue');
revenue.value = +revenue.value + myObj.price
}
First of all your price should by a number, later we take the total-revenue input and store it in variable, to add number to input value we need to convert that input value to number first ( if you dont do it the out would be "35353535...") by using "+" operator.
I have an object with randomly generated data:
var obj = {
price: getRandomNumberRange(10, 200),
rooms: getRandomNumberRange(1, 5)
};
where
var getRandomNumberRange = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
};
I keep trying and failing to write a function, which will allow to create an array of 4 objects, so each objects' data will be regenerated at the time of creation. My function:
var createArr = function () {
var arr = [];
for (var i = 0; i < 5; i++) {
arr.push(obj);
}
return arr;
};
In other words I expect to get this kind of array:
var arr = [{price:40, rooms:2}, {price:23, rooms:4}, {price:99, rooms:2}, {price:191, rooms:3}];
But I keep getting this:
var arr = [{price:40, rooms:2}, {price:40, rooms:2}, {price:40, rooms:2}, {price:40, rooms:2}];
Will appreciate your help!
UPD. Thanks to everyone who suggested more complex way to solve my problem. As soon as I progress in JS I will recheck this thread again!
It looks like you reuse the object for pushing over and over and because of the same object, it pushes the same object reference with the latest values.
To overcome this, you need to create a new object for each loop and assign the random values with short hand properties.
// inside the loop
let price = getRandomNumberRange(10, 200),
rooms = getRandomNumberRange(1, 5);
array.push({ price, rooms }); // short hand properties
I want to fetch only image urls from the concat values of price and pic url. But when i select checkbox with different images still it print first selected image url all the time.
when i try to print split value all price and image urls are displayed in an array in [price,imageurl]form i.e[5,imgurl,7,...] but when i try to print res[1] index wise only single image url comes all the time even if i select different pictures from checkbox
<input name="product" type="checkbox" class="checkbox" id="product" value="<%=price+"&"+photo_url+"&"%>" onclick="totalIt()" />
function totalIt() {
var input = document.getElementsByName("product");
total=0;
var count=0;
for (var i = 0; i < input.length; i++) {
if (input[i].checked) {
total += parseInt(input[i].value);
totalpicscost+=input[i].value;
var f=totalvaluepics.toString();
res = totalpicscost.split("&");
count++;
}
//console.log("nearestvalue"+res[res.length-1]);
}
document.getElementById("pics").value = count;
document.getElementById("total").value = "Rs " + total.toFixed(2);
totalvaluepics= [totalpicscost];
console(res[0]);
}
I expect output to print all selected pictures url not same image url displaying all the time i select checkbox.
It was quite not easy to understand your question, neither to read your code, please do not consider it to be offensive, but you need to work a bit more on JS styleguides and good practices to get rid of bad ones.
Regarding your question (as far as I understood it):
function totalIt() {
// Get all inputs
const inputs = document.querySelectorAll('[name="product"]');
// Filter out inputs that are checked
// and map them according to "splitValueIntoPriceAndUrl()" function declared below
// so the data we need looks like
// [{price: 5, imageUrl:'imgurl'}, {price: 7, imageUrl:'imgurl2'}, ...]
let checkedInputsValues = [ ...inputs ].filter(i => i.checked).map(splitValueIntoPriceAndUrl);
// as far as I understood from given example,
// you want to store the count of selected pics in #pics (and why is it an input??)
document.querySelector('#pics').value = checkedInputsValues.length;
// and also as far as I understood from given example,
// you want to store total price into another input#total value,
// so we reduce our checkedInputsWalues array in a way
// to calculate all prices together
const total = checkedInputsValues.reduce((totalPrice, { price }) => totalPrice += price, 0).toFixed(2)
document.querySelector('#total').value = `Rs ${total}`;
// and now we are collecting the image urls in res array.
// now res[] values should be logged (at least).
const res = checkedInputsValues.map(({ imageUrl }) => imageUrl);
for (let i = 0; i < res.length; i++) {
console.log(res[i]);
}
// remapper helper
function splitValueIntoPriceAndUrl(checkbox) {
return {
price: parseInt(checkbox.value.split('&')[0]),
imageUrl: checkbox.value.split('&')[1]
}
}
return res;
}
Regarding your codestyle and practices:
You've declared some variables you had never use: var inputVal = document.getElementById("second");, var f=totalvaluepics.toString();. Don't do that.
Don't assign values to undeclared variables.
Switch to ES6 and functional methods of arrays/collections - it is helpful.
Consider the following Bacon.js code sample (loosely based on the code here, uses bacon.model & bacon.jquery):
<input id="total" type="text" placeholder="total">
/
<input id="quantity" type="text" placeholder="quantity" value="1">
=
<span id="price"></span>
<script>
var $total = Bacon.$.textFieldValue($("#total")).changes();
var quantity = Bacon.$.textFieldValue($("#quantity"));
var price = Bacon.when(
[$total, quantity], function(x,y) { return x/y; }
).toProperty(0);
price.onValue(function (p) {
$('#price').text(p);
});
</script>
What happens here is that the stream $total and the property quantity are joined into a property price. Both $total and quantity get their input from a text input field, but only $total prompts price to be updated. (I.e. quantity is sampled but not part of the synchronization pattern.)
I am trying to achieve this same behavior using RxJS instead of Bacon.js, but all I can come up with is super ugly solutions. I reckon I must be overlooking something…?
My best shot so far (behavior not identical, but you get the idea):
var totalChange = Rx.Observable.fromEvent($('#total'), 'input');
var totalValue = totalChange.map(function () {
return parseInt($('#total').val(), 10);
});
var quantityValue = totalChange.map(function () {
return parseInt($('#quantity').val(), 10);
});
var price = Rx.Observable.zip(
totalValue,
quantityValue,
function (x,y) {
return x/y;
}
);
price.subscribe(function (p) {
$('#price').text(p);
});
Because neither combineLatest or zip offer the desired behavior on observables that represent the two text fields directly, all I can think of is to create an observable for quantity based on input events in the text field for total. Which works, but it feels pretty far-fetched.
Any suggestions for alternative approaches?
Use the recently added withLatestFrom instead of combineLatest.
var price = totalValue.withLatestFrom(quantityValue, (x,y) => x/y);
Compare their diagrams:
http://rxmarbles.com/#combineLatest
http://rxmarbles.com/#withLatestFrom
http://rxmarbles.com/#zip
EDIT: combineLatest seems to be the equivalent of bacon's when.
I think maybe you're looking to do something like this?
var totalChange = Rx.Observable.fromEvent($('#total'), 'input');
var totalValue = totalChange.map(function () {
return parseInt($('#total').val(), 10);
});
var quantityValue = totalChange.map(function () {
return parseInt($('#quantity').val(), 10);
});
Observable.combineLatest(totalValue, quantityValue, function(total, quantity) {
return total / quantity;
});
Unfortunately, that's the prettiest thing I can think of to answer the spirit of your question.
In reality though, given the example, you'd just do this:
var price = totalChange.map(function() {
return parseInt($('#total').val(), 10) / parseInt($('#quantity').val(), 10);
});
Edit: I just realized this solution is at the bottom of Ben Lesh's answer. I'll leave this here just for the wordier explanation.
If a variable isn't reactive, sometimes the simplest solution is to just acknowledge that and not try to react to it.
Since quantity is not observable, any attempts to make it observable and then combining 2 observable streams is just going to feel hackish.
Instead I'd just embrace its unobservable-ness and use map to read it when total changes:
var totalChange = Rx.Observable.fromEvent($('#total'), 'input');
var totalValue = totalChange.map(function () {
return parseInt($('#total').val(), 10);
});
var price = totalValue.map(function (total) {
var quantity = parseInt($('#quantity').val(), 10);
return total / quantity;
});
price.subscribe(function (p) {
$('#price').text(p);
});
// or, more succinctly with a single map operation:
var totalChange = Rx.Observable.fromEvent($('#total'), 'input');
var price = totalChange.map(function () {
var total = parseInt($('#total').val(), 10);
var quantity = parseInt($('#quantity').val(), 10);
return total / quantity;
});
price.subscribe(function (p) {
$('#price').text(p);
});
I am attempting to enter in 3 number input fields in my HTML, listed below:
HTML File-
<label for="num1">Enter number 1.</label><input type="text" size="20" id="num1">
<label for="num2">Enter number 2.</label><input type="text" size="20" id="num2">
<label for="num3">Enter number 3.</label><input type="text" size="20" id="num3">
<div id="greatestbutton" class="button">Click here to get the Greatest Number!</div>
<div>The greatest number is <span id="num1 || num2 || num3"></span></div>
Once these number have been entered, I want to ensure that they are indeed numbers and not letters and I wanted to take the greatest of those that have been entered:
JS File-
var button = document.getElementById("greatestbutton");
button.onclick = function greaterNumber(num1, num2, num3) {
var a = parseFloat(num1);
var b = parseFloat(num2);
var c = parseFloat(num3);
var greatest = Math.max(a, b, c);
return greatest;
}
}
I can 'see' the 'button' accept the click, but I am unable to get it to return anything, let alone the greatest number.
Any help for this newbie would be greatly appreciated!!
Thanks!!!
Change the result element's id first:
<div>The greatest number is <span id="result"></span></div>
Then modify the button click function a little:
var button = document.getElementById("greatestbutton");
button.onclick = function() {
var num1 = document.getElementById('num1').value; // get value from inputs
var num2 = document.getElementById('num2').value;
var num3 = document.getElementById('num3').value;
var a = parseFloat(num1);
var b = parseFloat(num2);
var c = parseFloat(num3);
var greatest = Math.max(a, b, c);
document.getElementById('result').innerHTML = greatest; // set value for result
}
You aren't passing any values to the function greaterNumber, it doesn't just take values automatically. In this case you want to give it the values from the input fields, you can do that in a lot of ways, one of them being:
var button = document.getElementById("greatestbutton");
button.onclick = function greaterNumber() {
var a = parseFloat(document.getElementById('num1').value); // get value
var b = parseFloat(document.getElementById('num2').value);
var c = parseFloat(document.getElementById('num3').value);
var greatest = Math.max(a, b, c);
return greatest;
}
return simply returns the value to whatever you call it from, in this fiddle i used alert instead just to prove that it works http://jsfiddle.net/NMys3/
A couple of points:
1) You seem to be assuming that the form's input values will be passed automatically as arguments to the event callback. They will not - all that's passed to the event callback is the event object itself. You need to grab those values manually, yourself.
2) You can't merely return the value from the event callback - you need to do something with it.
3) You don't say what should happen if one or more values are not numbers; presumably a message should appear or something, which is what I've assumed.
4) Not sure what you meant by that weird id attribute on the span; it seems like you've made a lot of assumptions about how code works here. Give it an ID of something like "max_num".
Try:
document.querySelector('#greatestbutton').addEventListener('click', function() {
var num_nums = 3, nums = [];
for (var i=0; i<num_nums; i++) {
var val = document.querySelector('#num'+(i+1)).value;
if (!parseFloat(val)) { alert('bad input!'); return false; }
nums.push(val);
}
document.querySelector('#max_num').innerHTML = Math.max.apply(null, nums);
}, false);
Note I have also...
1) Modernised the code slightly; it's better to register events using addEventListener (for reasons that are beyond the scope of this question) and I've used the ECMA5 jQuery-like method querySelector, which finds elements by CSS-style syntax
2) Made it dynamic for N numbers; your code is hard-coded to three, and requires more lines of code should this number be increased at any point