Showing/Hiding DOM for only one element - javascript

I basically have multiple elements like this:
<tr class="class1">
...
</tr>
<tr class="class1">
...
</tr>
<tr class="class1">
...
</tr>
<tr class="class1">
...
</tr>
etc. I want to request the DOM like this:
dom.query(".class1").live("click", function() {
if (dom.query("#div1InsideClass1").is(':visible')) {
dom.query("#div1InsideClass1").hide();
...
} else {
dom.query("#div1InsideClass1").show();
...
}
});
But the way that this function works is that it does the function for every single instance of the above, as well as every instance of #div1InsideClass1. Is there a way to detect which particular tr element was clicked on? I heard the .next() function usually helps, but I'm not sure.

You have a root problem: there can't be duplicated ids on your html. You'll need to address that first of all.
Assuming you add a css class to those divs named div1InsideClass1, this should do the trick
$(".class1").live("click", function() {
$(this).find('.div1InsideClass1').toggle();
});
If you need to keep using the if statement, this code is equivalent but allows you to add extra logic within if else
$(".class1").live("click", function() {
var $this = $(this);
var $div = $this.find('.div1InsideClass1');
if ($div.is(':visible')) {
$div.hide();
...
} else {
$div.show();
...
}
});

Related

Jquery nested div datatable click event

This is my first post as I usually find answers (or similar ones) to my questions easily via Google and StackExchange sites. However, I've tried researching various methods, but I am not finding one that meets what I am trying to do. Hopefully someone smarter than me can help me figure this out.
My main page has a "InitiativeContainer" DIV. This DIV loads content into sub DIV containers ListDetails and InitiativeDetails. These sub containers are separate pages loaded into the sub DIVs so that the entire main page is not reloaded, only these content containers. The mainpage loads with the ListDetails DIV populated and is a seperate page with a DataTable named tblDetails. I want to grab the ID of the row that is clicked on in the Datatable, and return that ID as a variable to the parent page so that it can be passed to the InitiativeDetails page.
Right now, I can achieve an alert with getKeyValue, but only after 2 clicks. The 1st click does nothing, but the second and following clicks provide the ID in an alert. The 2 clicks is not user friendly and has to be corrected. It is as if the ListDetails container is not being "initialized" or the "focus" set and the first click initializes/sets the focus of the DIV and the second click does what it is supposed to. Code Below:
Main Page snippet:
<div class="InitiativeContainer">
<div id="ListDetails"></div>
<div id="InitiativeDetails"></div>
</div>
<script type="text/javascript">
$(document).ready(function() {
ListDetails.onload=ListLoad();
});/*End (document).ready*/
</script>
<script>
function ListLoad() {
var urlListDetails = './cfm/Initiative_List.cfm';
var ListDetails = $('#ListDetails');
$.ajax({
beforeSend: function() {
ListDetails.html('<b>Loading...</b>');
},
success: function(html) {
ListDetails.html(html);
},
url: urlListDetails
}); /*End Ajax*/
}; /*End ListLoad*/
ListLoad();
function DetailLoad(InitiativeID) {
var InitiativeID = 1
var urlInitiativeDetails = './cfm/Initiative_Info.cfm?InitiativeID=' + InitiativeID;
var InitiativeDetails = $('#InitiativeDetails');
$.ajax({
beforeSend: function() {
InitiativeDetails.html('<b>Loading...</b>');
},
success: function(html) {
InitiativeDetails.html(html);
},
url: urlInitiativeDetails
}); /*End Ajax*/
} /*End DetailsLoad*/
function getKeyValue(key){
var keyValue = key
alert('key Value: '+keyValue)
}
$('#ListDetails').on('click',function(event) {
// Get project_key
$('#tblDetail tbody tr').on('click',function(event){
var k2 = $(this).find('td').first().text();
event.stopPropagation();
getKeyValue(k2);
return false;
});
return false;
});
</script>
Initiative_List.cfm page Snippet:
<div id="ListDetails" align="center" style="width:100%;">
<table id="tblDetail" class="title display compact cell-border dataTable_pointer" cellpadding="0" cellspacing="0" border="0">
<thead>
<tr>
<th>ID</th>
<th>Prospect</th>
<th>Status</th>
<th>ClientType</th>
<th>Effective</th>
<th>Approved</th>
<th>Consultant</th>
<th>Audit Request</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
<cfoutput query="qryListDetails">
<tr>
<td>#qryListDetails.ID#</td>
<td>#qryListDetails.Prospect#</td>
<td>#qryListDetails.ProspectStatus#</td>
<td>#qryListDetails.ClientType#</td>
<td>#dateFormat(qryListDetails.EffectiveDate,"yyyy-mm-dd")#</td>
<td>#qryListDetails.Approved#</td>
<td>#qryListDetails.Consultant#</td>
<td>#dateFormat(qryListDetails.AuditRequestDate,"yyyy-mm-dd")#</td>
<td>#qryListDetails.Completed#</td>
</tr>
</cfoutput>
</tbody>
</table>
</div>
Is the issue that I have a nested click event inside of a click event? If so how could I handle this better? I am looking to understand what I am doing wrong in addition to a better code solution. Thank you in advance.
The #tblDetail tbody tr click handler should be defined outside of your ListDetails click handler. That is likely causing the click-twice issues.
I don't see what the ListDetails handler is supposed to be doing, maybe we can just omit that and have the end of your code snippet look something like this:
function getKeyValue(key){
var keyValue = key
alert('key Value: '+keyValue)
}
$('#ListDetails').on("click", "#tblDetail tbody tr", function(event) {
var k2 = $(this).find('td').first().text();
event.stopPropagation();
getKeyValue(k2);
return false;
});
</script>
It seems you were on the right track, the nesting of click handlers caused the inner handler to be defined only after the outer click handler had fired. After the first click, the inner handler starts working.

Correctly binding event handlers jQuery

I am trying to neatly package up some functionality that adds editing controls to a table cell. Below is an example of what I am trying to achieve.
What I want to know is if this is the correct way to do this. I end up having to re-bind the event handlers when I empty the cell. I think jQuery removes them but I am not certain. I expected them to remain since I have saved the DOM elements within the ScoreManager object.
<div id="main">
<table id="points-table">
<thead>
<th>First Name</th>
<th>Last Name</th>
<th>Points</th>
</thead>
<tr>
<td>Joe</td>
<td>Bloggs</td>
<td class="points">
<span>100</span>
<button>edit</button>
</td>
</tr>
<tr>
<td>Jiminy</td>
<td>Cricket</td>
<td class="points">
<span>77</span>
<button>edit</button>
</td>
</tr>
</table>
</div>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript">
window.onload = init;
var ScoreManagers = [];
function init() {
$('#points-table .points').each(function(){
ScoreManagers.push( new ScoreManager(this) );
});
}
var ScoreManager = function(cell) {
this.cell = $(cell);
this.edit = $('button', this.cell);
this.points = $('span', this.cell);
this.scoreInput = $('<input>');
this.submit = $('<button>Submit</button>');
this.cancel = $('<button>Cancel</button>');
this.init();
};
ScoreManager.prototype.init = function() {
this.edit.bind('click', $.proxy(this.showEditControls, this));
};
ScoreManager.prototype.showEditControls = function(e) {
this.cell.empty();
this.cell.append(this.scoreInput, this.submit, this.cancel);
this.submit.bind('click', $.proxy(this.savePoints, this));
this.cancel.bind('click', $.proxy(this.cancelEdit, this));
};
ScoreManager.prototype.cancelEdit = function() {
this.cell.empty();
this.cell.append(this.points, this.edit);
this.edit.bind('click', $.proxy(this.showEditControls, this));
}
ScoreManager.prototype.savePoints = function() {
this.cell.empty();
this.points.text(this.scoreInput.val());
this.cell.append(this.points, this.edit);
this.edit.bind('click', $.proxy(this.showEditControls, this));
}
</script>
You should take a look at event delegation and event bubbling in browsers, the PPK blog is a good place.
Then take a look at jQuery on method which implements delegation in an elegent way.
Now bind events to the top element under consideration that doesnt get removed added to DOM, it can be body also, and delegate to the element you want.
$('#points-table').on('click', '.points', function(){
//what should be done when you click a point element
});
bind will not work after element is removed. It will attach an event to all already available elements, but if you remove that element - binidng will be lost. Newly added elements will have no binding too. You may find usefull jQuery.live which allows to bind an event to elements with specified selector no matter if it already exists or will be added later. But if you are using latest jQuery, you may need to use alternatives as it is depricated. Also you may find usefull to use .detach() instead of .empty() because detach keeps event handler bindings. But you will need to modify your code as this.cell.detach(); will remove whole cell) instead of its children only.

jQuery parent() in table

I want to access the div tag in the following HTML code:
<table>
<div class="rowBound">
<tr onclick="expandLastResultDetails(this);">
<td class="c1">56835-14513</td>
...
</tr>
<tr class="rowDetails">
<td colspan="0">
<div style="background-color: #0F9;"> expandable
</div></td>
</tr>
</div>
</table>
But jQuery commands just give me a TBODY instead of my DIV ..
This is what I was doing in another case:
function expandNavContent(navEntryTitle) {
var content = $(navEntryTitle).parent().children('.navContent');
$(content).slideToggle('slow', function () {
// Animation complete.
});
}
What I want:
rowDetails should be animated with slideToggle if someone clicks on the tr
If I use this code:
function expandLastResultDetails(tableEntry) {
var content =$(tableEntry).parent().children('.rowDetails');
$(content).slideToggle('slow', function () {
// Animation complete.
});
}
It toggles ALL rows but it should just toggle ONE row so I nested them into a division, maybe that was wrong
Try this:
$('tr.rowDetails').find('div');
But, your Markup is not valid. tr is element of table, not for div.
To get the outer parent div:
$('tr.rowDetails').closest('div');
After edit
function expandLastResultDetails(tableEntry) {
var content =$(tableEntry).next('.rowDetails'); // will point to next tr
$(content).slideToggle('slow', function () {
// Animation complete.
});
}
Try $('.rowDetails').closest('div.rowBound');
It will bubble from the current element (outwards) until it find a div element.
Hope it helps
What command did you try? You could have used this:
$(".rowDetails tr td div");
And moreover, there cannot be a <tr> inside a <div>!
Since your markup is invalid the browser will propably insert the tbody and table for you (look at the markup in FireBug/Dev tools). Something like $(".rowDetails").parent().parent().parent() could work (in some browsers) but I'd recommend fixing the markup instead

What's the smartest way to select specific table elements in javascript?

I've got a table with hidden rows on it, like such
-visible-
-invisible-
-visible-
-invisible-
When I click on a table row, I want it to show the invisible row. Currently I have that using this function:
var grid = $('#BillabilityResults');
$(".tbl tr:has(td)").click(
function () {
$(grid.rows[$(this).index()+1]).toggle();
}
However, this table also hides the visible rows if I click on one of the (now visible) hidden rows.
I'd like the click function to only work on the specific visible rows. Currently all my invisible rows have the class "even" so I figured I could limit the click based on that. However, I can't seem to find the syntax to explain that to my function. How would I go about doing that? And, more importantly, is there a better way to approach this?
Use next:
$(".tbl tr:has(td)").click(
function () {
$(this).next().toggle();
}
);
And also if you have specific selector for odd or even:
$(".tbl tr.odd").click(
function () {
$(this).next().toggle();
}
);
But I think that the major help with my answer is to use next() that get you the next row, instead of the index process that you were doing.
var grid = $('#BillabilityResults');
$(".tbl tr:visible").click(
function () {
$(this).next('tr').toggle();
});
Use the NOT function to disregard the EVEN tr elements:
http://jsfiddle.net/7AHmh/
<table class="tbl">
<tr><td>one</td></tr>
<tr class="even" style="display:none"><td>two</td></tr>
<tr><td>three</td></tr>
<tr class="even" style="display:none"><td>four</td></tr>
</table>​
$(".tbl tr:has(td)").not("tr.even").click(function() {
alert("Click triggered.");
$(this).next("tr").show();
});
I guess you could check for even/odd rows with the modulus operator before calling your toggling code:
function() { // your anonymous function
if (rowNumber % 2 == 0) { // only even rows get through here
// toggle code here
}
}
I hope it helps.

Struggling with performance how do I change this to use for loops?

I have a function that checks each cell in each row of a table for the edit-error class. If this is found in any cell of a particular row (The cell value hasn't passed through validation) then I remove the edit-submit class from the entire row and unselect a checkbox which is in the first td of that row.
When there are a large number of rows (1,000+) I am struggling with performance so I thought I could try for loops but I am not an expert in JavaScript so am looking for help with this.
The original code is as follows:
var checkErrors = function () {
$('#results tr:not(:first)').each(function () {
$('td:first input:checkbox', $(this)).attr('checked', 'checked');
$(this).addClass('edit-submit');
$(this).find('td').each(function () {
if ($(this).hasClass('edit-error')) {
$('td:first input:checkbox', $(this).parent('tr')).removeAttr('checked');
$(this).parent().removeClass('edit-submit');
}
});
});
}
The table has an id of #results. What I do first is check all the checkboxes and add the edit-submit class to each row then loop through all the cells checking for errors.
I am looking for any help with performance improvements on this function please.
EDIT
It would have made sense to add the table html. Apologies for that here it is:
<table id="results">
<tbody>
<tr>
<th> </th>
<th>Policy Number</th>
<th>Quota Code</th>
<th>Contract Date</th>
</tr>
<tr>
<td><input type="checkbox"></td>
<td class="edit-error">ABC123%</td>
<td>123</td>
<td class="edit-error">99/99/9999</td>
</tr>
<tr class="edit-submit">
<td><input type="checkbox" checked="checked"></td>
<td>ABC123</td>
<td>123</td>
<td>16/03/2012</td>
</tr>
</tbody>
</table>
This would show on the page:
--------------------------------------------------
| | Policy Number | Quota Code | Contract Date |
--------------------------------------------------
| | ABC123% | 123 | 99/99/9999 |
--------------------------------------------------
| X | ABC123 | 123 | 16/03/2012 |
--------------------------------------------------
There are several ways to speed up your code without going to a plain JS for loop. To name just a few:
You already have a reference to the current row within the outer .each() loop so use that rather than calling $(this).parent() within the inner loop.
If you want to process just the td elements that have a particular class you can include the class in your selector rather than calling .hasClass().
Avoid calling the same selector more than once, by storing a reference to the returned jQuery object and/or by chaining.
When providing a context as the second parameter to $() you can pass a DOM element or this directly, no need to wrap it in another $() call.
If you know that there will always be a checkbox you can uncheck it directly to save a function call.
So:
var checkErrors = function () {
$('#results tr:not(:first)').each(function () {
var $tr = $(this),
cb = $('td:first input:checkbox', this)[0];
cb.checked = true;
$tr.addClass('edit-submit')
.find('td.edit-error').each(function () {
cb.checked = false;
$tr.removeClass('edit-submit');
});
});
};
However, the inner loop on the tds isn't needed at all since you just need to test whether there are any tds in the row with the error class - you don't need to actually loop through them all. Plus for good measure here's one way to use a for loop rather than .each() - note that you can avoid the complicate not-first selector if you simply select all rows then loop from the second onwards:
var checkErrors = function () {
var $rows = $('#results tr'),
i;
for (i = 1; i < $rows.length; i++) {
var $tr = $rows.eq(i),
cb = $tr.find('td:first input:checkbox')[0];
if ($tr.find('td.edit-error').length === 0) {
cb.checked = true;
$tr.addClass('edit-submit');
} else {
cb.checked = false;
$tr.removeClass('edit-submit');
};
}
};
Try setting a variable for $(this). Also do you need to use find('td')? Would $('td',this) do the same thing but more efficiently?
Try this:
$.each($('#results .edit-error').parent(),function(){
$(this).removeClass('edit-submit').find('td:first-child input:checkbox').removeAttr('checked');
});
Use a variable for $(this).
Your selectors can be improved too : instead of $('#this', '#that'), use $('#that').find('#this')
Here is an improved version with these optimizations.
var checkErrorsOptimized = function () {
$('#results tr:not(:first)').each(function () {
var $this = $(this);
$this.find('td:first input:checkbox').attr('checked', 'checked');
$this.addClass('edit-submit');
$this.find('td').each(function () {
var $that = $(this);
if ($that.hasClass('edit-error')) {
$that.parent('tr').find('td:first input:checkbox').removeAttr('checked');
$that.parent().removeClass('edit-submit');
}
});
});
}
You can see it on this jsFiddle (using Firebug profiling) : http://jsfiddle.net/5kBsU/3/

Categories