I am sorry to keep asking this question but I am really struggling with it and I cannot figure out what is going wrong, I have read countless SO pages and general internet searches with no luck. A few people have helped me on here but the values are updating incorrectly so I thought it would be best to ask a fresh question with my most recent trials.
The challenge is to create a client-side (only) mock dog walking application based on localStorage, I so far am able to add, delete, and view appointments in the browser. I also have an edit function set up, however when I hit submit (#edit), the value at position [x] (end index) updates no matter which index I try to edit. Here is an example of my stored arrays in localStorage under key 'bookings':
[0]{fname: "John", lname: "Smith"}
[1]{fname: "Jane", lname: "Doe"}
[2]{fname: "David", lname: "Miller"}
When I hit edit on John Smith, for example, it will replace the values of David Miller, rather than Johns details. I thought of trying to find the index of each person similar to what I have done when finiding the values to display in HTML (bookings[i].lname), however this throws an error saying that i cannot be used before initialisation (makes sense, but not sure how to work around it).
Here is my most recent JS:
// ~~~ add bookings to localStorage
var bookings = JSON.parse(localStorage.getItem("bookings")) || [];
window.onload = showBooking();
$("#submit").click(function() {
var newBookings = {
fname: $('#fname').val(),
lname: $('#lname').val()
}
bookings.push(newBookings);
var json = JSON.stringify(bookings);
window.localStorage.setItem("bookings", json);
showBooking();
});
// ~~~ edit bookings in localStorage
$(document).on('click','#edit',function (e) {
e.preventDefault();
var parent_form = $(this.form);
var fname = parent_form.find('.input:eq(0)').val();
var lname = parent_form.find('.input:eq(1)').val();
const i = bookings.findIndex(booking => bookings.fname == fname && bookings.lname == lname);
deleteBooking(i);
bookings.push({
fname,
lname
});
var json = JSON.stringify(bookings);
window.localStorage.setItem("bookings", json);
// showBooking();
});
// ~~~ display bookings in browser
function showBooking() {
var bookingResult = document.getElementById("result");
var ul = document.createElement("ul");
bookingResult.innerHTML = "";
for (let i = 0; i < bookings.length; i++) {
bookingResult.innerHTML += `<div class="card card-body bg-light m-4">
<h3>${bookings[i].fname + " " + bookings[i].lname}
<button onclick="deleteBooking(${i})" class="btn btn-danger text-light ">Delete</button>
<button onclick="editBooking(${i})" class="btn btn-danger text-light ">Edit</button>
</h3>
</div>`;
}
}
// ~~~ edit bookings in browser
function editBooking(i) {
// $('#regForm').hide();
$('#result').hide();
var currentItem = document.getElementById("currentItem");
var editBooking = document.getElementById("editAppt");
currentItem.innerHTML += `<div class="card card-body bg-light m-4">
<h3>${bookings[i].fname + " " + bookings[i].lname} </h3>
</div>`;
editBooking.innerHTML = `<input type="text" class="input" id="fname_${i}" placeholder="${bookings[i].fname}" name="${bookings[i].fname}" value="${bookings[i].fname}" required>
<input type="text" class="input" id="lname_${i}" placeholder="${bookings[i].lname}" name="${bookings[i].lname}" value="${bookings[i].lname}" required>
<input id="edit" type="submit" value="Edit">`;
}
// ~~~ delete bookings from localStorage
function deleteBooking(i) {
bookings.splice(i, 1);
localStorage.setItem("bookings", JSON.stringify(bookings));
showBooking();
}
My form for creating an appointment (this changes when editBooking is called):
<form id="regForm" name="regForm" action="" class="col-sm-6">
<div id="editAppt" class="row">
<input type="text" class="input" id="fname" placeholder="First Name" name="fname" required>
<input type="text" class="input" id="lname"placeholder="Last Name" name="lname" required>
<input id="submit" type="submit" value="Submit">
</div>
</form>
You need to assign a unique identifier to each appointment. This will help fix your problem as you are currently identifying appointments by their first name, last name and position in the array.
When you edit an appointment, it removes it from its current position and adds it at the end which changes the index of all the current elements leading to your problem.
This would also cause problems if you had two appointments with the same name.
For a unique identifier, I suggest using new Date().getTime() for now.
var newBookings = {
id: new Date().getTime(),
fname: $('#fname').val(),
lname: $('#lname').val()
}
Once you've assigned a unique identifier to each appointment, you can change your Edit button so that it looks like this:
<input data-id="${bookings[i].id}" id="edit" type="submit" value="Edit">
Then in your Edit event handler, change the bottom part so that it looks like this:
let i = bookings.findIndex(booking => booking.id == $(this).data("id"));
bookings[i].fname = fname;
bookings[i].lname = lname;
var json = JSON.stringify(bookings);
window.localStorage.setItem("bookings", json);
So to explain, assign a unique identifier to each appointment, store the id in the data-id attribute, retrieve the data-id, find the index of the appointment with that id, update the appointment properties, save the bookings.
If you also want to improve the readability of your code, I suggest not mixing vanilla JavaScript and jQuery, i.e. document.getElementById("result") could be $("#result")
Related
Edit - Updated JS code to display suggestions made in comments, still having issues.. Now the button <input id="edit" type="submit" value="Submit"> won't go to edit.html, instead it is returning action.html? It is nested inside of the editForm?
I have a simple form which I have managed to learn to submit, add, remove, and display bookings using the localStorage (thanks to those who helped on here!).
My last task is to amend a booking, I think I am almost there with it, but not sure how to call the indexes (excuse my jargon), to replace the values.
The form submits and the web address reads something like edit.html?OldFirstName=NewFirstName&OldLastName=NewLastName, however the values don't update in storage, and it throws an error saying
Uncaught TypeError: Cannot read property 'fname' of undefined`.
I expected this to happen as I know I am not finding the values correctly, but I can't figure out how I should be writing it out? My thought process was that it would be similar to the original submit function but with the [i] values for fname and lname?
Here's my JS code - if you need anything else from me let me know:
// ~~~ add bookings to localStorage
var bookings = localStorage.getItem("bookings");
$("#submit").click(function () {
bookings = (bookings) ? JSON.parse(bookings) : [];
var newBookings = {
fname: $('#fname').val(),
lname: $('#lname').val()
}
bookings.push(newBookings);
var json = JSON.stringify(bookings);
window.localStorage.setItem("bookings", json);
});
// ~~~ edit bookings in localStorage
$("#edit").click(function (e) {
e.preventDefault();
bookings = (bookings) ? JSON.parse(bookings) : [];
var parent_form = $('#editForm');
var fname = parent_form.find('.input:eq(0)').val();
var lname = parent_form.find('.input:eq(1)').val();
var newBookings = {
fname: fname,
lname: lname
}
bookings.push(newBookings);
var json = JSON.stringify(bookings);
window.localStorage.setItem("bookings", json);
});
// ~~~ display bookings in browser
function showBooking(i) {
var bookingResult = document.getElementById("result");
var ul = document.createElement("ul");
var bookingItems = JSON.parse(localStorage.getItem("bookings")) || [];
bookingResult.innerHTML = "";
for (let i = 0; i < bookingItems.length; i++) {
bookingResult.innerHTML += `<div class="card card-body bg-light m-4">
<h3>${bookingItems[i].fname + " " + bookingItems[i].lname}
<button onclick="deleteBooking(${i})" class="btn btn-danger text-light ">Delete</button>
<button onclick="editBooking(${i})" class="btn btn-danger text-light ">Edit</button>
</h3>
</div>`;
}
}
// ~~~ edit bookings in browser
function editBooking(i) {
var bookingResult = document.getElementById("editAppt");
var bookingItems = JSON.parse(localStorage.getItem("bookings")) || [];
bookingResult.innerHTML =
`<form id="editForm" name="editForm" onsubmit="return editForm(this)" class="col-sm-6">
<div class="row">
<input type="text" class="input" id="fname_${i}" placeholder="${bookingItems[i].fname}" name="${bookingItems[i].fname}" required>
<input type="text" id="lname_${i}" class="input" placeholder="${bookingItems[i].lname}" name="${bookingItems[i].lname}" required>
<input id="edit" type="submit" value="Submit">
</div>
</form>`;
}
// ~~~ delete bookings from localStorage
function deleteBooking(i){
var bookingItems = JSON.parse(localStorage.getItem("bookings"));
bookingItems.splice(i, 1);
localStorage.setItem("bookings", JSON.stringify(bookingItems));
showBooking();
}
// ~~~ form submit handlers
function setAction(form) {
form.action = "action.html";
}
function editForm(form) {
form.action = "edit.html";
}
I can see that the issue comes from this like :
$("#edit").click(function (i) {
You expect the click event to return an index but it's not, the i will represent the event object, so you may need to use $(this) to get the related inputs like :
$("#edit").click(function (e) {
var parent_form = $(this.form);
var fname = parent_form.find('.input:eq(0)').val();
var lname = parent_form.find('.input:eq(1)').val();
....
NOTE: The id must not be duplicated, so you need to avoid that, you may use prefix like:
<input type="text" class="input" id="fname_${i}" placeholder="${bookingItems[i].fname}" name="${bookingItems[i].fname}" required>
<input type="text" class="input" id="lname_${i}" placeholder="${bookingItems[i].lname}" name="${bookingItems[i].lname}" required>
I have managed to get data registering to my localStorage as arrays, however I have three queries:
Why are there double square brackets around my array?
How do I change the name field to the respective html ID?
Data returning as undefined when I try to retrieve it from the localStorage?
The output I am looking for in my localStorage is:
bookings: [
[0]{fname: "John", lname: "Smith" }
[1]{fname: "Jane", lname: "Doe" }
]
But I am currently getting:
bookings: [
[0][{name: "fname" value: "John"},{name: "lname": value: "Smith" }]
[1][{name: "fname" value: "Jane"},{name: "lname": value: "Doe" }]
]
I understand how to change the name value when items are hardcoded but I am initialising an empty array in my JS and not sure where the error is, I have tried assigning a value to the array [0] but then it doesn't register anything. I have also tried the data.flat() method which does nothing.
The issue is my next step is to amend and delete items so I need to try and understand the structure. Currently I am getting undefined when I try to get data from storage, I have provided my remove function (currently to show) below, I know it is wrong but I think the issue is to do with how I am storing the data. Sorry I have asked so many questions on this but I am new to JS and still learning. I am struggling with searches as there are so many variations of Javascript and getting a lot of answers relating to C# or Python which isn't helping.
Here is my code:
//var bookings = [];
var bookings = localStorage.getItem("bookings");
$("#submit").click(function () {
//bookings = JSON.parse(localStorage.getItem("bookings")) || [];
bookings = (bookings) ? JSON.parse(bookings) : [];
var newBooking = $("#regForm").serializeArray();
bookings.push(newBooking)
var json = JSON.stringify(bookings);
const newData = bookings.flat();
window.localStorage.setItem("bookings", json);
});
$("#remove").click(function () {
var strBookings;
var i;
strBookings = localStorage.getItem("bookings");
//document.write("<p>" + strBookings + "</p>");
bookings = JSON.parse(strBookings);
for (i = 0; i < strBookings.length; i++) {
document.write("<p>" + strBookings[i].value + "</p>");
}
//localStorage.removeItem('bookings');
});
Form
<form id="regForm" name="regForm" class="col-sm-6">
<div class="row">
<input type="text" id="fname" placeholder="First Name" name="fname" required>
<input type="text" id="lname" placeholder="Last Name" name="lname" required>
<input id="submit" type="submit" value="Submit">
</div>
</form>
Show
//var bookings = [];
var bookings = localStorage.getItem("bookings");
$("#submit").click(function () {
//bookings = JSON.parse(localStorage.getItem("bookings")) || [];
bookings = (bookings) ? JSON.parse(bookings) : [];
var newBooking = $("#regForm").serializeArray();
bookings.push(newBooking)
var json = JSON.stringify(bookings);
const newData = bookings.flat();
window.localStorage.setItem("bookings", json);
});
$("#remove").click(function () {
var strBookings;
var i;
strBookings = localStorage.getItem("bookings");
//document.write("<p>" + strBookings + "</p>");
bookings = JSON.parse(strBookings);
for (i = 0; i < strBookings.length; i++) {
document.write("<p>" + strBookings[i].value + "</p>");
}
//localStorage.removeItem('bookings');
});
<form id="regForm" name="regForm" class="col-sm-6">
<div class="row">
<input type="text" id="fname" placeholder="First Name" name="fname" required>
<input type="text" id="lname" placeholder="Last Name" name="lname" required>
<input id="submit" type="submit" value="Submit">
</div>
</form>
<button id="remove" value="Remove">Show</button>
Im trying to write a validation for 2 groups of fields. I have 6 inputs, 3 for text name and 3 more for id number... the validation should do this "if input name="RE_SignedByID" has an input type name="RE_SignedByName", then other inputs name="RE_SignedByID", should NOT contain the same name="RE_SignedByName" More easy explanation... one ID number should have only one Person Name (Id number is unique for one person name). What can I use for that? Should I map() all the inputs?
Those are my inputs:
<div id="signedBy" class="clearfix">
<label>Signer, person ID & name</label>
<span id="signedByID" class="ids half">
<input type="text" name="RE_SignedByID" placeholder="personID, person1" data-validate="" tabindex="101" required>
<input type="text" name="RE_SignedByID" placeholder="personID, person2" data-validate="" tabindex="103">
<input type="text" name="RE_SignedByID" placeholder="personID, person3" data-validate="" tabindex="105">
</span>
<span class="names half">
<input type="text" name="RE_SignedByName" placeholder="name, person1" tabindex="102" required>
<input type="text" name="RE_SignedByName" placeholder="name, person2" tabindex="104">
<input type="text" name="RE_SignedByName" placeholder="name, person3" tabindex="106">
</span>
</div>
I guess it should also be an "on change" function? or can I make the validation on click? Some ideas...? Im actually compleatley lost here...
Thanks in advance!!!
Maybe use different class names for all 3 of them to make them unique?
<input class="name1">
<input class="name2">
<input class="name3">
I'm not sure what you mean but if you want to make the input types unique and not call them all when you write class="names half", then you should give them all unique class names.
So from my understanding you don't want multiple fields to have the same value.
My approach would be this:
let inputTimeout = null; //set an empty timeout object
let vars = [null, null, null, null]; // create an array containing as many nulls as you have inputs
$('.nameInput').on('keyup', function(){
let self = $(this);
clearTimeout(inputTimeout); //clear the timeout
inputTimeout = setTimeout(function(){ //set a timeout to check whether there is a dupe after the user has stopped typing
if (vars.indexOf(self.val()) == -1){ //check if the vals array contains the newly entered string
vars[self.attr('data-inputnum')] = self.val(); //insert the value into the array
}else{
//handle duplicates here
}
}, 500); //500ms is a sensible value for end of user input, change it if users complain that your app is too fast/slow
});
You then just have to edit your HTML a bit so that all name inputs have a class in common (i used .nameInput) and have a data-inputnum attr.
This would look something like this:
<input type="text" name="RE_SignedByName" placeholder="name, person1" tabindex="102" class='nameInput' data-whichinput='0'/>
<input type="text" name="RE_SignedByName" placeholder="name, person2" tabindex="103" class='nameInput' data-whichinput='1'/>
<!--and so on-->
Of course, never rely on JavaScript verification alone, always also check inside your backend. However this would be out of scope for this answer.
Hi Thanks all for the help, made me realize a couple of things till I got the answer. This is my working code:
var valSignedID = $("[name=SignedByID]").map(function() {
return this.value.trim();
}).get();
var valOwnersID = $("[name=OwnersID]").map(function() {
return this.value.trim();
}).get();
valSignedID.sort();
valOwnersID.sort();
for (var i = 0; i < valSignedID.length - 1; i++) {
if (valSignedID[i] == valSignedID[i + 1] && valSignedID[i] != "") {
alert(" You can not have duplicated signers ID's");
return false;
// break;
}
}
for (var i = 0; i < valSingedName.length; i++) {
if (valSingedName[i] == valSingedName[i + 1] && valSingedName[i] != "") {
alert(valSingedName[i] + " should not have different ID");
//return false;
}
}
I am writing a "Gamebook Engine" which offers the possibility of setting a user name. The Name is taken from an input with the id="setUserNameInput" and saved by the function setUserName(). It is displayed / loaded into an element containing the class="displayUserName" and loaded by the function displayUserName(). It works fine with only one class one the page, but as soon as I add more I have to define which one to target as it won't target them all automatically. I have tried to use document.getElementById, document.getElementsByName as well as document.querySelectorAll and document.querySelector, none of which work. (I use Bulma as my CSS Framework, by the way)
Here is the code I have so far (though it will show an error as it cannot access the localStorage inside the snippet):
This page http://scampsblog.com/docs/example-de.html contains an (working, haha) example. Since it is a documentation (page lies on my testing sever, thus the domain) you might want to take a look at http://scampsblog.com/docs/character-enginedotjs-de.html which explains / shows the individual elements (the documentation is in German but I can provide a translation if you need one).
The part of the JS I am struggling with is right in the first line but if you suggest some overall improvements, I will be happy to take them.
var userNameOutput = document.getElementsByClassName('displayUserName')[0];
function setUserName() {
var usernameinput = document.getElementById('setUserNameInput').value;
localStorage.setItem('userName', usernameinput);
if (!localStorage.getItem('userName')) {
setUserName();
} else {
var storedUserName = localStorage.getItem('userName');
userNameOutput.innerHTML = storedUserName;
}
}
function displayUserName() {
if (!localStorage.getItem('userName')) {
setUserName();
} else {
var storedUserName = localStorage.getItem('userName');
userNameOutput.innerHTML = storedUserName;
}
}
window.onload = function displayUserName() {
if (!localStorage.getItem('userName')) {
setUserName();
} else {
var storedUserName = localStorage.getItem('userName');
userNameOutput.innerHTML = storedUserName;
}
}
<input type="text" class="input" placeholder="Your name goes here" id="setUserNameInput">
<input type="button" class="button" value="Set your username" onclick="setUserName()" />
<input type="button" class="button" value="Display on click" onclick="displayUserName()" />
<br> So you shall be called <span class="displayUserName"></span>! But dont worry, <span class="displayUserName"></span>, it will be all fine.
Instead of getting the first item in the collection (using [0]) you could iterate through it (using for...of) and set the innerHTML of each element having the class displayUserName.
e.g.
var userNameOutputs = document.querySelectorAll('.displayUserName');
for (let ele of userNameOutputs) {
ele.innerHTML = userName;
}
Full code, with some optimizations to structure:
function setUserName() {
var usernameinput = document.getElementById('setUserNameInput').value;
localStorage.setItem('userName', usernameinput);
displayUserName(true); // pass true to avoid recursion
}
function displayUserName(skipSet) {
var userName = localStorage.getItem('userName');
if (!userName && !skipSet) {
setUserName();
} else {
var userNameOutputs = document.querySelectorAll('.displayUserName');
for (let ele of userNameOutputs) {
ele.innerHTML = userName;
}
}
}
window.onload = displayUserName;
<input type="text" class="input" placeholder="Your name goes here" id="setUserNameInput">
<input type="button" class="button" value="Set your username" onclick="setUserName()" />
<input type="button" class="button" value="Display on click" onclick="displayUserName()" />
<br> So you shall be called <span class="displayUserName"></span>! But dont worry, <span class="displayUserName"></span>, it will be all fine.
Working fiddle: https://jsfiddle.net/hosney/3pxfybrc/1/
var userNameOutput = document.getElementsByClassName('displayUserName')[0];
the [0] selects the first element of the array of elements of the class name.
This is my form and below is a function. I need 3 functions, 1 to store data, 1 to save the data and 1 to show all stored data. How can I do all of these functions so they all correspond with each other.
NOTE: The user will be entering the data so it's the users choice data which would temporarily be saved. The user should be able to click on the buttons to function the function but how can I do the three functions properly.
<form name = "Music">
<p>Input a Song Name, Artist, Collaborations, Duration or Album</p>
<input type="text" name = "Song_Name" id= "Song_Name" size="20"> <br>
<input type="text" name = "Artist" id= "Artist" size="20"> <br>
<input type="text" name = "Collaborations" id= "Collaborations" size="20"> <br>
<input type="text" name = "Duration" id= "Duration" size="20"> <br>
<input type="text" name = "Album" id= "Album" size="20"> <br>
<input type="button" value = "Save" onclick = "Save()"> <br>
<input type="button" value = "Search" onclick = "Store()"> <br>
<input type="button" value = "Show_All" onclick = "Show_All()"> <br>
Save function:
var catalogue[];
function Save = Music.Song_Name.Artist.Collaborations.Duration.Album.Save{
var SongName = document.getElementById('Song_Name').value;
var Artist = document.getElementById('Artist').value;
var Collaborations = document.getElementById('Collaborations').value;
var Duration = document.getElementById('Duration').value;
var Album = document.getElementById('Album').value;
document.write("You have chosen "+ Song_Name + Artist + Collaborations + Duration + Album)
}
First, I would recommend storing the values in an object rather than an array. It's much easier to work with when you have multiple, related pieces of data. Here's how it would work for Store and Show:
var catalogue;
function Store() {
catalogue = {
SongName: document.getElementById('Song_Name').value;
Artist: document.getElementById('Artist').value;
Collaborations: document.getElementById('Collaborations').value;
Duration: document.getElementById('Duration').value;
Album: document.getElementById('Album').value;
}
}
function Show_All() {
document.getElementById('Song_Name').value = catalogue.SongName;
document.getElementById('Artist').value = catalogue.Artist;
document.getElementById('Collaborations').value = catalogue.Collaborations;
document.getElementById('Duration').value = catalogue.Duration;
document.getElementById('Album').value = catalogue.Album;
}
How you handle the "Save" would depend on where you want to save it to, but the general idea would be that you would pass the catalogue value either directly into wherever you're saving it, or parse the properties of the catalogue object and pass each one in to wherever you're saving them.