How to fetch all image URL when checkbox is clicked in Javascript - javascript

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.

Related

Compare user input with csv file, return second column value javascript

I came across the following topic, it just has 1 line instead of 2 columns.
How do I return the second value here (see topic below)
Compare my variable with a csv file and get the matching value in javascript
This is my CSV file values:
csv screenshot of columns
This is what I have currently
IT just checks the file for the serial number from the user and marks the div with text "Valid".
This Valid should have the second Columns value.
<script>
const checkm = document.getElementById('check');
checkm.addEventListener('click', serialChecker)
async function serialChecker(){
const url = 'http://localhost/validator/serials.csv';
const response = await fetch(url);
// wait for the request to be completed
const serialdata = await response.text();
console.log(serialdata);
const inputserialnumber = document.getElementById('serialnumber').value.toString();
console.log(inputserialnumber);
// serialdata.match(/inputserialnumber/)
// serialdata.includes(inputserialnumber)
if(serialdata.includes(inputserialnumber) == true && inputserialnumber.length == 7 ){
document.getElementById('validity').innerHTML = "Valid";
startConfetti(); // from confetti.js
}else {
document.getElementById('validity').innerHTML = "Invalid";
stopConfetti(); // from confetti.js
}
//document.getElementById('test').innerHTML = "Valid";
}
</script>
This is my console output
It shows the full csv(currently), & the users input
changed the csv data into to different arrays if that helps:
array
& Thanks all in advance for taking the time to reply to my silly question!
EXTRA Clarification:
What I'm trying to do is a validate website checker.
So the user inputs their serial through an simple input field. & I have the serials in a csv file with an extra column that has the name matching to the serial.
So if the user inputs 1234567 it is present in the CSV file, my current code returns value = true for that. as it is present in the CSV file.
But I want it to return the value next to 1234567 (so in the second Column) instead, in this case "test1". So I can use that value instead of just a standard "Valid" text to be pushed back onto the website.
You can match values of two arrays by their index. In your case, I think it's easiest to use Array.map() to return a transformed array based on the one you loop trough. So for example, if you have two arrays called namesArray and valuesArray, do the following:
const validationResults = valuesArray.map((value, index) => {
return {
valid: checkValidity(value), // or whatever your validation function is called
name: namesArray[index] // match the index of the namesArray with the index of this one (valuesArray)
};
// or `return namesArray[index] + ', valid: ' + checkValidity(value)`
});
This loops through the valuesArray, and validationResults will then be an array of what you return per each item in the map function above.
One important note is that this assumes the arrays are both in the same order . If you want to sort them, for instance, do this after this.
Looking up and registering the values in a Map seems like the best answer.
// ...
const serialdata = await response.text();
const seriallookup = new Map();
// Set all Serial values to Names
for (let s in serialdata.split("\n")) {
let data = s.split(',');
seriallookup.set(data[0], data[1]);
}
Using this, checking for a serial's existance could be done with .has()
if (inputserialnumber.length == 7 && seriallookup.has(inputserialnumber)) {
And set to the elements text using
document.getElementById('validity').innerHTML = serialdata.get(inputserialnumber);
If the .csv file most likely wouldn't change between multiple requests (or if you only send just one request), you should probably initialize and request the data outside of the function.
Thank you all for the feedback.
I have not been able to use your suggestions exactly as intended.
But I managed to combine the idea's and create a new piece that does the trick for me!
const checkm = document.getElementById('check');
checkm.addEventListener('click', serialChecker)
async function serialChecker(){
const url = 'http://localhost/validator2/naamloos.csv';
const response = await fetch(url);
// wait for the request to be completed
const serialdata = await response.text();
const table = serialdata.split('\r\n');
const serialsArray = [];
const nameArray = [];
table.forEach(row =>{
const column = row.split(',');
const sArray = column[0];
const nArray = column[1];
serialsArray.push(sArray);
nameArray.push(nArray);
})
var array1 = serialsArray,
array2 = nameArray,
result = [],
i, l = Math.min(array1.length, array2.length);
for (i = 0; i < l; i++) {
result.push(array1[i], array2[i]);
}
result.push(...array1.slice(l), ...array2.slice(l));
function testfunction(array, variable){
var varindex = array.indexOf(variable)
return array[varindex+1]
}
//calling the function + userinput for serial
const inputserialnumber = document.getElementById('serialnumber').value.toString();
console.log(testfunction(result, inputserialnumber))
if(serialsArray.includes(inputserialnumber) == true && inputserialnumber.length == 7 ){
document.getElementById('validity').innerHTML = "Valid " + testfunction(result, inputserialnumber);
startConfetti();
}else {
document.getElementById('validity').innerHTML = "Invalid";
stopConfetti();
}
}
Hope this can help someone out in having an input field on their website with a .csv file in the backend (possible to have multiple for the user to select with a dropdown box with the async function).
This will check the file & will return the value from the csv that matches the serial!(based on serial number & length of the serial number(7characters))

How can I use Javascript to gather prices on my page to add to total?

I have a product page that recently had a product add-on tool made for it. I am modifying the code to change it's format a bit. There is a "add to cart" button bar that displays the total price before adding to cart.
I used .innerHTML to match the price of the top of the page for consistency, but I am trying to include the price of the product addons once selected.
Current code for bottom bar:
var selector1 = ".top-bar";
var el1 = document.querySelector(selector1);
var selector2 = ".bottom-bar";
var el2 = document.querySelector(selector2);
var totalPriceSelected = '';
function getPrice() {
el2.innerHTML = el1.innerHTML;
};
document.addEventListener('variant:priceChange', ()=>{
getPrice();
});
The add-on tool is a vue app which I am a little new to. I managed to get to the point that I can get the addons to print the prices to the console once selected:
watch: {
selected: function(new_value) {
setTimeout(function(){
var priceSelected = document.querySelectorAll(".price_selected");
var strippedPriceSelected = priceSelected;
for(var i=0;i<strippedPriceSelected.length;i++){
var totalPriceSelected = priceSelected[i].innerHTML;
console.log(totalPriceSelected);
}
}, 10);
this.product.selected = new_value;
//this.$emit('selected', this.product, new_value);
}
Currently, once the add-on products are selected, the vue adds the class of ".price_selected" to the price element once it is selected. The code above looks for ".price_selected", saves it to the variable totalPriceSelected, then prints it to the console.
I am wondering how I can add all of the addon price values (once selected) to the total price in the add to cart bar. I'd imagine I'd have to convert all the prices to numbers instead of strings and add them together, but I'm not quite sure how to do that.
Product page for reference
UPDATE
watch: {
selected: function(new_value) {
setTimeout(function(){
var priceTotal = 0
var priceSelected = document.querySelectorAll(".price_selected");
var strippedPriceSelected = priceSelected;
for(var i = 0; i < strippedPriceSelected.length; i++){
var totalPriceSelected = priceSelected[i].innerHTML; // '+$ 85.00'
var priceNumber = parseFloat(totalPriceSelected.split(' ')[1]);
priceTotal += priceNumber;
}
var currentBottomPrice = parseFloat(el2.innerHTML);
el2.innerHTML = currentBottomPrice += parseFloat(priceTotal);
console.log(priceTotal);
}, 10);
this.product.selected = new_value;
//this.$emit('selected', this.product, new_value);
}
},
So I have this now which is adding them correctly, but when the value of priceTotal goes back to 0 (add-ons are deselected) the total stays the same because it added the value and it is tr
Yeah so you can split the price string into two parts on the space, then parse the second part as a float (decimal number). Now you can do any math you need to it.
setTimeout(function() {
var priceSelected = document.querySelectorAll(".price_selected");
var strippedPriceSelected = priceSelected;
for(var i = 0; i < strippedPriceSelected.length; i++){
var totalPriceSelected = priceSelected[i].innerHTML; // '+$ 85.00'
var priceNumber = parseFloat(totalPriceSelected.split(' ')[1]);
console.log(priceNumber) // Prints 85 as number
}
}, 10);
Let me know if you have any questions or it doesn't work, I'd be happy to help!
We shouldn't just seek to have a working piece of code that solves a minor issue but could pose a major functionality break later.
I see potential pitfalls around the approach used here:
a) converting from string to number (using Number/parseFloat) could pose issues if the string contains invalid characters. For e.g., if price is from the add-on is displayed as '+$85.00' (notice the missing space after $ symbol), totalPriceSelected.split(' ')[1] will return undefined. Adding undefined to a number will give you NaN. Or, what if the add-on starts displaying the price as '85.00 $'. The approach used above would fail again.
b) What if the vue component updates the CSS class for prices to something else, say amount_selected? Such a change would be beyond your control but has a direct impact on the code/logic that you would implement to display the total price. Needless to say, when it comes to dealing with showing/requesting prices from users, we should be extremely careful about it.
There could be more such cases and we should look for a solution that is more robust.

Creating a google form/quiz from a google spreadsheet

I am trying to create a multiple choice question form to be created from data in a google spreadsheet. I managed to create the form of 60 questions each with 4 choices and setting the correct choice based on the information I have in the spreadsheet.
Last thing I need to do is to insert the correct feedback for each question based on column G in my spreadsheet that contains the feedback for each question.
Edit: here is a picture of how my spreadsheet & form would look like
Picture for Spreadsheet
Picture for how the form questions should look like
Picture of how the form questions look like (without a feedback)
The problem is that is not being implemented, The maximum I could was to set a fixed feedback/word for all questions, but was not possible to import the specific feedback for each question to the feedback section of each question, could anyone help with that, below is my code:
function popForm() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Sheet1');
var numberRows = sheet.getDataRange().getNumRows();
var myQuestions = sheet.getRange(1,1,numberRows,1).getValues();
var myAnswers = sheet.getRange(1,2,numberRows,1).getValues();
var myGuesses = sheet.getRange(1,2,numberRows,4).getValues();
var myfeedback = sheet.getRange(1,7,numberRows,1).getValues();
var myShuffled = myGuesses.map(shuffleEachRow);
Logger.log(myShuffled);
Logger.log(myAnswers);
// Create the form as a quiz. The resulting form's "Quiz options" are different from a manually created quiz. Be aware (and change manually if needed!
var form = FormApp.create('Fast Track Question - Domain I');
form.setIsQuiz(true);
// Write out each multiple choice question to the form.
for(var i=0;i<numberRows;i++){
if (myShuffled[i][0] == myAnswers[i][0]) {
var addItem = form.addMultipleChoiceItem();
addItem.setTitle(myQuestions[i][0])
.setPoints(1)
.setChoices([
addItem.createChoice(myShuffled[i][0],true),
addItem.createChoice(myShuffled[i][1]),
addItem.createChoice(myShuffled[i][2]),
addItem.createChoice(myShuffled[i][3])
]);
var incorrectFeedback = FormApp.createFeedback()
.setText(myfeedback[i][7])
.build();
addItem.setFeedbackForIncorrect(incorrectFeedback);
}
else if (myShuffled[i][1] == myAnswers[i][0]) {
var addItem = form.addMultipleChoiceItem();
addItem.setTitle(myQuestions[i][0])
.setPoints(1)
.setChoices([
addItem.createChoice(myShuffled[i][0]),
addItem.createChoice(myShuffled[i][1],true),
addItem.createChoice(myShuffled[i][2]),
addItem.createChoice(myShuffled[i][3])
]);
var incorrectFeedback = FormApp.createFeedback()
.setText(myfeedback[i][7])
.build();
addItem.setFeedbackForIncorrect(incorrectFeedback);
}
else if (myShuffled[i][2] == myAnswers[i][0]) {
var addItem = form.addMultipleChoiceItem();
addItem.setTitle(myQuestions[i][0])
.setPoints(1)
.setChoices([
addItem.createChoice(myShuffled[i][0]),
addItem.createChoice(myShuffled[i][1]),
addItem.createChoice(myShuffled[i][2],true),
addItem.createChoice(myShuffled[i][3])
]);
var incorrectFeedback = FormApp.createFeedback()
.setText(myfeedback[i][7])
.build();
addItem.setFeedbackForIncorrect(incorrectFeedback);
}
else if (myShuffled[i][3] == myAnswers[i][0]) {
var addItem = form.addMultipleChoiceItem();
addItem.setTitle(myQuestions[i][0])
.setPoints(1)
.setChoices([
addItem.createChoice(myShuffled[i][0]),
addItem.createChoice(myShuffled[i][1]),
addItem.createChoice(myShuffled[i][2]),
addItem.createChoice(myShuffled[i][3],true)
]);
var incorrectFeedback = FormApp.createFeedback()
.setText(myfeedback[i][7])
.build();
addItem.setFeedbackForIncorrect(incorrectFeedback);
}
}
}
// This function, called by popForm, shuffles the 5 choices.
function shuffleEachRow(array) {
var i, j, temp;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
Proposed change to script
Your code was long and I found it easier to re-write it with a few extra tools such as getDataRange, push and splice and forEach.
It seemed you were calling the methods in the right way, but since you were having to repeat yourself in a few places and keep track of many arrays and indices, it is likely that a small mistake came up.
This is a working script adapted from yours:
function createQuiz() {
let file = SpreadsheetApp.getActive();
let sheet = file.getSheetByName("Sheet1");
// Instead of getting individual ranges, it is more efficient
// to get all the data in one go, and then operate on the two
// dimensional array in memory.
let range = sheet.getDataRange();
let values = range.getValues();
// Here I am using a existing form to test, but you can just
// create a new one if you want.
var form = FormApp.openById("[TESTING_ID]");
form.setIsQuiz(true);
values.shift(); // Using this to remove the first row of headers
// Going through each line using a forEach to create a
// multiple choice question
values.forEach(q => {
let choices = [q[1], q[2], q[3], q[4]];
let title = q[0];
let feedback = q[5]
// Calling function to create multiple choice question
createShuffledChoices(form, title, choices, feedback)
});
}
function createShuffledChoices(form, title, choices, feedback){
let item = form.addMultipleChoiceItem();
item.setTitle(title)
.setPoints(1)
// Setting up the array that will be passed into item.setChoices()
let shuffledChoices = [];
// Making sure that the correct answer is only marked once
let correctAnswerChosen = false;
// I found I had to shuffle the questions within the process of
// creating choices as it made it easier to maintain the spreadsheet
for (let i = choices.length; i != 0; i--) {
let rand = Math.floor(Math.random() * (i - 1));
// If the first answer is chosen, it is the correct one.
if (rand == 0 && correctAnswerChosen == false) {
// Combination of push and splice to remove from ordered array
// to the shuffled one
shuffledChoices.push(item.createChoice(choices.splice(rand, 1)[0], true));
// Marking the correct answer as chosen,
// so that no others are marked correct.
correctAnswerChosen = true;
} else {
shuffledChoices.push(item.createChoice(choices.splice(rand, 1)[0]));
}
}
// Finally setting the choices.
item.setChoices(shuffledChoices);
// Creating the feedback
let formFeedback = FormApp.createFeedback().setText(feedback).build();
item.setFeedbackForIncorrect(formFeedback);
}
The way that you were creating feedback was correct, I suspect that you were just getting mixed up with your arrays and indexes. This is why I tried to simplify your code and eliminate repeated sections.
I combined the shuffling process with the creation of the multiple choice question. This is because the shuffled array that is passed into item.setChoices has to be built of item.createChoice objects. This can't be done in another scope because item is not available.
Combining the logic for shuffling this way means that you don't need to have the letter prefixes in your questions A). You also don't need the column that has the correct answer, because the process knows that the first answer is the correct one. So your sheet can be simplified to this:
For this script to work, the data needs to be organized in this way. (Though you can adapt it anyway you like of course)
References
getDataRange
push
splice
shift
forEach

How do I calculate the sum of numbers stored in LocalStorage?

I am new to JSON, so bear with me!
I am working on a website that stores values to LocalStorage via inputs. Each form input has the following function (only difference is formInput2, formInput3)
function formInput(e) {
// Save userInput from input
// Get form values
var input = document.querySelector('.input').value;
this.style.visibility = 'hidden';
smtBtn.style.display = 'inline-block'
var userInput = {
answer: input
}
// Test if bookmark is null
if (localStorage.getItem('bookmarks') === null) {
// Init Array
var bookmarks = [];
// Add to array
bookmarks.push(userInput);
// Set to LocalStorage
localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
} else {
// Get Bookmarks from LocalStorage
var bookmarks = JSON.parse(localStorage.getItem('bookmarks'));
// Add bookmark to array
bookmarks.push(userInput);
// Reset back to LocalStorage
localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
}
// Refetch bookmarks
fetchBookmarks();
// Prevent form from submitting
e.preventDefault();
}
I need to add the three numbers that are in local storage, and I am using this function:
function bookmarkMath() {
var bm1 = JSON.parse(localStorage.getItem('bookmarks')),
total = 0,
i;
for (i = 0; i < bm1.length; i++) {
total += bm1[i].answers;
}
console.log(total);
}
}
But alas, my output is NaN. :(
Any help would be very appreciated!!!!!!!
edit: In dev tools, this is what I get back with console.log(LocalStorage) - the numbers I have entered in the form on the site.
Storage {bookmarks: "[{"answer":"2"},{"answer":"4"},{"answer":"5"}]", length: 1}
bookmarks: "[{"answer":"2"},{"answer":"4"},{"answer":"5"}]"
length: 1
__proto__: Storage
Edit 2: I have updated the second ]function to include the JSON.parse. But now I am getting just the numbers 0245 as my result, NOT the sum of 0+2+4+5. Any help?? :p
You are on the right track by doing JSON.parse(). However, the value is in a string. You can see quote at the value it is mean will be threated as a string. You should convert it to number format like following:
total += parseInt(bm1[i].answers);
If you don't want to do parseInt() then your output should be :
{"answer": 2} //this one mean your value is Number
Instead:
{"answer":"2"} //this one mean your value is in String
I think I see it ... this statement looks "wrong, yet something that JavaScript would accept!"
var bm1 = JSON.parse(localStorage.getItem('bookmarks')),
total = 0,
i;
Notice the commas.
Instead, write this as three separate lines:
var var bm1 = JSON.parse(localStorage.getItem('bookmarks'));
var total = 0;
var i;
const bookmarks = JSON.parse(localStorage.getItem('bookmarks')) || []
const totalAnswers = bookmarks.map(o => +o.answer).reduce((a, b) => a + b)

Angular js function to add counts based on match in names

I have an angular app in this plunker
I have a text area which has some text in the format of (key,count) in them by default.
What i am trying to achieve is this(in the calc() function):
When the button is pressed the results of the summation should be displayed.
I was able to split the data from the textarea into different arrays but i am missing the logic to add when the names match.
EDIT:
please notice a few updates in my plunker
New to angular and javascript!
This should do it.
JS:-
$scope.calc = function() {
$scope.users = {};
$scope.values.split('\n').forEach(function(itm){
var person = itm.split(','),
name,
key;
if(! itm.trim() || !person.length) return;
name = person[0].trim()
key = name.toLowerCase();
$scope.users[key] = $scope.users[key] || {name:name, count:0};
$scope.users[key].count += +person[1] || 0;
});
}
HTML:-
<div class="alert alert-success" role="alert">
<ul>
<li ng-repeat="(k,user) in users">The total for {{user.name}} is {{user.count}}</li>
</ul>
</div>
Demo
Add shim for trim() for older browsers.
Here's another way to do it. I can't speak to performance against PSL's method, but I think this reads a little easier to my not-very good at javascript eyes.
function groupByName(names) {
inputArray = names.split('\n');
outputArray = {};
for (i = 0; i < inputArray.length; i++) {
var keyValuePair = inputArray[i].split(',');
var key = keyValuePair[0];
var count = Number(keyValuePair[1]);
// create element if it doesnt' exist
if (!outputArray[key]) outputArray[key] = 0;
// increment by value
outputArray[key] += count;
}
return outputArray;
}
This will produce an object that looks like this:
{
"John": 6,
"Jane": 8
}
You can display the name of each property and it's value with ng-repeat:
<li ng-repeat="(key, value) in groupedNames">
The total for {{key}} is {{value}}
</li>
It's not an overwhelmingly complex bit of javascript, depending on the number of name value pairs. So you can probably even get rid of the manual calc button and just put a $watch on values in order to automatically recalculate totals with every change.
$scope.$watch('values', function() {
$scope.groupedNames = groupByName($scope.values);
});
Demo in Plunker
You can build a TotalArray with the actual name (key) of your input as key and the count as value with it. Iterate over the input pairs and check if there is already a key called the key of this iteration if so: add the count to its value, otherwise create a new one in the TotalArray.
Some pseudo code that might help you:
inputArray = text.split('\n')
outputArray = []
for(i = 0, i< length, i++) {
name = inputArray[i].split(',')[0]
count = inputArray[i].split(',')[1]
if (outputArray[name] exists) {
outputArray[name] += count
} else {
outputArray[name] = count
}
}
Now outputArray contains [[name1] => total_count1, [name2] => total_count2,... ]
I hope this helps you.

Categories