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.
Related
I'm currently having trouble understanding what's going on with this code
$("#table").on("click", ".plusRow", function(event){
var name = this.getAttribute("table-data");
tableData.addData(name, 0, 1);
displayTable();
});
I understand that the first part should go something along the lines of
document.getElementById("table").addEventListener("click", function(event)
but im having trouble understanding where the ".plusRow" class should go, is it added onto the eventlistener? or how would this code be better translated back to regular Javascript.
This code snippets binds a listener on a single element (the table) and delegates it to its children which means that it will only run the event handler when it bubbles up to one or multiple elements that match the predicate (having a "plusRow" class).
With event delegation you could do:
let table = document.getElementById('table');
table.addEventListener('click', event => {
const elem = event.target;
if (elem.classList.contains('plusRow')) {
const name = elem.getAttribute("table-data");
tableData.addData(name, 0, 1);
displayTable();
}
});
Here we have to keep in mind that this code will always run when a child of the table is clicked but will only update the table when the target matches the predicate.
Without using event delegation you could do the following which will have similar results but behaves quite differently:
let tableElem = document.getElementById('table');
// To keep this simple we assume there is only one button
let button = tableElem.getElementsByClassName('plusRow')[0];
button.addEventListener('click', event => {
const name = event.currentTarget.getAttribute("table-data");
tableData.addData(name, 0, 1);
displayTable();
})
This version will only ever run when the first child of the table with a class of "plusRow" is clicked. Please note that this is just an example because if there is no element with such class an exception will be raised when we try to bind the event listener.
So I've come up with a dummy possible solution example using querySelector and querySelectorAll. Let me know if anyone sees an issue with the suggested solution.
function delegate(parentSelector, eventType, childSelector, callback) {
//lookup the parent element
var parent = document.querySelector(parentSelector);
//put the event listener for the event on the parent
parent.addEventListener(eventType, function(event) {
//get the element that caused the event
var element = event.target;
//find all the children in the parent that match the child selector,
//at this point in time
var children = parent.querySelectorAll(childSelector);
//if the element matches a child of the parent that matched the
//childSelector, we want to do our callback
for (var i = 0; i < children.length; i++) {
if (children[i] === element) {
callback();
break;
}
}
});
}
delegate('#table', 'click', '.addRow', function() {
document.querySelector('#table').innerHTML += `
<tr>
<td>Something</td>
<td><button class="addRow">Add Row</button></td>
</tr>
`;
});
<table id="table">
<tr>
<td>Something</td>
<td><button class="addRow">Add Row</button></td>
</tr>
</table>
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.
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();
...
}
});
i have table and it has 2 tag. when i click hidden button then will dissapear by using $().empty(). but i don't know how to restore.
i don't want to use $().append or something like add new data. I want restore it.
How can i do that. thanks
<script type="text/javascript">
$(function(){
$('#hidden').click(function(){
$('.hidden').empty();
});
$('#restore').click(function(){
// restore defaults
});
});
</script>
<table>
<tr class="hidden">
<td>a</td>
<td>b</td>
</tr>
<tr>
<td>c</td>
<td>d</td>
</tr>
</table>
<input type = "button" id ='hidden' value="hidden"/>
<input type = "button" id ='restore' value="restore"/>
Instead of emptying, you can simply hide and show the items.
$('#hidden').click(function(){
$('.hidden').hide();
});
$('#restore').click(function(){
$('.hidden').show();
});
Why not just using hide and show:
$(function(){
$('#hidden').click(function(){
$('.hidden').hide();
});
$('#restore').click(function(){
// restore defaults
$('.hidden').show();
});
});
If you don't want to simply hide it (as already suggested), you can save it in an object.
$(function(){
var data;
$('#hidden').click(function(){
data = $('.hidden').html();
$('.hidden').empty();
});
$('#restore').click(function(){
$('.hidden').html(data);
});
});
empty() will remove content from dom, so it's lost forever
If you want only hide content, you should use
$.hide()
and then
$.show()
There's no way to restore content after calling .empty(). There is another function: .detach() that detaches selected elements from the DOM, but not fully erase them. After detaching the element can be appended again with .append() or .prepend().
I have a scenario like
<table>
<thead>
<tr>
<th id ="myid1">header1</th>
<th id ="myid2">headre "2</th>
<th id ="myid3">header3</th>
</tr>
</thead>
<tr>
<td>v1</td>
<td>v2</td>
<td>v3</td>
</tr>
<tr>
<td>v10</td>
<td>v11</td>
<td>v12</td>
</tr>
<tr>
<td>v20</td>
<td>v21</td>
<td>v22</td>
</tr>
<tr>
<td>v30</td>
<td>v31</td>
<td>v32</td>
</tr>
</table>
there can be thousands of row .
i need to get the id of the td on which that perticulat td belongs to.
for example . if i click the third td of third row .. i should get the id of corresponding th , here it is myid3 (here its hard coded but it will set based on the value from server side)
$('tbody td').live('click', function() {
var idOfTh = ??
});
$(function(){
$('td').click(function() {
alert($('table th').eq($(this).index()).attr('id'));
});
});
Working JSfiddle: http://jsfiddle.net/6etRb/
You only need to use live delegation if the table rows are being added dynamically, and in that case you should use .on() as described by others.
You can use eq() method, try the following:
$('tbody td').live('click', function() {
var ind = $(this).index()
var idOfTh = $('thead th:eq('+ind+')').attr('id')
});
Please note that live() is deprecated you can use on() instead.
First of all, the .live() function has been deprecated. If you want to delegate events, use either .on() (jQuery 1.7+) or .delegate(). I'll assume you're using .on() for the rest of this, but there's only a minor syntax change (switch the first two arguments) if you have to use .delegate().
$(document).on('click', 'tbody td', function() {
var tdIndex = $(this).index();
// ^ gets the index of the clicked element relative to its siblings
var thId = $('thead th:eq(' + tdIndex + ')')[0].id;
// ^ selects the th element in the same position in its row, then gets its id
});
The following answer is wrong, but I'm keeping the edit as it may help someone
$('tbody td').live('click', function() {
var tdIndex = $(this).parent('tr').indexOf($(this));
var idOfTh = $(this).parent('table').find('tr').eq(tdIndex);
});
Untested, but theoretically should work.
CORRECTION
The above is incorrect, as pointed out by Felix Kling below. The correct way to get the index is simply by calling $(this).index(). I had presumed this would find the position of $(this) within the matched selector (in this case, every <td> in the <tbody>), but actually, if you pass the index() function no arguments, it finds the index relative to its siblings. Thanks to Felix for pointing out the relevant documentation.
Here's a way you could do it:
$('td').click(function() {
var colNum = $(this).parent().children().index($(this)) + 1;
alert($('#myid' + colNum).text());
});
jsfiddle: http://jsfiddle.net/p8SWW/4/
The handler can looks like:
function() {
var self = this
var th_num = 0;
$(this).parent().children().each(function(num){
if (this == self) {
th_num = num;
return false
}
});
var th_id = $('thead th').eq(th_num).attr('id');
}