I'm working with three tabs called 'Monday', 'Tuesday' and 'Favorites'. I have a toggle icon which is an empty heart at start 'favorite i'. If I'm in Monday and click on the icon, the empty heart turns to be filled out and its parent is cloned and added to the '#fav' tab. When this happens the clone is saved to local storage. So if people refresh the page, they can still see their preferences.
When the heart is clicked in one of those cloned divs that specific div is removed from '#fav' and is also removed from the array.
Everything works well, except when I refresh the browser and local storage is detected.
So, in this case if I'm in Monday and click on a filled heart it doesn't remove the clone from #fav and still adds a new clone to #fav. Also, if I'm in #fav tab, when clicking in one of the hearts, it should erase the index from the array, but in fact, it erases the full array.
How to overcome this issue? Many thanks.
HTML:
<section id="speakers-programme">
<div class="container">
<div class="tabs_main">
<div class="col-md-5"><a data-target="#mon" class="btn active" data-toggle="tab">Monday</a></div>
<div class="col-md-5"><a data-target="#tue" class="btn active" data-toggle="tab">Tuesday</a></div>
<div class="col-md-2"><a data-target="#fav" class="btn active" data-toggle="tab"><i class="fa fa-heart" aria-hidden="true"></i></a></div>
</div>
<div class="tab-content">
<div class="tab-pane active" id="mon">
<br>
<div class="spaces">
<div class="box-container">
<div class="box not-selected" id="box1">
<span>1</span>
<i class="fa fa-heart-o" aria-hidden="true"></i>
</div>
</div>
<div class="box-container">
<div class="box not-selected" id="box2">
<span>2</span>
<i class="fa fa-heart-o" aria-hidden="true"></i>
</div>
</div>
</div>
</div>
<div class="tab-pane active" id="tue">
<br>
<div class="spaces">
</div>
</div>
<div class="tab-pane active" id="fav">
<br>
<div class="spaces">
</div>
</div>
</div>
</div>
</section>
JS
console.clear();
//localStorage.setItem('sessions', "");
var tempArray = [];
// Clones
$('div.tab-pane').on('click', '.favorite', function(e) {
e.preventDefault();
// Elements we play with... Having significative variable names.
var heartLink = $(this);
var box = heartLink.parent('.box');
var container = box.parent('.box-container');
var favoriteTab = $("#fav .spaces");
// I don't know what is the use for those 3 lines below.
var idFind = box.attr("id");
var idComplete = ('#' + idFind);
console.log(idComplete);
//TOGGLE FONT AWESOME ON CLICK
heartLink.find('i').toggleClass('fa-heart fa-heart-o'); // .selected or not, you need those 2 classes to toggle.
box.toggleClass("selected not-selected"); // Toggle selected and not-selected classes
// Clone div
var boxContent = container.clone(true, true);
// Change the id
var thisID = boxContent.attr("id")+"_cloned";
boxContent.attr("id", thisID);
// Get the html to be saved in localstorage
var get = boxContent.wrap('<p>').parent().html();
get = get.replace(/\r?\n/g, "").replace(/>\s*</g, "><"); // remove line feeds and spaces
console.log(get);
boxContent.unwrap();
// Decide to add or remove
if(box.hasClass("selected")){
console.log("Add to array")
tempArray.push(get);
// Add to favorites tab
favoriteTab.append(boxContent);
}else{
console.log("Remove from array");
var index = tempArray.indexOf(get);
tempArray.splice(index);
// Remove from favorite tab
favoriteTab.find("#"+thisID).remove();
}
// Save array
localStorage.setItem('sessions', tempArray.join(""));
console.log(tempArray);
// save this current toggle state
localStorage.setItem(box.attr("id"), $(this).find("i").attr("class"));
console.log($(this).find("i").attr("class"));
});
// Append item if localstorage is detected
if (localStorage["sessions"]) {
console.log("storage exist");
// Load
$(".box").each(function(){
console.log( $(this).attr("id") );
console.log( localStorage.getItem($(this).attr("id")) );
if(localStorage.getItem($(this).attr("id")) != null){
$(this).find("i").removeClass().addClass( localStorage.getItem($(this).attr("id")) );
}
});
$("#fav .spaces").append(localStorage["sessions"]);
console.log( localStorage["sessions"] );
}
Fiddle: https://codepen.io/Bes7weB/pen/bobjdv?editors=1011
I twisted your code in a way that deserves explanations.
First, you finally don't need to save the HTML of your favorited elements. You just need the heart icon states, which you already do. I added a counter, just to know how many favorited there is in storage.
Now, on page load... If there is more than zero favorites in storage, Apply the icon states by loading their classes from storage. You already had this part right. THEN cycle throught all hearts to target the filled ones and clone them in the favorite tab. I made a "named function" to do this.
On icon click now... Clicking on a cloned element or on an original element are two different situations.
On an original element, you want to toggle its classes and clone it to the favorite tab. So here, just do the togglings and for the favorite tab, just call the previous named function to clone them all!
On a cloned element, you want to remove it from favorites and toggle the original element classes. See the code to get this twist I made! I redefined some variables in this case.
Notice there no more tempArray in use.
;)
var favoriteTab = $("#fav .spaces");
// Named function to load the favorites tab
function loadFav(){
// First, clear old favorites.
favoriteTab.empty();
// Look for filled hearts
var favCount = 0;
$(".tab-content").find("i.fa-heart").each(function(){
// Count them
favCount++;
// Clone its box
var favClone = $(this).closest(".box").clone();
// Change the id
favClone.attr("id", favClone.attr("id")+"_clone");
// Append to favorites
favoriteTab.append(favClone);
});
console.log("favCount: "+favCount);
localStorage.setItem("favAmount", favCount);
}
// Click handler
$('div.tab-pane').on('click', '.favorite', function(e) {
e.preventDefault();
// Elements we play with... Having significative variable names.
var heartLink = $(this);
var box = heartLink.parent('.box');
var thisID = box.attr("id");
var container = box.parent('.box-container');
if(thisID.split("_")[1] == "clone"){
console.log("You clicked a clone!");
// Remove that clone
box.remove();
// Use the original element for the rest of the function.
heartLink = $("#"+thisID.split("_")[0]).find("a.favorite");
box = heartLink.parent('.box');
thisID = box.attr("id");
}
//TOGGLE FONT AWESOME ON CLICK
heartLink.find('i').toggleClass('fa-heart fa-heart-o'); // .selected or not, you need those 2 classes to toggle.
box.toggleClass("selected not-selected"); // Toggle selected and not-selected classes
// Clone div
loadFav();
// Save this current toggle state
localStorage.setItem(box.attr("id"), heartLink.find("i").attr("class"));
console.log(heartLink.find("i").attr("class"));
});
// ON PAGE LOAD
// Append item if localstorage is detected
if (localStorage["favAmount"]>0) {
console.log("storage exist");
// Load heart's element states
$(".box").each(function(){
console.log( $(this).attr("id") );
console.log( localStorage.getItem($(this).attr("id")) );
if(localStorage.getItem($(this).attr("id")) != null){
$(this).find("i").removeClass().addClass( localStorage.getItem($(this).attr("id")) );
}
});
// Load favorites
loadFav();
}else{
console.log("no storage");
}
CodePen v6
Related
Basically I'm trying to make a todo list app similar to Trello. I have a button that when pressed turns into an input element, gets a "To Do Task" item and adds that to a list. This is achieved by this piece of code:
function createCardBoxNode(title){
/*HTML looks like:
<div class="task-card">
<div class="writings">
<p class="title">Tasks To Do</p>
<ul id="tasks">
<li>Task 1</li>
</ul>
</div>
<button class="btn-add-task">
Add New Task...
</button>
<input....>
</div>
*/
var containerBox = createElement('div', {class:'task-card'});
var writingPartBox = createWritingAreaNode(title);
var newTaskBtn = createElement('button', {class:'btn-add-task show'},'Add New Task...');
var newTaskInput = createElement('input', {class:'new-task hide', type:'text', placeholder:'New Task'});
//When 'add new task' is clicked, make it an input area
newTaskBtn.addEventListener('click', function(){
newTaskBtn.classList.remove('show');
newTaskBtn.classList.add('hide');
newTaskInput.classList.remove('hide');
newTaskInput.classList.add('show');
newTaskInput.focus();
});
// when input is entered, that's a new "To Do Task" so add it to the list
newTaskInput.addEventListener('keyup', function(e){
if (e.keyCode === 13){
// If Enter is pressed
var newTask = createListItems(newTaskInput.value);
var listArea = document.getElementById('tasks');
listArea.appendChild(newTask);
newTaskInput.classList.remove('show');
newTaskInput.classList.add('hide');
newTaskInput.value = '';
newTaskBtn.classList.remove('hide');
newTaskBtn.classList.add('show');
}
});
containerBox.appendChild(writingPartBox);
containerBox.appendChild(newTaskBtn);
containerBox.appendChild(newTaskInput);
return containerBox;
}
This works fine until I add another Card at the same time and decide to add new tasks to the second card. Then every task gets added to the first card. I wonder if there is any way to check if the "input" that's being sent is going to a specific card checking the card's title. I don't have any limits on how many tasks can be added to each card, and don't want to add that. I also want the user to be able to work on two separate cards at the same time. As a beginner, I also want to fix this using only JavaScript. I hope I've explained the issue well enough.
Edit:
I have tried doing this:
if (document.querySelector('.title').innerText === title){
var newTask = createListItems(newTaskInput.value);
var listArea = document.getElementById('tasks');
listArea.appendChild(newTask);
newTaskInput.classList.remove('show');
newTaskInput.classList.add('hide');
newTaskInput.value = '';
newTaskBtn.classList.remove('hide');
newTaskBtn.classList.add('show');
But then I cannot add anything new to the second box.
I think your main problem is that you use the <ul> with the same id for different cards.
First of all, change your markup and replace <ul id="tasks"> with <ul class="tasks-list">
<div class="task-card">
<div class="writings">
<p class="title">Tasks To Do</p>
<ul class="tasks-list"> <!-- !!! here !!! -->
<li>Task 1</li>
</ul>
</div>
<button class="btn-add-task">
Add New Task...
</button>
<input....>
</div>
and then change that selector in your keyup handler:
//...
var listArea = containerBox.querySelector('.tasks-list');
// ...
Also, it would be better to declare
var listArea = containerBox.querySelector('.tasks-list');;
outside your handler.
I solved it on my own.
The trick is to not select anything at all. Everytime you queryselect anything, or get an element using it's ID - it will select the first element with that id or class.
What I ended up doing is to combine two of my functions, have a function generate my and then just straight up append the list items into that ul. No selection whatsoever.
I need one help. I need to add one new class along with the existing class using Jquery/Javascript. I am explaining my code below.
<div class="fynd-space-itms">
<div class="col-sm-3">
Ram</div>
<div class="col-sm-3">
Raj</div>
<div class="col-sm-3">
Ray
</div>
</div>
Here I need when user will click on the onclick event one new class will add along with existing class. Suppose user clicked on Ram and after clicking the new class i.e-active will add and the total class name will be item-exhibitationactive maploc and same for others i.e-item-parkingactive maploc,item-officesactive maploc. At the same time from other anchor tag the active class will remove if it added before. Please help me.
In jquery try this:
$('.maploc').click(function(){
$('.maploc').removeClass('active'); // to remove already added class
$(this).addClass('active');
});
Working Fiddle
function keepSection($this){
var className=$($this).attr('class').replace('maploc','').replace(' ','');
var newclassName = className +'active';
//alert(className);
$('a').each(function(){
var theirClass= $(this).attr('class');
var patt = new RegExp("active");
if(patt.test(theirClass))
{
$(this).removeClass(theirClass);
var newCls = theirClass.replace('active','');
$(this).addClass(newCls);
}
});
$($this).removeClass(className);
$($this).addClass(newclassName);
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="col-sm-3">
Ram
Raj
Ray
</div>
<div class="col-sm-3">
Ram1
Raj1
Ray1
</div>
If you are trying to have differant active states for the items, you can just use
Sorry, first answer, have updated with snippet
$('.maploc').click(function(e){
e.preventDefault();
// Save DOM element to a var for speed.
var self = $(this),
// Get the first class name from the attr
active = self.attr('class').split(" ")[0],
// Set inactive to be the same as active
inactive = active;
// Remove maploc class so we only have 1 class left to work with
self.removeClass('maploc');
// Is active already active?
if(active.match(/active/g)) {
// YES : Remove active from the class
inactive = active.replace(/active/,'');
} else {
// NO : Append active to the class
active = active+'active';
}
// Toggle between active and inactive
self.toggleClass(active +" "+ inactive);
// Append maploc back into the class attr
self.addClass('maploc');
// Next line not needed.
$('#class').text(self.text() + '=' + self.attr('class'));
});
.item-exhibitationactive{
color:red;
}
.item-parkingactive {
color:yellow;
}
.item-officesactive {
color:green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="col-sm-3">
Ram
Raj
Ray
</div>
<div><b>Current Class:</b> <span id="class"></span>
I am not able to save active class for a div on page reload, when I click button in first div then its active class is removed and next class is made active. But when the page reloads in second div, how to save active class for second div when page is reloaded . i tried using local storage but i dunno that.
<div class="divs active" id="first">
<h1> first div</h1>
<a class="btn-link" href="javascript:void(0);" id="btn-first">Next - 2</a>
</div>
<div class="divs" id="second">
<h1> second div</h1>
<a class="btn-link" href="javascript:void(0);" id="btn-second"/>Next -3 </a>
</div>
<div class="divs" id="third">
<h1> third div</h1>
<a class="btn-link" href="javascript:void(0);" id="btn-third"/>Next -1</a>
</div>
<script>
$(document).ready(function(){
$('#btn-first').click(function(){
$('.divs').removeClass('active');
var activeID = $('#second').addClass('active');
console.log(activeID);
localStorage.setItem("activeDIV", activeID);
//var reloadactiveDIV = localStorage.getItem("activeDIV");
// var activeID = $('#second');
// localStorage.setItem('activeTab', $activeID );
// var activeTab = localStorage.getItem('activeTab');
// if (activeTab) {
// $('.divs').removeClass('active');
// $('#second').addClass('active');
// }
});
$('#btn-second').click(function(){
$('.divs').removeClass('active');
$('#third').addClass('active');
});
$('#btn-third').click(function(){
$('.divs').removeClass('active');
$('#first').addClass('active');
});
});
</script>
To achieve this you can store the index of the active div in local storage, instead of the entire jQuery object, and then re-set the active class on the element within the index of localStorage when the page is next loaded.
Also note that you can DRY up the logic by using a single event handler for all the buttons. You can find the parent .divs and retrieve the next one, looping back to the first if there is no next sibling. Try this:
$(document).ready(function() {
// set active on click:
$('.btn-link').click(function(e) {
e.preventDefault();
var $parentDiv = $(this).closest('div');
var $nextDiv = $parentDiv.next('div');
var $divs = $('.divs').removeClass('active');
if (!$nextDiv.length)
$nextDiv = $divs.first();
$nextDiv.addClass('active');
localStorage.setItem("activeDiv", $nextDiv.index('.divs'));
});
// set active on load:
var activeIndex = localStorage.getItem("activeDiv");
if (activeIndex)
$('.divs').removeClass('active').eq(activeIndex).addClass('active')
});
Working Example
Note that I couldn't place a working example in a SO Snippet as it has restrictions in place on accessing localStorage.
I have the following div collection in my HTML. It's designed to dynamically replicate according to user interaction.
<div class="bill-item">
<!-- Section for a single item -->
<div class="bill-item-img">
<!-- Section for Item pic -->
</div>
<div class="bill-item-description">
<!-- Section for Item description and pricing -->
<div class="bill-item-name">
<p class="bill-item-name-left">Normal Cofee</p><p class="bill-item-name-right">170.00</p>
<div class="clear"></div>
</div>
<div class="bill-item-price">
<span>170.00 USD</span>
</div>
<div class="bill-item-amount">
<span>2</span>
</div>
</div>
<div class="bill-amount-selection">
<!-- Section where the increment & decrement of item amount goes -->
<a class="amount-increase" href="#"></a>
<a class="amount-decrease" href="#"></a>
</div>
</div>
This is the HTML Rendered image of the elements.
I've written the following script to increase the bill-item-amount span value.
$(".amount-increase").click(function(){
x+=1;
$(".bill-item-amount span").html(x);
});
$(".amount-decrease").click(function(){
if(!x<=0){
x-=1;
$(".bill-item-amount span").html(x);
}
});
This works great but, it updates the value of both the span elements. what I want is to catch the event of the clicked element (which I do now) and increase the span value of the respective span. How can I filter out which span to update using javascript.?
Something like $(this).parents('.bill-item').find('.bill-item-amount span') should select the right element.
Inside your callback this is assigned to the eventSource.
You should walk the dom tree from the clicked element up until you reach the .bill-item element and the go down to the .bill-item-amount span node
$(".amount-increase").click(function(){
var $span = $(this).parent().parent().find(".bill-item-amount span");
var x = $span.html();
x+=1;
$span.html(x);
});
$(".amount-decrease").click(function(){
var $span = $(this).parent().parent().find(".bill-item-amount span");
var x = $span.html();
if(!x<=0){
x-=1;
$span.html(x);
}
});
Hi dimal update your code:
$(".amount-increase").click(function(){
x+=1;
$(".bill-item-amount").html(x);
});
$(".amount-decrease").click(function(){
if(!x<=0){
x-=1;
$(".bill-item-amount").html(x);
}
});
dont add span inside the selector [ it changes entire span values]
$(".amount-increase").click(function(){
x+=1;
$("use ur increase span id here").html(x); //
});
$(".amount-decrease").click(function(){
if(!x<=0){
x-=1;
$("use ur decrease span id here").html(x);
}
});
Inside each function the selector $(".bill-item-amount span") will find all the <span> amounts in the document. You can walk the DOM to find the correct <span> using jQuery or plain JavaScript. You seem to be using jQuery functions so my answer also uses jQuery.
The following code combines the two actions into a single function that increases or decreases the amount based on the class name of the <a> clicked. I also added a return false so that the browser will not follow the href="#" on the anchor.
$('.bill-amount-selection').on('click', 'a', function(){
var change = this.className == 'amount-increase' ? 1 : -1
var $amount = $(this).closest('.bill-item').find('.bill-item-amount span')
var amount = parseInt($amount.html(), 10) + change
$amount.html(amount < 0 ? 0 : amount)
return false
});
The use of .on() means that jQuery v1.7+ is required. I can supply a compatible function with lower jQuery versions if necessary.
I have a JS/jQuery script that adds our leads (web contacts) to the DOM in a for loop. Everything works fine except for one thing. I want the body of the lead to be hidden upon the initial display, and then have a slideToggle button to display or hide the details That means dynamically adding click events to each button as it is created. The entire HTML (HTML and a JSON object mixed into the HTML) of the lead and the slideToggle button are all appended to a node in the DOM in the for loop. Here is the pertinent part of the for loop:
// Hide the body of the lead; just show the title bar and the first line
var dataID = data[i].id
var div = $('#row' + dataID);
var more = $('#more' + dataID);
div.hide();
// Create click event for each "+" button
more.click(function() {
div.slideToggle();
});
But when I click on the "+" button to reveal the details, it opens the last div, not the div I am trying to open. This is true no matter how many leads I have on the page. How do I get the click event to open the right div. If I console.log "div" in the click event, it gives me the ID of the last div, not the one I am clicking on. But if I console.log(div) outside the click event, it has the right ID.
Also, I was unsure whether I needed the "vars" in the loop or if I should declare them outside the loop.
Here is the HTML. It's one lead plus the beginning of the next lead, which I left closed in Firebug
<div id="lead1115">
<div id="learnmore">
<a id="more1115" class="more" href="#">+</a>
</div>
<div id="lead-info">
<div id="leadID">Lead ID# Date: March 27, 2012 11:26 AM (Arizona time)</div>
<div id="company">No company given</div>
<div id="name">Meaghan Dee</div>
<div id="email">
meaghan.dee#gmail.com
</div>
<br class="clearall">
<div>
<div id="row1115" style="display: none;">
<div id="phone">No phone given</div>
<div id="source">www.ulsinc.com/misc/expert-contact/</div>
<div id="cp-name">No channel partner chosen</div>
<br class="clearall">
<div id="location">
No location given
<br>
<strong>IP Address:</strong>
198.82.10.87
<br>
<span>Approximate Location: Blacksburg, Virginia, United States</span>
<br>
</div>
<div id="details">
<strong>Questions/Comments</strong>
<br>
We have the Professional Series Universal Laser Systems (laser cutter), and I wondered how I would order a high power density 2.0 replacement lens.nnThank you
</div>
</div>
</div>
</div>
<div id="learnmore">
<a id="1115|send_message" class="verify" href="#">Verify</a>
<a id="1115|send_message" class="markAsSpam" href="#">Spam</a>
<a id="1115|send_message" class="markAsDuplicate" href="#">Duplicate</a>
</div>
</div>
<br class="clearall">
<div id="lead1116">
<br class="clearall">
Try using .bind (or .on for 1.7+) and the data parameter.
more.bind("click",{target:div},function(e){
e.data.target.show();
}
or
more.on("click",{target:div},function(e){
e.data.target.show();
}
I think your basic problem is that div is common as a variable to all items. You have to separate the div's from each other by, for example, creating a local function and call it for each item. Something like:
function buildMore(div) {
more.click(function() {
div.slideToggle();
});
}
and in the loop call:
addMore(div);
p.s.
Whether you declare your variables inside or outside the loop doesn't matter: you still get the same variables.
This is because div variable gets changed and settles with the last value set in the loop.
Try this:
...
funciton createClick(div) {
return function() { div.slidToggle();
}
more.click( createClick(div) );
...
The variable div doesn't stay frozen with your click handler so it's value will be what it was at the end of the for loop and all click handlers will use the same value (which is what you're seeing).
There are a number of different ways to approach this and I thought all would be educational. Any one of them should work.
Idea #1 - Manufacture the row id from the clicked on more id
Use the id value on the clicked on link to manufacture the matching row ID. Since you create them in pairs, this can be done programmatically like this:
// Hide the body of the lead; just show the title bar and the first line
var dataID = data[i].id
$('#row' + dataID).hide();
$('#more' + dataID).click(function() {
// manufacture the row ID value from the clicked on id
var id = this.id.replace("more", "#row");
$(id).slideToggle();
});
Idea #2 - Use a function closure to "freeze" the values you want
Another way to do that is to create a function and closure that will capture the current value of div:
// Hide the body of the lead; just show the title bar and the first line
var dataID = data[i].id
var div = $('#row' + dataID).hide();
var more = $('#more' + dataID);
function addClick(moreItem, divItem) {
// Create click event for each "+" button
moreItem.click(function() {
divItem.slideToggle();
});
}
addClick(more, div);
Idea #3 - Use the HTML spatial relationship to find the row associated with a more
To make this work, you need to put a common class=lead on the top level lead div like this:
<div id="lead1115" class="lead">
And, a common class on each row:
<div id="row1115" class="row" style="display: none;">
Then, you can use the position relationships to find the row object that is in the same parent lead object as the clicked on more link like this:
// Hide the body of the lead; just show the title bar and the first line
var dataID = data[i].id
$('#row' + dataID).hide();
$('#more' + dataID).click(function() {
// find out common parent, then find the row in that common parent
$(this).closest(".lead").find(".row").slideToggle();
});
Idea #4 - Put the row ID as data on the more link
// Hide the body of the lead; just show the title bar and the first line
var dataID = data[i].id
$('#row' + dataID).hide();
$('#more' + dataID).data("row", "#row" + dataID).click(function() {
// get the corresponding row from the data on the clicked link
var rowID = $(this).data("row");
$(rowID).slideToggle();
});