I have a card list that is being created by looping through some JSON data which is stored on Firebase. Each card is split in two parts: "overviewCardInfo" and "overviewCardOptions".
I would like to make it so that when the options part is clicked, a function is called (in this case a .toggleClass()) and when the info part is clicked, the title of that card is displayed somewhere else.
So far I have been unable to achieve either effect: The title of the card is nowhere to be seen and the toggleClass() either affects the div with class ".overviewCardOptions" or affects all cards rather than just the one clicked.
This is what the JSON array I am taking from Firebase to generate the cards looks like:
[{"name":"Push Ups", "duration":3, "break":3},{"name":"Squats", "duration":3, "break":3},{"name":"Running in Place", "duration":3, "break":3}]
JavaScript (some jQuery):
// Compile Routine Overview List.
var obj = JSON.parse(localStorage.exercise); // The JSON shown above
obj.forEach(function(exercise)
{
$("#overviewList").append("<li><div class='overviewCard'><div class='overviewCardInfo'>\n\
<h3>" + exercise.name + "</h3><p>" + exercise.duration + " sec.</p><p id='right'>Break: " + exercise.break + " sec.</p>\n\
</div><div class='overviewCardOptions'><img src='images/thrash.png' width='23' alt='' /></div></div></li>");
});
$(document).on("click", ".overviewCardInfo", function() // Attach event handler to document, as cards are generated after other elements on page.
{
$("#overviewSpecifier").css("display", "block"); // Works fine.
$("#infoP").text($(this).text()); // Shows all text contained in card, of course, how do I target the h3 tag specifically? jQuery API docs not helping me understand atm.
});
$(document).on("click", ".overviewCardOptions", function()
{
$(".overviewCard", this).toggleClass("toDelete"); // Fails.
});
HTML:
<ul id="overviewList"></ul>
<div id="overviewSpecifier">
<p id="infoP"></p>
</div>
Attach the click() event to the document instead to make it global.
$(document).on("click", ".overviewCardOptions", handleThrashClick);
Also, your object (in fact it's an array) shouldn't have quotation marks
var obj = "[ ... ]" should be var obj = [ ... ]
See demo here >> https://fiddle.jshell.net/9a457qzv/3/
Related
I need to change the location.href of some URLs on my site. These are product cards and they do not contain "a" (which would make this a lot easier).
Here is the HTML:
<div class="product-card " onclick="location.href='https://www.google.com'">
I mean it is pretty simple, but I just cannot get it to work. Did not find any results from Google without this type of results, all of which contain the "a":
$("a[href='http://www.google.com/']").attr('href', 'http://www.live.com/')
Any ideas on how to get this to work with jQuery (or simple JS)?
I cannot change the code itself unfortunaltely, I can just manipulate it with jQuery and JS.
To change the onClick for all the class='product-card', you can do something like this:
// All the links
const links = document.getElementsByClassName('product-card');
// Loop over them
Array.prototype.forEach.call(links, function(el) {
// Set new onClick
el.setAttribute("onClick", "location.href = 'http://www.live.com/'" );
});
<div class="product-card " onclick="location.href='https://www.google.com'">Test</div>
Will produce the following DOM:
<div class="product-card " onclick="location.href = 'http://www.live.com/'">Test</div>
Another option, is to loop over each <div> and check if something like google.com is present in the onClick, if so, we can safely change it without altering any other divs with the same class like so:
// All the divs (or any other element)
const allDivs = document.getElementsByTagName('div');
// For each
Array.from(allDivs).forEach(function(div) {
// If the 'onClick' contains 'google.com', lets change
const oc = div.getAttributeNode('onclick');
if (oc && oc.nodeValue.includes('google.com')) {
// Change onClick
div.setAttribute("onClick", "location.href = 'http://www.live.com/'" );
}
});
<div class="product-card" onclick="location.href='https://www.google.com'">Change me</div>
<div class="product-card">Don't touch me!</div>
I using PhoneGap to create a Geolocation App following this excellent tutorial (link). Unfortunatelly, I'm having an issue that I can't figure out. The relevant parts that are giving me a headache are these:
//Section 1
$('#history').on('pageshow', function () {
tracks_recorded = window.localStorage.length;
$("#tracks_recorded").html("<strong>" + tracks_recorded + "</strong> workout(s) recorded");
$("#history_tracklist").empty();
for (i = 0; i < tracks_recorded; i++) {
$("#history_tracklist").append("<li><a href='#track_info' data-ajax='false'>" + window.localStorage.key(i) + "</a></li>");
}
$("#history_tracklist").listview('refresh');
});
//Section 2
$("#history_tracklist li a").on('click', function () {
$("#track_info").attr("track_id", $(this).text());
});
//Section 3
$('#track_info').on('pageshow', function () {
var key = $(this).attr("track_id");
$("#track_info div[data-role=header] h1").text(key);
var data = window.localStorage.getItem(key);
data = JSON.parse(data);
});
Section 1 works just fine, the data is stored, and the list is created without any issues. But then in Section 2 is when everything goes to hell. By clicking on the element, a new attribute (track_id) is supposed to be created, but it doesn't. Therefore, in Section 3, the "var key" won't get a value, and as a consequence, "var data" will be null also. As you can imagine, nothing works from there. What am I doing wrong here? I only included what I considered the relevant code, but if more is needed I'll do so. Thansk!
In section 2, I think you just need to delegate click handling to the "#history_tracklist" container, as follows :
$("#history_tracklist").on('click', "li a", function () {
$("#track_info").attr("track_id", $(this).text());
});
Without delegation you have a rule saying :
when any existing li a element within #history_tracklist is clicked execute my function
With delegation, you have a rule saying :
when any existing or future li a element within #history_tracklist is clicked execute my function
I'm attempting to make a menu bar that can have <li> elements added and removed. So far so good, but when I try and remove them I'm running into issues. I've toyed with this for a couple hours and now I'm wondering if this whole process could just be made easier (maybe an object?).
Anyways, here's the full code (80 lines), with comments to follow along.
var tabs = $('.accountSelectNav');
var titles = [];
var listItems = [];
// when the page loads check if tabs need to be added to the ul (menu bar)
$(document).ready(function(e) {
if ($.cookie('listItems') != null) {
console.log('not null');
//return "listItems" to it's array form.
listItems = JSON.parse($.cookie('listItems'));
$('.accountSelectNav').append(listItems);
}
});
$('.selectTable td:first-child').on('click', function(e) {
$('#home_select').removeClass('navHighlight');
//grab the text value of this cell
title = $(this).text();
$.ajax({
url:'core/functions/getAccountId.php',
type: 'post',
data: {'title' : title}
}).fail (function() {
alert('error');
}).done(function(data) {
accountId = $.trim(data);
// store values in the cookie
$.cookie('account_id', accountId, {expires : 7});
$.cookie('title', title, {expires : 7});
window.location = ('home_table.php');
});
// make sure the value is NOT currently in the array. Then add it
var found = jQuery.inArray(title, titles);
if (found == -1) {
titles.push(title);
addTab();
}
// make sure the value is NOT currently in the array. Then add it
found = jQuery.inArray(title, listItems);
if (found == -1) {
addListItem();
//place <li>'s in cookie so they may be used on multiple pages
$.cookie('listItems', JSON.stringify(listItems));
};
});
$("body").on("click", ".deleteImage", function (e) {
var removeTitle = $(this).closest('li').find('a').text();
var removeItem = $(this).closest('li')[0].outerHTML;
//remove title from "titles" array
titles = jQuery.grep(titles, function (value) {
return value != removeTitle;
});
//remove <li> from "listItems" array
listItems = jQuery.grep(listItems, function (value) {
return value != removeItem;
});
// this shows the <li> is still in the listItemsarray
console.log(listItems);
// put the array back in the cookie
$.cookie('listItems', JSON.stringify(listItems));
removeTab(this);
});
$("body").on("mouseover", ".accountSelectNav li", function(e) {
$(this).find('.deleteImage').show();
});
$("body").on("mouseleave", ".accountSelectNav li", function(e) {
$(this).find('.deleteImage').hide();
});
function addTab() {
tabs.append('<li class="navHighlight">' + '' + title + '' + '' + '<img src="images/delete.png" class="deleteImage"/>' + '' + '</li>');
};
function removeTab(del) {
$(del).closest('li').remove();
}
function addListItem() {
var s = ('<li class="navHighlight">' + '' + title + '' + '' + '<img src="images/delete.png" class="deleteImage"/>' + '' + '</li>');
listItems.push(s);
}
So you see I have two arrays of equal length that should always be the same length. One stores the title to be displayed in the tab, the other holds the html for the <li> which will be appended to the <ul>. I have no problem removing the title from its array. However removing the <li> from it's array is becoming a rather big hassle. You see when I get the <li> element after its been inflated the html inside does not exactly match what was put in, the browser adds style elements.
Example, the variable "removeItem" represents the html value of the selected <li> I wish to remove. It looks like this:
<li class="navHighlight">Test1<img src="images/delete.png" class="deleteImage" style="display: inline;"></li>
yet the value in my array "listItems" looks like this:
<li class="navHighlight">Test1<img src="images/delete.png" class="deleteImage"/></li>
So my attempt at removing it from my array always fails because they aren't a perfect match.
Now my question is how do I remove this <li> item? Also is there an easier way to do this whole process and I'm just not seeing it?
Thanks for your time.
EDIT
Fiddle by request here
Easiest way I can explain it.
Click the link to the fiddle.
Click any cell in the "App Name" column
This will add a <li> to the <ul> (menu) above of the table
When you hover over the <li> a picture appears
Click the picture
This should remove the <li>, both from the <ul> and from the array listItems
right now it does not
In the process of making this easier to check, I've taken your JSFiddle and did the following:
removed extra console.log and comments
removed interaction with cookies (since I did not have them in the first place, I figured they wouldn't just the first scenario)
After doing so I reached a point (you can see it here) where the desired functionality just works.
I even went ahead and removed the ajax stuff because that alert was driving me crazy. (here)
Since this works fine, my guess is that your issue lies between the lines that I removed.
Your usage of cookies is as follows:
To load existing tabs and add them back again
To save account_id and title, which is not used back again
To persist the listItems after a new item has been added
I then opened up the console with your version of the fiddle and the execution of javascript stops at $.cookie() with the error undefined is not a function.
This clearly indicates that the issue present in the Fiddle is that jQuery.cookie is not present and so those calls are halting the execution of the rest of your script. This also explains why it just started working when I took them out.
I posted the whole process of how I got there to indicate how I trimmed down the problem to specific parts, which is useful to reduce the problem space. When you're out of options and reach a place when you're lost, it's easier to post a question with less code and the specific part of the problem that you've identified. This will help you in finding the issues that you're facing and StackOverflow to provide proper answers to your questions.
Hope it helps!
Here is the solution I came up with. It should be much easier for people to understand than my original post. Although it's a long read it may be worth it, especially for new developers.
The point of this code is to make a menu bar out of an un-ordered list or <ul>. The menu bar needs to be used on multiple pages. So I'll be using cookies.
I start with this code to get a text value from my table.:
$('.selectTable td:first-child').on('click', function(e) {
// This value will be used later for the name of the tab or `<li>` inside our menu bar or `<ul>`
title = $(this).text();
});
Then I place the value in an array. I do this only if the array does not already have this string inside it. I do not want duplicates:
var found = jQuery.inArray(title, titles);
var titles = [];
if (found == -1) {
titles.push(title);
}
Then I store the array into a cookie, using a library like this:
$.cookie('titles', JSON.stringify(titles));
Now when any page loads that needs this menu bar I run this code to check if there are any values:
$(document).ready(function() {
if ($.cookie('titles') != null) {
titles = JSON.parse($.cookie('titles'));
}
});
Now I need to loop through the array. When I loop through the array I have to do 3 things:
1) Grab the string value.
2) Add the html to my new string so it becomes a list item or <li>.
3) Append the newly created <li> to our <ul>.
Like so:
for(var i = 0; i < titles.length; i++) {
var str = titles[i];
var listItem = '<li class="navHighlight">'
+ '<a href="#">'
+ str
+ '</a>'
+ '<a href="#">'
+ '<img src="images/delete.png" class="deleteImage"/>'
+ '</a>'
+ '</li>';
$('.accountSelectNav').append(listItem);
}
Now, if I want to remove this <li> I click the delete image found inside our <li>. What delete image you say? Look at the html I added again. You will see I add an <img> tag in there.
Now delete like so:
$("body").on("click", ".deleteImage", function (e) {
// grabs the text value of my li, which I want to remove
var removeTitle = $(this).closest('li').find('a').text();
// runs through my titles array and returns an array without the value above
titles = jQuery.grep(titles, function (value) {
return value != removeTitle;
});
});
Then I simply place the new array inside my cookie once again. Like this:
$.cookie('titles', JSON.stringify(titles));
And finally I remove the tab like this:
removeTab(this);
function removeTab(del) {
$(del).closest('li').remove();
}
Yay, I'm done. So now, if anyone has a more elegant way of accomplishing this I'm listening. I have no doubt there's a better way, javascript/jQuery isn't even close to my strong point.
The full code can be found here.
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();
});
UPDATE: A commenter told me to change some codes, this is the new code and its not working neither.
I'm creating a Facebook-Like chat. It gets the latest messages "Not Read" from a JSON file and it appends the text to an "UL" element vía "LI" into a box. If the box doesn't exist, it creates and attach the text. I want that when I click that div, it hides using margin-bottom negative, and when I click it again it shows by Margin-Bottom:0. Please help me since it's just not working.
function showChat(id){
$(this).animate({marginBottom : "0"}).removeClass("hidden_box").addClass("active_box").removeAttr('onclick').click(function(){
hideChat(Id);
});
}
function hideChat(id){
$(this).animate({marginBottom : "-270px"}).removeClass("active_box").addClass("hidden_box").click(function(){
showChat(Id);
});
}
function getOnJSON(){
//Creating Variables that will be used
var from;var to;var msg_id;var msg_txt;
//Getting the data from the json file
$.getJSON("/ajax/chat.json.php",function(data){
//Repeat for each result
$.each(data.notif, function(i,data){
//Getting a var to info
from = data.from;to = data.to;msg_id = data.id;msg_txt = data.text;
//check if div exists
if ($("#chat_"+from+"_lp").length === 0){
//If not, create the div
$("#boxes").append('<div id="chat_'+from+'_lp" class="chat_box hidden_box clickable_box"></div>');
//Add the senders name
$("#chat_"+from+"_lp").append('<div id="'chat_+from+'_nick" class="chat_name">'+from+'</div>');
//Add the chats UL
$("#chat_"+from+"_lp").append('<ul id="chat_'+from+'_txt" class="chat_txt"></ul>');
//Add the message text
$("#chat_"+from+"_lp").append('<li id="' + msg_id + '">'+ msg_txt+'</li>');
//Add event handler for each div
$('#chat_'+from+'_lp').click(function() {showChat(this);});
//If div exists just add the text
}else{
//Add the message text
$("#chat_"+from+"_txt").append('<li id="' + msg_id + '">'+ msg_txt+'</li>');
//Add event handler for each document
$('#chat_'+from+'_lp').click(function() {showChat(this);});
//Close If
}
//Close data for each item
});
//Close JSON
});
//Close Function
}
UPDATE 2: in order to stop making and appending things, I made an unique HTML string that is going to be appended.
new_chat_string = '<div id="chat_'+from+'_lp" class="chat_box hidden_box clickable_box"><div id="'chat_+from+'_nick" class="chat_name">'+from+'</div><ul id="chat_'+from+'_txt" class="chat_txt"><li id="' + msg_id + '">'+ msg_txt+'</li></ul></div>';
$("#boxes").append(new_chat_string);
use class instead of id
<div id="chat_sender_lp" class="chat_box hidden_box clickable_box sender-click"
then
$('.hidden_box.sender-click').live('click', function(){
$(this).slideToggle(500);
});
After:
$("#boxes").append('<div id="chat_'+from+'_lp" class="chat_box hidden_box clickable_box" ><div id="name">'+from+'</div><ul id="chat_'+from+'_txt" class="chat_txt"><li id="' + msg_id + '">'+ msg_txt+'</li></ul></div>');
Add the event handler for the inserted element:
$('#chat_'+from+'_lp').click(function() { showChat(this) })
"this" passes a DOM reference to itself.
Keep in mind that you're adding: <div id="name"> every time. IDs must be unique. Use a class name instead.
EDIT:
Appending to the DOM is really quite slow. It's actually more efficient to build up your HTML as a string and just insert it in one go. Also, you only really need to stick and ID on the wrapping element. Everything else can be derived from that using a jQuery selector. It helps you write much cleaner code.
Here's the string you need to append:
'<div id="chat_'+msg_id+'" class="chat_box hidden_box clickable_box">
<div class="chat_name">'+from+'</div><ul class="chat_txt"><li>
'+msg_txt+'</li></ul></div>'
If you wanted to select chat name later, you'd use: $('chat_1 .chat_name').html()
It also makes more semantic sense to hook up your click handler to an A tag. So you'd use:
$('#chat_'+msg_id).find('a').click(function() {showChat(this);});
The code is a lot cleaner and easier to follow this way. I hope this helps.