问题
I have a table of students and in each row are their names, a select list to select their attendance for their lesson and then a "Message" link when clicked will popup a a dialog to send a message to the student.
The table is dynamically driven by a select list of courses. For example, a teacher selects a course and then the table is repopulated with all the students within that course. This is done through AJAX. The table body is basically getting written every time a course is selected. My problem is this, when a new course is selected, the div for the dialog becomes visible inside the cell of the Message link. I suspect the problem is to do with AJAX and not being able to rebind the link and click event. How do I therefore overcome this?
This is my table generated in PHP (http://pastebin.com/CTD3WfL6):
public function createTable($cid)
{
$userModel = new Users();
$attendanceModel = new Attendance();
$students = $userModel->getStudents($cid);
$table2 = '<table id="tutorTable">';
$tableHeaders =
'<thead>
<th>Student Name</th>
<th>Attendance</th>
<th>Message</th>
<th>Mobile</th>
<th>Parent Name</th>
<th>Message</th>
</thead>
<tbody>';
$table2 .= $tableHeaders;
foreach($students as $student)
{
$table2 .=
'<tr><td id="studentName">'.$student['firstname'].' '.$student['lastname'].'</td>
<td>
<select class="attendSelect" id="studentSelect"'.$student['id'].'>
<option value="Attended">Attended</option>
<option value="Absent">Did not Attend</option>
<option value="Excused Absent">Excused</option>
<option value="Late">Excused</option>
<option value="Excused Late">Did not Attend</option>
</select>
</td>
<td>
<a href="#MessageStudent" class="popUpLink">Message</a>
<div class="popUpDialog" id="'.$student['id'].'" title="Message '.$student['firstname'].' '.$student['lastname'].'">
<form id="studentForm" action="" method="POST">
<fieldset>
<input type="hidden" value="message_send" name="action"/>
<input type="hidden" value="'.$student['id'].'" name="studentId"/>
<textarea rows="3" cols=35" name="message"></textarea>
<input type="submit" value="Send Message"/>
</fieldset>
</form>
</div>
</td>
<td>'.$student['phone1'].'</td>
<td>Parent name goes here</td>
<td>
<a href="mailto:ParentsEmail@email.com" id="parentEmail">Message</a>
</td>
</tr>';
}
$table2 .= '</tbody></table>';
return $table2;
}
This is the jQuery to handle the dialog and the table:
/** Dialog Handler **/
$('.popUpLink').each(function()
{
$divDialog = $(this).next('.popUpDialog');
$.data(this, 'dialog', $divDialog.dialog(
{
autoOpen: false,
modal: true,
title: $divDialog.attr('title')
}));
}).on('click',function()
{
$.data(this, 'dialog').dialog('open');
return false;
});
/**AJAX to handle table **/
$('#courseSelect').on('change', function()
{
var cid = $('#courseSelect').val();
$.getJSON('?ajax=true&cid=' + cid, function(data)
{
var lessonSelect = "";
var count = 1;
/** populate select list of lessons **/
for(var i in data.lessons)
{
lessonSelect += '<option id="' + count + '" value="' + data.lessons[i].id+ '">' + data.lessons[i].name + '</option>'
count++;
};
var lessonDialog = '<p>' + data.lessons[0].name + '</p>';
var launchLessonDiv = '<a href=" ' + data.launchLesson.reference + ' ">Launch Lesson</a>';
var courseDialog = '<p>' + data.course.fullname + '</p>';
$('#lessonSelect').html(lessonSelect);
$('#lessonDialog').html(lessonDialog);//insert lesson information into lesson dialog
$('#launchLessonDiv').html(launchLessonDiv);//insert link to launch lesson
$('#courseDialog').html(courseDialog);
/**Repopulate table **/
//var lessonCount = 1;
//var table = createTutorTable(data, cid, lessonCount);
//$('table#tutorTable>tbody').html(table);
$('form#tutorTableForm').html(data.table);
});//getJSON
});//Course select
Everything works fine until a new course is selected and the textarea becomes visible inside the cell. I've only just started jQuery last month so bear with me!
回答1:
As I understands, each row has its own markup for dialogs and the code that creates those dialogs is executed only once, during page load:
/** Dialog Handler **/
$('.popUpLink').each(function()
{ ... }
But this code should be called on startup AND every time you repopulate your table, because markup for dialogs is located inside row cells. I suggest you to put this code in a function:
var initDialogs = function() {
$('.popUpLink').each(function()
{
...
}).on('click', function(){
...
});
}
Call it right after page load, and each time you repopulate table:
initDialogs();
$('#courseSelect').on('change', function()
{
var cid = $('#courseSelect').val();
$.getJSON('?ajax=true&cid=' + cid, function(data)
{
// .. lots of code here
// then you populate your table
// (can't find #formTableForm in your example though)
//$('table#tutorTable>tbody').html(table);
$('form#tutorTableForm').html(data.table);
// now your table filled, old dialogs gone.
// init dialogs again.
initDialogs();
});//getJSON
});
Also, I noticed how you create your table rows inside foreach loop. Every row will have the same id's, like this one <td id="studentName">. Having many id's repeated on the page is not OK, it can leads to problems that hard to debug.
I hope it helps.
EDIT: Just noticed, it is almost the same approach that @Lazerblade proposed.
回答2:
You should not be using click() with elements which are dynamically added to a page because this jQuery method has no way of binding future events to these elements. It's fine for static pages and documents, but not for functionality which inserts, deletes or modifies objects on the page.
Instead, you need to use on() because it allows you to bind future events. You need to implement it like this:
$(document).on('change', '#courseSelect', function()
{
// Your existing code here
}
At the moment, from the sound of things, you're only using live() as a potential substitute. Don't use this because as of jQuery 1.7 it is deprecated. Refactoring all out-of-date code like this is an absolute must in any web project -- you can't just refuse to change things because of the range and depth of existing implementation. In fact, that should only motivate you more: because if you leave the site with deprecated software something's going to go wrong.
If you're unsure about how you can change from live() to on(), see jQuery's documentation which provides a succinct and easy way to update existing functionality.
回答3:
Here's your javascript, rewritten to include modified syntax for recalling the .each() for the popUpLink after reloading. I've also bound the popUpLink to the outer form wrapper. Also, verify that your jQuery has been updated to the latest version, 1.7.2, in order to use the .on() function:
/** Dialog Handler **/
function reSetPop() {
$('.popUpLink').each(function() {
$divDialog = $(this).next('.popUpDialog');
$.data(this, 'dialog', $divDialog.dialog({
autoOpen: false,
modal: true,
title: $divDialog.attr('title')
}));
});
}
reSetPop();
$('#tutorTableForm').on('click', '.popUpLink', function() {
$.data(this, 'dialog').dialog('open');
return false;
});
/**AJAX to handle table **/
// let's assume your select, below, is part of the form and replaced as well
$('#tutorTableForm').on('change', '#courseSelect', function() {
var cid = $('#courseSelect').val();
$.getJSON('?ajax=true&cid=' + cid, function(data) {
var lessonSelect = '';
var count = 1;
/** populate select list of lessons **/
for(var i in data.lessons) {
lessonSelect += '<option id="' + count + '" value="' + data.lessons[i].id+ '">' + data.lessons[i].name + '</option>';
count++;
};
var lessonDialog = '<p>' + data.lessons[0].name + '</p>';
var launchLessonDiv = '<a href=" ' + data.launchLesson.reference + ' ">Launch Lesson</a>';
var courseDialog = '<p>' + data.course.fullname + '</p>';
$('#lessonSelect').html(lessonSelect);
$('#lessonDialog').html(lessonDialog);//insert lesson information into lesson dialog
$('#launchLessonDiv').html(launchLessonDiv);//insert link to launch lesson
$('#courseDialog').html(courseDialog);
/**Repopulate table **/
$('form#tutorTableForm').html(data.table);
reSetPop();
});//getJSON
});//Course select
回答4:
Here is a simpler example. I hope it demonstrates what you are asking. You can see a working version at http://jsfiddle.net/4wEPm/2/
When you overwrite the content of the table with ajax, the references to the dialog box is lost. I think it's easier just to reinitialize the dialog boxes again.
I am using a delegate on the table (based on your example the table element doesn't get overwritten, just the rows). This way the click listener will persist after the ajax call. Whenever the dialog div is initialized by jquery, the div is moved to the end of the page. So, I added a reference to the dialog object in the anchor, just for easy access to invoke dialog('open'). Since I am handling the initialization, and opening of the dialog box in the same click function, the dialog div needs to be hidden to begin with.
Also, in the ajax call, it's best to clean up the old dialog boxes, as new one will get created.
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" />
<div>
<table id="t1" border="1">
<tr>
<td><a href="" class="popup">message 1</a>
<div class="dlog" style="display: none">dialog 1</div>
<p>stuff</p>
<p>
<button class="btn"> faking ajax </button>
</p>
</td>
</tr>
<tr>
<td><a href="" class="popup">message 2</a>
<div class="dlog" style="display: none">dialog 2</div>
<p>stuff</p>
<p>
<button class="btn"> faking ajax </button>
</p>
</td>
</tr>
</table>
<br/><br/>
<script>
$("#t1").on('click', '.popup', function(e){
/*
initialize the dialog box on the first click of the message link
and insert a reference to the newly created dialog box, the next
time the link is clicked, don't need to initialize, just invoke open
*/
if (typeof $(this).data("dialog-ref") == "undefined"){
$(this).data("dialog-ref", $(this).next('.dlog').dialog({ modal: true }));
}else {
$(this).data("dialog-ref").dialog('open');
}
return false; // don't follow the link.
});
// this is just to fake the ajax calls and overwrites the table content.
$("#t1").on('click', '.btn', function(){
// clean up of the old dialog boxes, because the new content will have new dialog boxes.
$('#t1 .popup').each(function() {
try {
$(this).data('dialog-ref').dialog('destroy');
}catch(err) {
}
});
var x = Math.floor(Math.random() * 100);
var table = $("<tr><td><a href='' class='popup'>message "+x+"</a><div class='dlog' style='display: none'>dialog "+x+"</div><p>stuff</p><button class='btn'> faking ajax </button></td></tr><tr><td><a href='' class='popup'>message "+(x+1)+"</a><div class='dlog' style='display: none'>dialog "+(x+1)+"</div><p>stuff</p><p><button class='btn'> faking ajax </button></p></td></tr>");
$("#t1").html(table);
});
</script>
回答5:
If I got your question right you should use
.live('click',function()
{
$.data(this, 'dialog').dialog('open');
return false;
});
or
.on('click',function()
{
$.data(this, 'dialog').dialog('open');
return false;
});
instead of
.click(function()
{
$.data(this, 'dialog').dialog('open');
return false;
});
回答6:
You are overwriting the table so the state of the dialog gets lost every time.
回答7:
generally,jQuery binds all the element with event on load time however it can not be bonded to the dynamic functions by .click() methods for this use .live method which is far more effective in dynamic event binding.
You can also try using live function. http://docs.jquery.com/Events/live and read with example at
http://api.jquery.com/live/
after applying it will be something like
$('.popUpLink').live('click',function()
{
$.data(this, 'dialog').dialog('open');
return false;
});
回答8:
I have a simplify example here
Basically, you need a delegate function or most people recommend live. Delegate is basically, similar function to live but instead of bind to the top document, delegate only bind to specific dom that you specified. Therefore, it is not possible to have stopPropagation in live. Also, live is deprecated in jquery 1.7
if you lazy to see the fiddle, here is the code
<input id="madButton" type="button" value="add dynamic link" />
<div id="container"></div>
$('#madButton').on('click',function() {
var linkDom = '<a class="dynamicLink" href="#">click me</a><br/>';
$('#container').append(linkDom);
});
$('#container').delegate('.dynamicLink', 'click', function() {
alert('say aye!');
});
I hope it helps
回答9:
I had a similar issue when building with AJAX a dialog for "Upload complete" and such, just destroy the dialog before you create it like:
on('click',function(e) {
$.data(this, 'dialog').dialog('destroy').dialog('open');
/**
* You could also use e.preventDefault() instead of
* "return false" as and alternative
*/
return false;
});
回答10:
$('#tutorTable').delegate('.clickElement', 'click' function(){
var elem = $(this); //which will be your div/dialog if you supply the right selector
/*No need to rebind events since this handler will be attached to the #tutorTable
It will listen for the desired event and delegate it back
to the element that matches the supplied selector.*/
});
来源:https://stackoverflow.com/questions/9975742/how-do-i-rebind-the-dialog-after-it-is-being-rewritten-by-ajax