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')
Related
I am a bit confused here. I have a div which contains comments. Then when you click on the more comments icon, it calls php via ajax and appends the results to a div then slides it down. That part works fine. Then I have a hide icon which when you click on it, it collapses the expended div that was showing more comments. The problem, I am having is that if I collapse the div then click on show more comments again, the div no longer expands.
Below is the code which handles the more and hide clicks. Any tips would be appreciated.. Thanks
$("[id^='show_more_comments_']").on( 'click', function(e) {
var pagenum = $(this).data('newpage');
var photoid = $(this).data('photoid');
var commentsdiv = 'morecomments_' + photoid;
var moredots = 'show_more_comments_' + photoid;
var lessdots = 'hide_more_comments_' + photoid;
$.ajax({
type: "POST",
url: "showmorecomments.php",
data: {page: pagenum, photoid: photoid},
success: function(data){
$("#" + moredots).data('newpage', pagenum + 1);
$("#" + lessdots).show();
$(data).hide().appendTo("#" + commentsdiv).slideDown(500);
}
});
});
$("[id^='hide_more_comments_']").on( 'click', function(e) {
var photoid = $(this).data('photoid');
var commentsdiv = 'morecomments_' + photoid;
var moredots = 'show_more_comments_' + photoid;
var lessdots = 'hide_more_comments_' + photoid;
$("#" + commentsdiv).slideUp(500);
$("#" + lessdots).hide();
$("#" + moredots).data('newpage', 1);
$("#" + moredots).data('photoid', photoid);
});
If I set an alert to see the data values, they are correct. Its just no longer expanding the comments afyer I close them and try to open them again.
Thanks
JT
Figured it out, slideUp hides the comments div so you must use a callback function to show the comments div again.
$("#" + commentsdiv).slideUp(500, function(){
$(this).empty().show()
})
Check this code snippet:
var data = '<div id="content">'
data += 'These are the comments to be added to the comment div'
data += '<ul>'
data += ' <li>Comment 1</li>'
data += ' <li>Comment 2</li>'
data += ' <li>Comment 3</li>'
data += '</ul>'
data += '</div>';
$(function() {
$("#showBtn").click(function() {
$(data).hide().appendTo("#morecomments_1").slideDown(500)
})
$("#hideBtn").click(function() {
$("#morecomments_1").slideUp(500, function(){
$(this).empty().show()
})
})
})
<div id="morecomments_1"></div>
<button id="showBtn">Show</button>
<button id="hideBtn">Hide</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
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
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/
function createList() {
var list = '';
var listID;
$.each(obj_JSON.colors, function(index, value) {
listID = "clr_" + index;
list = list + "<div id=" + listID + " alt='" + obj_JSON.colors[index].name + "'></div>"
clrList.html(list);
updateListColours(listID);
});
}
function updateListColours(x) {
$('#' + x).css({"background-color":"rgb(255, 0, 0)", "height":"25px", "width":"25px"});
}
When I watch it get created. The first div gets the style applied to it. Then the second gets created and the style is wiped from the first div and applied to the second and it goes on until the list is complete...
Why is this happening and how can I apply the style to each div? Expecting answer that shows i've done something really stupid as usual
Because this line clrList.html(list);, you are removing the previous element then add new created.
Instead, do it with:
$.each(obj_JSON.colors, function(index, value) {
listID = "clr_" + index;
list = "<div id=" + listID + " alt='" + obj_JSON.colors[index].name + "'></div>"
clrList.append(list);
updateListColours(listID);
});
There's a much easier solution:
function createList() {
$.each(obj_JSON.colors, function() {
var d = document.createElement('div');
d.setAttribute("alt",this.name);
d.className = "listcolour";
clrList.appendChild(d);
// ^ assumes `clrList` is a DOM node, not a jQuery object
});
}
And CSS:
.listcolour {
width: 25px;
height: 25px;
background-color: #f00;
}
So I'm working on this think where I need to generate elements and then bind events to them. Every time I generate an element I unbind and rebind the events to the generated elements with a for loop. These functions seem to trigger, but they get the wrong index (which tells the code which element to work with).
Check out the code at jsFiddle http://jsfiddle.net/6UgYe/4/
Anything modular that solves this will do. Also feel free to comment on my code. I wrote most of this last year when I had just begun with javascript.
Regards,
Akke
EDIT: Here is the solution in action: http://jsfiddle.net/nwH8Z/3/ it calculates VAT-Free prices on blur.
Change your bindEmAll function to look like this -
function bindEmAll()
{
$('#container').on('blur', 'input[id$=".5"]', function(){
$('#Errorfield').append('Current box is ' + this.id + '<br/>').append(num_format(this.value) + '<br />')
})
}
It makes all input boxes with IDs that end in '.5' append their ids and values, handled by your num_format() function, to #Errorfield. The event handler is attached to all input boxes inside #container, even if they are added dynamically.
And remove bindEmAll() from your click handler for #addTT; otherwise the event handlers will be bound as many times as you've clicked addTT, which makes things quite messy ;-)
$('#addTT').click(function() {
addTT('#container');
});
Updated fiddle here.
The problem is happening because the blur event handler is not being run until well after the loop finished. The order of execution is:
Attach event handler to blur event on item 1
Attach event handler to blur event on item 2
...some time later...
Actually invoke the blur event
By the time your event handler is invoked the value of the variable i from the loop has changed to the index of the last value, so that is what is being used in the event handler.
To get around this you can just put your code inside a closure:
(function(i) {
$('#container input#box' + i + '\\.5').unbind();
$('#container input#box' + i + '\\.5').blur(function() {
//error processing function;
$('#Errorfield').append('Current box is $(\'#container input#box' + i + '\\.5\')<br />');
});
$('#container input#box' + i + '\\.5').blur(function() {
$('#container input#box' + i + '\\.5').val(num_format($('#container input#box' + i + '\\.5').val()));
$('#Errorfield').append('Current box is $(\'#container input#box' + i + '\\.5\')<br />');
});
})(i);
I've updated your fiddle here
boxes = 1;
function num_format(input) {
//For demo purporses, we only parseInt()+5
ret = parseFloat(input) + 5;
return ret;
}
function addTT(parentElement, arg1, arg2, arg3, arg4, arg5) {
if (!arg1) {
arg1 = "";
}
if (!arg2) {
arg2 = "";
}
if (!arg3) {
arg3 = "";
}
if (!arg4) {
arg4 = "";
}
if (!arg5) {
arg5 = num_format((0.00).toFixed(2));
}
row = $('<tr></tr>').attr('id', boxes);
cell1 = $('<td class="inputcell"></td>');
cell2 = $('<td class="inputcell"></td>');
cell3 = $('<td class="inputcell"></td>');
cell4 = $('<td class="inputcell"></td>');
cell5 = $('<td class="inputcell"></td>');
input1 = $('<input></input>').attr('style', 'width:100px;').attr('id', 'box' + boxes + '.1').attr('name', 'box' + boxes + '_1').attr('type', 'text').attr('value', arg1);
input2 = $('<input></input>').attr('style', 'width:100px;').attr('id', 'box' + boxes + '.2').attr('name', 'box' + boxes + '_2').attr('type', 'text').attr('value', arg2);
input3 = $('<input></input>').attr('style', 'width:93px;').attr('id', 'box' + boxes + '.3').attr('name', 'box' + boxes + '_3').attr('type', 'text').attr('value', arg3);
input4 = $('<input></input>').attr('style', 'width:149px;').attr('id', 'box' + boxes + '.4').attr('name', 'box' + boxes + '_4').attr('type', 'text').attr('value', arg4);
input5 = $('<input></input>').attr('style', 'width:90px;').attr('id', 'box' + boxes + '.5').attr('name', 'box' + boxes + '_5').attr('type', 'text').attr('value', arg5);
$(cell1).append(input1);
$(cell2).append(input2);
$(cell3).append(input3);
$(cell4).append(input4);
$(cell5).append(input5);
$(row).append(cell1);
$(row).append(cell2);
$(row).append(cell3);
$(row).append(cell4);
$(row).append(cell5);
$('#tBoxes').append(row);
boxes++;
}
function subTT(parentElement) {
boxes = boxes - 1;
$(parentElement + ' #' + boxes).hide(0, function () {
$(parentElement + ' #' + boxes).remove();
}
);
}
function bindEmAll() {
alert(boxes);
for (var i = 1; i <= boxes - 1; i++) {
$('#container input#box' + i + '\\.5').blur(function () {
alert($(this).val());
$(this).val(num_format($(this).val()));
//$('#container input#box' + i + '\\.5').val(num_format($('#container input#box' + i + '\\.5').val()));
//$('#Errorfield').append('Current box is $(\'#container input#box' + i + '\\.5\')<br />');
});
}
}
$('#addTT').click(function () {
addTT('#container');
bindEmAll();
});
$('#subTT').click(function () {
subTT('#container');
});
$(function () { addTT('#container'); bindEmAll(); });
Here is a small example of adding new elements and process their events:
<button id="add">+</button>
<button id="remove">-</button>
<div id="holder">
</div>
.
$('#add').on('click', function () {
$('#holder').append('<p>click me!</p>');
});
$('#remove').on('click', function () {
$('#holder p:last-of-type').remove();
});
$('#holder').on('click', 'p', function () {
alert('my id is: ' + $('#holder p').index(this));
});
And you can check it out here: http://jsfiddle.net/simo/PyKDz/