I have a function that Creates new items and allows you to Delete, Update and Save the inputs on these items using localStorage
However, if I have more than one item and then update and save the changes, those changes are applied over all items.
The problem is encountered at the $(".save").click(function() but I'm not sure I have set up my .items with a proper array.
Since I use localStorage the working code can be found in the pen below:
https://codepen.io/moofawsaw/pen/NoBQKV
window.localStorage.clear();
//create localStorage item
if (!localStorage.getItem("_storage")) {
localStorage.setItem("_storage", "");
}
//set data to localStorage function
function saveData() {
localStorage.setItem("_storage", $("#content").html());
}
// Open the create dialgoue:
$(".add").on("click", function() {
$(".create").toggle();
});
//Save the entered inputs and post the item:
$(".post").click(function() {
var id = $(".createtext").val();
var createtitle = $(".createtitle").val();
var item = "";
if (id[0]) {
for (var i = 0; i < id.length; i++) {
item += "<div>" + id[i] + "</div>";
}
} else {
item = "<div>Click update to add a card</div>";
}
$("#content").append(
'<div class="item">' +
'<div class="title">' +
createtitle +
"</div>" +
"<div class='text'>" +
id +
"</div>" +
'<button class="delete">Delete</button>' +
'<button class="update">Update</button>' +
"</div>"
);
$(".createtitle").val("");
$(".createtext").val("");
$(".create").toggle();
saveData();
});
//Close out of creating a new item
$(".close").click(function() {
$(".createtitle").val("");
$(".createtext").val("");
$(".create").toggle();
});
//Get inputs and open edit window to update the items:
$("#content").on("click", ".update", function() {
var item = $(this).closest(".item");
$(".updatetext").val(
$(this)
.closest(".item")
.find(".text")
.text()
);
$(".updatetitle").val(
$(this)
.closest(".item")
.find(".title")
.text()
);
$(".edit").toggle();
});
//Save changes and update the items (error:changes all items when clicked):
$(".save").click(function() {
var id = $(".updatetext").val();
var title = $(".updatetitle").val();
var item = "";
if (id[0]) {
for (var i = 0; i < id.length; i++) {
item += "<div>" + id[i] + "</div>";
}
} else {
item = "<p>Click edit to add a card</p>";
}
$(".item").each(function() {
$(this).html(
'<div class="title">' +
title +
"</div>" +
"<div class='text'>" +
id +
"</div>" +
'<button class="delete">Deleted(2)</button>' +
'<button class="update">Updated(2)</button>'
);
});
$(".updatetext").val("");
$(".updatetitle").val("");
$(".edit").toggle();
saveData();
});
//Discard any of these changes:
$(".discard").click(function() {
$(".updatetext").val("");
$(".updatetitle").val("");
$(".edit").toggle();
});
//Delete an item:
$("#content").on("click", ".delete", function() {
$(this)
.closest(".item")
.remove();
saveData();
});
$(function() {
if (localStorage.getItem("_storage")) {
$("#content").html(localStorage.getItem("_storage"));
}
});
Point is, you call .each() in your update callback.
$(".item").each(function() {
$(this).html(
'<div class="title"> ....'
);
});
This literally means "Find all DOM elements with item class and replace their contents with given html.
But you need to replace contents of the one specific element, on which Update button was clicked. To do so, you need to persist that element somehow.
One of the ways to do that with minimum changes to your code - introduce a variable in a scope available for both update and save functions. But in your case it would be a global variable, and those are not generally a good idea.
So I'd suggest to wrap all your code into a function (like $(function() {});.
Then you can introduce a local variable:
$(function () {
// define it
var $selectedItem;
// assign a value in the update click callback
$('#content').on('click', '.update', function () {
$selectedItem = $(this).closest('.item');
// ...
});
// read the value in the save click callback
$('.save').click(function () {
// ...
$selectedItem.html('...');
// ...
});
});
Example: https://codepen.io/anon/pen/GzXaoV
Related
AS more or less, a proof-of-concept I want to use the LocalStorage API to save list items into storage. The method that I came up with is a little complicated and inefficient but up to the deleting single list items use case, it's worked well. Here's how it roughly works. Using localStorage.length I use Javascript to give notes an "id". This way I can use a for loop to iterate over from the first note to localStorage.length and append each note to the HTML page during page load in between executions. Furthermore, with this id I can identify which note was clicked on with jQuery. The problem is with deleting notes. I can delete the note that the user clicked on, but if I don't reorder the list of notes that creates what they call "holes" in storage. So any ideas?
Live demo here, but maybe the localStorage API is not supported: https://jsfiddle.net/2xke90sm/2/
Just the Javascript:
var localStorage = window.localStorage;
//First load all of the current notes in the user's storage into the page as list items
var al1 = "";
for(var p=0;p<localStorage.length;p++) {
var temp = localStorage.getItem(p.toString());
if(temp != null) { //assures that something is being written from memory, problem is that if it does find some null value, it's skipping over it
$("ul").append("<li id='note-" + p + "'><span>X</span> " + temp + "</li>");
al1 += "Note #" + p + ": '" + temp + "' has been loaded from memory. \n";
}
}
console.log(al1);
// Check Off Specific Todos By Clicking
$("ul").on("click", "li", function(){
$(this).toggleClass("completed");
//test if $("#note-[index]").html() is the same as localStorge.getItem(index.toString()) are the same.
var matchingStorage = localStorage.getItem($(this).attr("id").slice(5).toString());
console.log("HTML: " + $(this).text() + "\n" + "Local Storage: " + "X " + matchingStorage);
});
//Click on X to delete Todo
$("ul").on("click", "span", function(event){
$(this).parent().fadeOut(500,function(){
if(localStorage.getItem($(this).attr("id").slice(5).toString())) {
localStorage.removeItem($(this).attr("id").slice(5).toString()); //remove from storage
$(this).remove(); //remove from the page
reorder();
} else {
alert("could not delete element from storage.");
}
});
event.stopPropagation();
});
//Add new list item on enter key press
$("input[type='text']").keypress(function(event){
if(event.which === 13){
//grabbing new todo text from input
var todoText = $(this).val();
$(this).val("");
//create a new li, add to ul and give it the index of the localStorage system as the id
$("ul").append("<li id='note-" + localStorage.length + "'>" + "<span>X</span> " + todoText + "</li>");
localStorage.setItem(localStorage.length.toString(), todoText); //write the todoTextGet with the index as the key
}
});
$("#toggle-form").click(function(){
$("input[type='text']").fadeToggle();
});
This is indeed a task that can be done in different ways,
It is a good practice to 'store' items giving them a unique identifier.
let localStorage;
function generateId() {
let newId = Math.random().toString(36).substr(2);
while (storageGet(newId)) {
newId = Math.random().toString(36).substr(2);
}
return (newId);
}
function storageInsert(key, obj) {
localStorage.setItem(key, obj);
}
function storageGet(key) {
return (localStorage.getItem(key));
}
function storageRemove(key) {
localStorage.removeItem(key);
}
$(document).ready(() => {
localStorage = window.localStorage;
//Load all.
for (let i = 0; i < localStorage.length; i++){
let key = localStorage.key(i);
$("ul").append("<li id='note-" + key +"'> <span>X</span> " + storageGet(key) + "</li>");
}
});
//Click on X to delete Todo
$("ul").on("click", "span", function(event){
$(this).parent().fadeOut(500,function(){
storageRemove($(this).attr("id").substr(5).toString());
$(this).remove();
});
event.stopPropagation();
});
//Add new list item on enter key press
$("input[type='text']").keypress(function(event){
if(event.which === 13){
//Hold input.
let todoText = $(this).val();
//Generate an unique ID.
let newId = generateId();
//Reset Input.
$(this).val("");
//Create new element.
$("ul").append("<li id='note_" + newId + "'>" + "<span>X</span> " + todoText + "</li>");
//Add to storage.
storageInsert(newId, todoText);
}
});
$("#toggle-form").click(function(){
$("input[type='text']").fadeToggle();
});
A working example :
https://jsfiddle.net/2xke90sm/38/
Hope it helps you!
localStorage is not an array, despite it's having a length property. Instead of using length, you should iterate over Object.keys(localStorage), and your identifiers should not be index based. This way you don't get any "holes" or conflicts as you add and remove items:
const PREFIX = 'note-';
function appendItem(text, id) {
$("ul").append("<li id='note-" + id + "'><span>X</span> " + text + "</li>");
}
// Initial page load: create the list
var keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.indexOf(PREFIX === 0) {
appendItem(localStorage.getItem(key), key);
}
});
// Create a new item (inside an event handler):
const key = PREFIX + Math.random();
const text = $(this).val();
localStorage.set(key, text);
appendItem(text, key);
// Remove item (inside a click handler):
const key = $(this).attr("id");
localStorage.removeItem(key);
$(this).remove();
I have a bunch of products that will be loaded(append-ed) to the page when the user loads the page the first time. I use a $.post() call to the database and then append the data as a number of divs into a container.
$(function() {
var profile_looks = $('#profile_looks');
$.post("apples.php", function(json) {
var looks = $.parseJSON(json);
profile_looks.prepend(
(some code here)
)
}); // close $.post()
After these products are loaded, I want the products to change background color on hover.
var product_tags = $('.product_tags');
product_tags.mouseenter(function() {
$(this).css('background-color', 'white');
});
}); // close $(function()
However step 2 does not work, meaning when I mouseover the product_tags, they do not change. Why aren't the product_tags div responding to the function call?
Full code below
$(function() {
var profile_looks = $('#profile_looks');
$.post("apples.php", function(json) {
var looks = $.parseJSON(json);
var page_post = "";
$.post('oranges.php', function(products_data) {
var products_display = $.parseJSON(products_data);
for(i = 0; i < looks.length; i++) {
var fruits = products_display[i];
for(var key in fruits) {
var test = "<div class='product_tags' style='color:" + "black" + "' >" + "<span class='type' style='font-weight:600'>" + key + "</span>" + " " + "<span class='title'>" + fruits[key] + "</span>" + "</div>";
var mega = mega + test;
}; // the 2nd for-loop finishes, and re-runs the first for-loop
}; // b=0 timer loop finishes
profile_looks.prepend(
"<div class='look'>" + "<div class='look_picture_container'>" +
"<img src='" + "user_pictures/" + username_cookie + "/" + looks[i][0] + "'>" +
"<div class='heart'>" +
"<img src='" + "../function icons/hearticon black.png" + "'>" +
"<div class='heartcount'>" +
"</div>" +
"</div>" +
"<div class='product_tags_container' style='background-color:" + looks[i][3] + "' >" + mega + "</div>" +
"</div>" + // class="look_picture_container"
"<div class='post_description'>" +
looks[i][1] +
"</div>" + // class= "post_description"
"</div>"); // class="look"
var mega = "";
}
}); // for the $.post(displayproducts.php)
}) // for the $.post(displaylooks.php)
var product_tags = $('.product_tags');
product_tags.mouseenter(function() {
$(this).css('background-color', 'white');
});
product_tags.mouseleave(function() {
$(this).css('background-color', 'transparent')
});
}); // end of $(function()
It doesn't work because the elements doesn't exist yet when you try to bind the events to them.
You can use delegated events, which you bind to an existing element where the elements will end up:
profile_looks.on('mouseenter', '.product_tags', function() {
$(this).css('background-color', 'white');
}).on('mouseleave', '.product_tags', function() {
$(this).css('background-color', 'transparent')
});
Your data is arriving asynchronously, while you're creating the "hover rule" (attaching the event handlers) synchronously.
This means that when you write:
var product_tags = $('.product_tags');
product_tags.mouseenter(function() {//...
product_tags is a collection of elements that exist right after you dispatch the async POST calls. (The answer to these POSTs didn't arrive yet at this point, so the DOM you want to attach to was not generated either.)
To fix this, trigger the attaching of these mouseenter event handlers after the async answer has arrived (from the same callback you're using to work with the returned data), and you've set up the DOM you need to work with.
Note: the other answers bring up good points about delegating your event handlers via an already existing container using jQuery's .on(), which might prove to be a cleaner, more declarative solution.
As product_tags are dynamically generated elements, you need to use .on API to delegate the events for such elements. .on API
Use this
profile_looks.on({
mouseenter: function () {
$(this).css('background-color', 'white');
},
mouseleave: function () {
$(this).css('background-color', 'transparent')
}
}, '.product_tags')
function demo(){
$('.box').slideToggle('fast');
}
$(document).ready(function(){
$.getJSON( "js/JobOpenings.json", function( data ) {
var glrScrlImg = [];
$.each( data.getJobOpeningsResult, function( key, val ) {
var st = "",id,st2= "",st3="",id;
st +="<h4>" + val.JobTitle + "</h4>";
st3 += "<div class='box'>" + val.JobDetails + "</div>";
$("#newsDetails").append("<li onclick='demo()'>" + st+val.JobSector + "<br>" + st3 + "</li>");
$('.box').hide();
});
});
});
I am reading data from a json file. The div with 'box' class is hidden. Currently this code is displaying all div on click of the li. What changes should I make to display only the div corresponding to the clicked li?
Here what we need to do is to find the .box element within the clicked li, so we need to get a reference to the clicked element.
I would use a delegated jQuery event handler with css to initially hide the element
$(document).ready(function () {
$('#newsDetails').on('click', 'li', function () {
$(this).find('.box').toggleClass('hidden');
})
$.getJSON("js/JobOpenings.json", function (data) {
var glrScrlImg = [];
$.each(data.getJobOpeningsResult, function (key, val) {
var st = "",
id, st2 = "",
st3 = "",
id;
st += "<h4>" + val.JobTitle + "</h4>";
st3 += "<div class='box hidden'>" + val.JobDetails + "</div>";
$("#newsDetails").append("<li>" + st + val.JobSector + "<br>" + st3 + "</li>");
});
});
});
with css
.hidden {
display: none;
}
Pass the control to the function and then based on your control slideToggle its respective .box
function demo(ctrl){
$(ctrl).find('.box').slideToggle('fast');
}
$(document).ready(function(){
$.getJSON( "js/JobOpenings.json", function( data ) {
var glrScrlImg = [];
$.each( data.getJobOpeningsResult, function( key, val ) {
var st = "",id,st2= "",st3="",id;
st +="<h4>" + val.JobTitle + "</h4>";
st3 += "<div class='box'>" + val.JobDetails + "</div>";
$("#newsDetails").append("<li onclick='demo(this)'>" + st+val.JobSector + "<br>" + st3 + "</li>");
$('.box').hide();
});
});
});
Or add a class to li and attach an event handler like below instead of writing inline onclick as below:
$("#newsDetails").append("<li class="someclass"'>" + st+val.JobSector + "<br>" + st3 + "</li>");
and then instead of function demo() write this
$('#newsDetails').on('click','.someclass',function(){
$(this).find('.box').slideToggle('fast');
});
UPDATE
Method 1:
function demo(ctrl){
$('#newsDetails').find('li.box').hide('fast'); //hide all the .box
$(ctrl).find('.box').slideToggle('fast');
}
Method 2:
$('#newsDetails').on('click','.someclass',function(){
$('#newsDetails').find('li.box').hide('fast'); //hide all the .box
$(this).find('.box').slideToggle('fast');
});
UPDATE 2:
Method 1:
function demo(ctrl){
$('#newsDetails').find('li.box').not($(ctrl).find('.box')).hide('fast'); //hide all the .box
$(ctrl).find('.box').slideToggle('fast');
}
Method 2:
$('#newsDetails').on('click','.someclass',function(){
$('#newsDetails').find('li.box').not($(ctrl).find('.box')).hide('fast'); //hide all the .box except this
$(this).find('.box').slideToggle('fast');
});
You should structure your html (which is missing from the question!) so that the div and li are "connected" in some way (maybe the div is child of li, or they have same class, ecc).
Right now the line
$('.box').slideToggle('fast');
is applied to all element with class '.box' in your page. You want to be more selective there, that's where the way you structure the html comes into play.
Here's an example: http://jsfiddle.net/owe0faLs/1/
I have a performance related issue : it takes 10sec to load my ul -it contains more than 1000 li-.
Can you point me where the problem is. What can I optimize ?
Moreover I have some much trouble to read the profile result.
NB: the DOM element template is offline until I send it to the sandbox
var displayAllHypotheses = function () {
console.time('displayAllHypotheses');
console.profile('displayAllHypotheses');
var $template = $(_template);
var $item_example = $template.find('#item-example').clone();
var $list = $template.find('.content-ask ul.select-hypothese');
$item_example.removeAttr('id');
$template.find('#item-example').remove();
_$template_item_selected = $template.find('.item-example').removeClass('item-example').clone();
for (var i in _data_game.Hypotheses) {
var $clone = $item_example.clone();
var $a_select_hypothese = $clone.find('a');
$a_select_hypothese.html(_data_game.Hypotheses[i].nom).data('hypotheseid', _data_game.Hypotheses[i].id);
$a_select_hypothese.attr('href', '#' + i);
if (!!_hypotheses_selected[_data_game.Hypotheses[i].id]) {
$a_select_hypothese.addClass('inDaList');
}
$clone.appendTo($list);
}
$list.find('a').click(function () {
$('#mod_hypothese .part-select .select-hypothese a').removeClass('selected');
$(this).addClass('selected');
displayChooseButton();
});
$item_example = null;
$a_select_hypothese = null;
$clone = null;
$list = null;
console.timeEnd('displayAllHypotheses');
console.profileEnd('displayAllHypotheses');
return $template;
};
var initTemplate = function (data) {
console.time('initTemplate hypothese');
console.profile('initTemplate hypothese');
_template = data;
var $template = displayAllHypotheses();
$template.find('.close-modal').click(function () {
_sandbox.notify('close hypothese', null);
});
_sandbox.setTemplate($template);
initSearchBox();
displaySelectedHypotheses();
$template = null;
console.timeEnd('initTemplate hypothese');
console.profileEnd('initTemplate hypothese');
};
EDIT
So I tried the string concatenation :
var displayAllHypothesesString = function () {
console.time('displayAllHypothesesString');
console.profile('displayAllHypothesesString');
var $template = $(_template);
var $list = $template.find('.content-ask ul.select-hypothese');
var lis = '';
_$template_item_selected = $template.find('.item-example').removeClass('item-example').clone();
for (var i in _data_game.Hypotheses) {
if (!_hypotheses_selected[_data_game.Hypotheses[i].id]) {
lis += '<li><a data-hypotheseid="' + _data_game.Hypotheses[i].id + '" href="#' + i + '">' + _data_game.Hypotheses[i].nom + '</a></li>';
} else {
lis += '<li><a class="inDaList" data-hypotheseid="' + _data_game.Hypotheses[i].id + '" href="#' + i + '">' + _data_game.Hypotheses[i].nom + '</a></li>';
}
}
$list.empty().append(lis);
$list.find('a').click(function () {
$('#mod_hypothese .part-select .select-hypothese a').removeClass('selected');
$(this).addClass('selected');
displayChooseButton();
});
console.timeEnd('displayAllHypothesesString');
console.profileEnd('displayAllHypothesesString');
return $template;
};
It's working fast enough !!
But now I have HTML snippet in my JS and if the web designer need to pimp the li he'll have to go to the JS file.
But I guess there is no workaround on this issue, is there ?
You can try creating a variable and store all the data in it. Once you completed the loop, append it to the element you want so you dont have to call append that many times.
var listData;
for(var i; i<data.length; i++)
listData += "<li>some data</li>"
$("#element").html(listData)
Something like that.
I have two DIVs, #placeholder AND #imageLoad. When the user clicks on a particular thumb its larger version (thumb2) should then appear in #imageLoad DIV.
Here is the jQuery that needs to be fixed:
$.getJSON('jsonFile.json', function(data) {
var output="<ul>";
for (var i in data.items) {
output+="<li><img src=images/items/" + data.items[i].thumb + ".jpg></li>";
}
output+="</ul>";
document.getElementById("placeholder").innerHTML=output;
});
//This is wrong!! Not working..
$('li').on({
mouseenter: function() {
document.getElementById("imageLoad").innerHTML="<img src=images/items/" +
data.items[i].thumb2 + ".jpg>";
}
});
Here is the external JSON file below (jsonFile.json):
{"items":[
{
"id":"1",
"thumb":"01_sm",
"thumb2":"01_md"
},
{
"id":"2",
"thumb":"02_sm",
"thumb2":"02_md"
}
]}
$.getJSON('jsonFile.json', function(data) {
var output="<ul>";
for (var i = 0; i < data.items.length; i++) {
output += "<li><img thumb2='" + data.items[i].thumb2 + "' src='images/items/" + data.items[i].thumb + ".jpg'></li>";
}
output += "</ul>";
$("#placeholder").html(output);
$('li').on({
mouseenter: function() {
$("#imageLoad").html("<img src='images/items/" + $(this).find('img').attr('thumb2') + ".jpg'>");
}
});
});
Your variable data is declared only within the callback function of your getJSON call and hence it is not available in the other methods/ event handlers. Store it to a global variable when you get it. Like Below:
var globalData;
$.getJSON('jsonFile.json', function(data) {
globalData = data;
var output="<ul>";
for (var i in data.items) {
output+="<li id=\"listItem" + i + "\"><img src=images/items/" + data.items[i].thumb + ".jpg></li>";
}
output+="</ul>";
document.getElementById("placeholder").innerHTML=output;
});
//This is wrong!! Not working..
$('li').on({
mouseenter: function() {
var index = parseInt($(this).attr('id').substring(8));
document.getElementById("imageLoad").innerHTML="<img src=images/items/" +
globalData.items[index].thumb2 + ".jpg>";
}
});
Firstly, $.getJSON is asynchronous, so the binding of mouseenter following the async function will not work as the li elements does not exist when you attach the event handler. Secondly, store the second image source in a data attribute on each li element, and just retrieve that data attribute in the mouseenter function:
$.getJSON('jsonFile.json', function(data) {
var out = $("<ul />");
for (var i in data.items) {
$('<li />', {
src: 'images/items/' + data.items[i].thumb + '.jpg'
}).data('big', data.items[i].thumb2).appendTo(out);
}
$("#placeholder").html(out);
});
$('#placeholder').on('mouseenter', 'li', function() {
var bigImg = $('<img />', {
src: 'images/items/' + $(this).data('big') + '.jpg'
});
$("#imageLoad").html(bigImg);
});