jQuery Traversing Dynamically generated DOM (next(), prev()) - javascript

I have an HTML table and jQuery handlers to move rows up and down, using .next() and .prev(), but I also want to add new rows and after adding new row and trying to move old rows up or down they move more positions than expected. Here is an example on jsfiddle http://jsfiddle.net/3CQYN/
$(function() {
initControls();
$('.new').click(function() {
$('<tr><td>TEST</td><td>Up Down</td></tr>').appendTo($('table tbody'));
initControls();
});
});
function initControls()
{
$('.down').click(function() {
var parentRow = $(this).closest('tr');
parentRow.insertAfter(parentRow.next());
});
$('.up').click(function() {
var parentRow = $(this).closest('tr');
parentRow.insertBefore(parentRow.prev());
});
}
Try to move rows up and down, then add few new rows and move the OLD rows up and down again and you'll see the problem.

Every time you add a new row, you rebind the handlers, ending up with multiple handlers bound to individual up and down links. Instead, use event delegation (only executed once, on DOM ready):
$(document).on('click', '.down', function() {
// ...
});
$(document).on('click', '.up', function() {
// ...
});
http://jsfiddle.net/Gt4Zq/
Note that if you can find a container to bind to that is closer to the elements than document, that would be preferable.

Related

How can I call slideDown() on an new element that renders?

I have an unordered list and the li elements inside of it are rendered based on the number of items in an array in my backend.
As new items are added to an array, a corresponding li element is rendered and pops up in my list. Is it possible to give each new li element a slideDown() animation?
Yes, but you need to use .on() or .live() depending on which version of JQuery you're using.
http://api.jquery.com/on/
http://api.jquery.com/live/
Ok so try this wrap this in a function
function doSlideDown(){
$('ul > li > input[type="checkbox"]').on("click", function() {
var parent = $(this).parent("li");
if($(this).is(":checked") === true) {
// move to the top
$(parent).slideUp(300, function() {
$(parent).prependTo($(parent).parent());
$(parent).slideDown(300);
});
} else {
$(parent).slideUp(300, function() {
$(parent).appendTo($(parent).parent());
$(parent).slideDown(300);
});
}
});
}
then add this outside of your document ready function
$(document).ajaxComplete(function() {
doSlideDown();
});
That will update the DOM every time your run some ajax and append a new item.

Remove clicked item from list JQuery

I have a simple list where you can add items in html/jquery.
I want to remove a specific item when i click on it in the list.
I can add items, they show up but the remove code is not working.
Remove code
$('#items a.delete').on('click', function(){
$(this).parent().remove();
});
This is my code:
$(document).on('pagebeforeshow', '#home', function(event) {
homepage();
});
$('#items a.delete').on('click', function(){
$(this).parent().remove();
});
function homepage(){
// Fetch the existing objects
objects = getObjects();
// Clear the list
$('#items').find('li').remove();
// Add every object to the objects list
$.each(objects, function(index, item){
element = '<li data-icon="delete">'+item.title+'</li>';
$('#items').append(element);
});
$('#items').listview();
$('#items').listview("refresh");
}
function getObjects(){
// See if objects is inside localStorage
if (localStorage.getItem("objects")){
// If yes, then load the objects
objects = JSON.parse(localStorage.getItem("objects"));
}else{
// Make a new array of objects
objects = new Array();
}
return objects;
}
homepage() gets called when you enter the page, it repopulates the list.
Objects are stored in localstorage.
HTML:
<ul id="items" data-role="listview" data-inset="true"></ul> <br>
You are binding the events before you are appending them to the DOM. When the elements are then appended, you'll need to bind the event after, or use event delegation to find that element. A possible fix would be to move this code block
$('#items a.delete').on('click', function(){
$(this).parent().remove();
});
after you call the homepage() function.
You are dynamically adding new elements, so you need to target the parent element on your event binding:
$('#items').on('click', 'a.delete', function(){
$(this).parent().remove();
});

Moving rows to and from Gridviews using .clone, .cloneNode and .append

I have two Gridviews, one loaded with data and the other not. When I double click an item from gvDisplayAvailItems, I want the row to go to gvDisplaySelectedItems, and vice-versa. The Grids are also multi-select, with a button allowing all selected items to be moved. gvDisplaySelectedItems differs by 1 additional input column.
AddDisplayParams() is called when the button is pressed.
function AddDisplayParams() {
var rows = $("#gvDisplayAvailItems").find('tr.selected');
rows.each(function (index, element) {
element.classList.remove("selected");
var newRow = element.cloneNode(true);
newRow.appendChild(customIdTb.cloneNode(true));
$("#gvDisplaySelectedItems").append(newRow);
element.remove();
});
}
AddDisplayParam is called on double-click.
function AddDisplayParam(param) {
var newRow = param.clone(true);
newRow.append(customIdTb.cloneNode(true));
$("#gvDisplaySelectedItems").append(newRow);
param.remove();
}
And here is how I trigger the selection and double clicks.
$("#gvDisplaySelectedItems tr").click(function () {
$(this).toggleClass("selected");
});
$("#gvDisplaySelectedItems tr").dblclick(function () {
RemoveDisplayParam($(this));
});
$("#gvDisplayAvailItems tr").click(function () {
$(this).toggleClass("selected");
});
$("#gvDisplayAvailItems tr").dblclick(function () {
AddDisplayParam($(this));
});
When I both double click and mass select rows on gvDisplayAvailItems, the rows are moved to gvDisplaySelectedItems correctly. However, nothing is triggered for the functions of gvDisplaySelectedItems for rows that were added via AddDisplayParams. Those added by AddDisplayParam can be highligted, but when double clicked only append another textbox to the row in gvDisplaySelectedItems.
So it seems that .clone and .cloneNode are doing something very different here despite having basically the same function. Could someone please explain why one partially works, while the other does not? And also, why my functions for the second grid are not triggered upon single and double click?
I'd suggest to try delegated event handlers.
E.g.
$("#gvDisplaySelectedItems").on("click", "tr", function () {
$(this).toggleClass("selected");
});
instead of
$("#gvDisplaySelectedItems tr").click(function () {
$(this).toggleClass("selected");
});
and so on for other event handlers.
More info
Regarding other improvements - you don't have to clone()/append()/remove() to move an element. Just doing append() to a new parent will effectively move it since an element can have only one parent each moment of time.
Example: JSFiddle

jquery: how to add event handler to dynamically added element without adding it to existing element again

I'll try to explain my problem:
I have a website where the user dynamically adds elements. They all belong to the "toBuy" class. Whenever a new element is added to this class I need to attach a click-handler to only this element but not to all others. To keep my code clean I want to have a function that does this work. Here is what i've tried:
this is how the stuff is added:
$("#addItemButton").click(function(){
var item= $('#item').val();
$('#item').val("");
var quantity= $('#quantity').val();
$('#quantity').val("");
var comment=$('#addComment').val();
$('#addComment').val("");
//construct new html
var newitem="<div class='toBuyItem'><div class='item'>";
newitem+=item;
newitem+="</div><div class='quantity'>";
newitem+=quantity;
newitem+="</div><div class='comment'><img src='img/comment";
if(comment==""){
newitem+="_none"
}
newitem+=".png' alt='Comment'></div><div class='itemComment'>"
newitem+=comment;
newitem+="</div></div>";
$("#toBuyItems" ).prepend( newitem );
toggle("#addItemClicked");
initializeEventListeners();
});
then this is the initializeEventListeners function (which I also run when the page loads so that the existing elements have the event handlers already:
function initializeEventListeners(){
$(".toBuyItem").click(function(){
console.log($(this).html());
console.log($(this).has('.itemComment').length);
if($(this).has('.itemComment').length != 0){
console.log("toggling");
$(this).addClass("toggling");
toggle(".toggling .itemComment");
$(this).removeClass("toggling");
}
});
}
function toggle(item){
$( item ).slideToggle(500);
}
now apparently what happens is that when a new element is added the existing elements get a new event handler for clicking (so they have it twice). Meaning that they toggle on and off with just one click. Probably it's damn simple but I cannot wrap my head around it....
EDIT:
so this works:
$(document).on('click', '.toBuyItem', function(){
if($(this).has('.itemComment').length != 0){
console.log("toggling");
$(this).addClass("toggling");
toggle(".toggling .itemComment");
$(this).removeClass("toggling");
}
});
Use jquery's on method. This way you have to add event only once. This will be added automatically to dynamically added elements.
$(document/parentSelector).on('click', '.toBuyItem', function() {
// Event handler code here
});
If you are using parentSelector in the above syntax, it has to be present at the time of adding event.
Docs: https://api.jquery.com/on
You can use jQuery.on method. It can attach handlers to all existing in the DOM and created in future tags of the selector. Syntax is as follows:
$(document).on('click', '.toBuyItem', function(){
//do onClick stuff
})
As others have suggested, you can delegate click handling to document or some suitable container element, and that's probably what I would do.
But you could alternatively define a named click handler, which would be available to be attached to elements already present on page load, and (scope permitting) to elements added later.
You might choose to write ...
function buy() {
if($(this).has('.itemComment').length != 0) {
$(this).addClass("toggling");
toggle(".toggling .itemComment");
$(this).removeClass("toggling");
}
}
function initializeEventListeners() {
$(".toBuyItem").on('click', buy);
}
$("#addItemButton").on('click', function() {
var item = $('#item').val(),
quantity = $('#quantity').val(),
comment = $('#addComment').val();
$('#item', '#quantity', '#addComment').val("");
//construct and append a new item
var $newitem = $('<div class="toBuyItem"><div class="item">' + item + '</div><div class="quantity">' + quantity + '</div><div class="comment"><img alt="Comment"></div><div class="itemComment">' + comment + '</div></div>').prependTo("#toBuyItems").on('click', buy);// <<<<< here, you benefit from having named the click handler
$newitem.find(".comment img").attr('src', comment ? 'img/comment.png' : 'img/comment_none.png');
toggle("#addItemClicked");
});

click event on a cell and not entire row - with FIDDLE

I am currently using this code for row click event in a bootstrap table
$('#myTable').bootstrapTable().on('click-row.bs.table', function (e, row, $element)
{
//....my operation
}
The problem is this triggers for the entire row and I want to be able to trigger it for a single cell.
Note I am using the arguments, row and $element
Here is the FIDDLE
$element is the entire row, you cannot know what cell have been clicked by this way,
bootstrap table do not have cell click event, so you need manually add click event on last cell and fill your needed vars yourself
$('#table').bootstrapTable({
data: data
}).on('click','td:last-child',function(){
var $t = $(this), $row = $t.parent(), i = $row.index(), row = data[i];
var $firstTd = $row.children().eq(0);
if($firstTd.data("haveTable") !== true){
$firstTd.data("haveTable",true);
$firstTd.append('<table class="newTable"><tr><td>NEW TABLE</td></tr></table>');
} else {
$firstTd.data("haveTable",false);
$firstTd.children("table").remove();
}
});
https://jsfiddle.net/e3nk137y/1663/
try
$('#myTable').bootstrapTable().on('click-row.bs.table td', function (e, row, $element)
{
//....my operation
}
Based on my guess, when you click on that cell, you probably want only the triggers for that particular cell to execute and not for the whole table.
This can be achieved by stopping the propagation of event from the table cell to the table row.
$('#myTable').bootstrapTable().on('click-row.bs.table', function (e, row, $element){
//stop the propagation for target cell
if($(event.target).hasClass('myClass')) e.stopPropagation();
//the code for handler
...
...
}
There are many ways stopPropagation can be utilized to do this thing. This was just one of those.
Since I dont know how your other triggers are set, I can't write code that works with those with assumptions.
Try this:
$('#myTable tr td:last-child').on('click', function() {
if( $('.newTable', $(this)).length ) {
$('.newTable', $(this)).remove();
} else {
$(this).append( '<table class="newTable"><tr><td>NEW TABLE</td></tr></table>' );
}
});

Categories