TableKit is a great javascript library for making your HTML tables fully editable. However, one problem is that you can’t add or delete rows from the tables…
I came up with a solution to this not so long ago, but it still had a problem - TableKit caches the tables on loading, so after we update the table body (adding or deleting a row) the sorting and editing of the table is completely screwed!
However, with a little more work, and some help from one of the guys in the office - we’ve finally got this cracked!
The basic idea to the fix is, instead of updating the table body, replace the entire table with a new one and get TableKit to do its stuff on the new table. Although this is not the ideal solution1 - this does work quite well. Here’s the details.2
First, we’ll update the code from our template file (/root/src/users/list.tt):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | [% META title = 'User List' -%] <div id="users_div"> <table id="users" class="sortable resizable editable"> <thead> <tr> <th id="id" class="sortfirstasc noedit">Id</th> <th id="firstname">First Name</th> <th id="lastname">Last Name</th> <th class="noedit nocol"></th> </tr> </thead> <tfoot> <tr> <th>Id</th> <th>First Name</th> <th>Last Name</th> <th class="nocol"></th> </tr> </tfoot> <tbody id="user_body"> [% FOREACH user IN users -%] <tr id='[% user.id %]'> <td>[% user.id %]</td> <td>[% user.firstname %]</td> <td>[% user.lastname %]</td> <td class="nocol"> <a class="delete" href="#" onclick="deleteUser([% user.id %]); return false">delete</a> </td> </tr> [% END -%] </tbody> </table> </div> <br /> <a class="add" href="#" onclick="addUser(); return false">add a user</a> |
Then add this javascript code to the bottom of the same file:
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | <script type="text/javascript" charset="utf-8"> var users_table = new TableKit( 'users', { editAjaxURI: '[% c.uri_for('/users/_update_user') %]' }); function addUser() { var timestamp = new Date().getTime(); var new_table = 'users' + timestamp; var url = '[% c.uri_for('/users/_add_user') %]?timestamp=' + timestamp; new Ajax.Updater( 'users_div', url, { asynchronous: true, onComplete: function() { new TableKit( new_table, { editAjaxURI: '[% c.uri_for('/users/_update_user') %]' }) } }); } function deleteUser( user_id ) { var timestamp = new Date().getTime(); var new_table = 'users' + timestamp; var url = '[% c.uri_for('/users/_delete_user/') %]?user_id=' + user_id + '×tamp=' + timestamp; var answer = confirm('Are you sure you want to delete this user?'); if (answer) { new Ajax.Updater( 'users_div', url, { asynchronous: true, onComplete: function() { new TableKit( new_table, { editAjaxURI: '[% c.uri_for('/users/_update_user') %]' }) } }); } } </script> |
The main differences in the above from last time round are as follows: - The ‘users’ table is now inside a div with the id ‘users_div’ - The link to add a user now calls a javascript function rather than an Ajax.Updater call directly. - The functions to add and delete users are essentially the same: - - They create a timestamp to individually identify the new table we are going to create (this is the most important thing here to remember)! - - They then use an Ajax.Updater call to talk to the Perl controller and generate the new table with a row added or removed. - - Once the Ajax.Updater is complete, TableKit is then called again to make our new table editable and sortable.
That’s the template sorted, now let’s look at the controller (/lib/MyApp/Controller/Users.pm):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | package MyApp::Controller::Users; use strict; use warnings; use base 'Catalyst::Controller'; =head1 NAME MyApp::Controller::Users - Catalyst Controller =head1 DESCRIPTION Catalyst Controller. =head1 METHODS =cut =head2 index =cut sub index : Private { my ( $self, $c ) = @_; $c->response->redirect('/users/list'); } =head2 list Fetch all user objects and pass to users/list.tt in stash to be displayed =cut sub list : Local { my ( $self, $c ) = @_; $c->stash->{users} = [ $c->model('MyAppDB::Users')->all ]; $c->stash->{template} = 'users/list.tt'; } =head2 _update_user Ajax method to update the users table =cut sub _update_user : Local { my ( $self, $c ) = @_; $c->model('MyAppDB::Users')->find( { id => $c->req->params->{id} } ) ->update( { $c->req->params->{field} => $c->req->params->{value} } ); $c->res->body( $c->req->params->{value} ); } =head2 _delete_user Ajax method to delete users from the users table =cut sub _delete_user : Local { my ( $self, $c ) = @_; # Look-up our user entry my $user = $c->model('MyAppDB::Users')->find( { id => $c->req->params->{user_id} } ) ->delete(); my $output = __return_all_users( $self, $c, $c->req->params->{timestamp}, 'null' ); $c->res->body($output); } =head2 _add_user Ajax method to add users to the users table =cut sub _add_user : Local { my ( $self, $c ) = @_; # create a new user... my $new_user = $c->model('MyAppDB::Users')->create( { id => $c->req->params->{user_id}, firstname => '[First Name]', lastname => '[Last Name]' } ); my $output = __return_all_users( $self, $c, $c->req->params->{timestamp}, $new_user->id ); $c->res->body($output); } =head2 __return_all_users Private method for the Users ajax interaction. Returns a html table. =cut sub __return_all_users : Private { my ( $self, $c, $timestamp, $new_user_id ) = @_; my @users = $c->model('MyAppDB::Users')->all(); my $html = '<table id="users' . $timestamp . '" class="sortable resizable editable"> <thead> <tr> <th id="id" class="sortfirstasc noedit">Id</th> <th id="firstname">First Name</th> <th id="lastname">Last Name</th> <th class="noedit nocol"></th> </tr> </thead> <tfoot> <tr> <th>Id</th> <th>First Name</th> <th>Last Name</th> <th class="nocol"></th> </tr> </tfoot> <tbody id="user_body">'; if ( scalar(@users) > 0 ) { for ( my $i = 0 ; $i < scalar(@users) ; $i++ ) { my $class; if ( $new_user_id && $users[$i]->id == $new_user_id ) { $class = 'new'; } else { if ( $i % 2 == 0 ) { $class = 'rowodd'; } else { $class = 'roweven'; } } $html .= "<tr class=\"" . $class . "\" id=\"" . $users[$i]->id . "\"> <td>" . $users[$i]->id . "</td> <td>" . $users[$i]->firstname . "</td> <td>" . $users[$i]->lastname . "</td> <td class=\"nocol\"> <a class=\"delete\" href=\"#\" onclick=\" deleteUser(" . $users[$i]->id . "); return false\">delete</a> </td> </tr>"; } } else { $html .= '<tr><td colspan="4" class="nocol">All Users Deleted</td></tr>'; } $html .= '</tbody></table>'; return ($html); } =head1 AUTHOR Darren Oakley =head1 LICENSE This library is free software, you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1; |
The only real changes in the controller are a general code clean-up, and a slight change to the ‘__return_all_users’ method - this now returns a whole table (identified by the unique timestamped id that we generated in the javascript functions). Nothing really needed changing in the back-end.
There you go! Now our TableKit based tables can be used to not only edit entries in the database, we can now add and remove them!
The files used in this example can be found below.
Attached Files:
-
That would be knowing more about javascript and the dom so that we can make changes to TableKits cache as we update the table body (thus keeping the original table). ↩
-
We’re using the code from the previous post as a starting point. ↩
Hi, can you have a simple version for php ? i wish to code your example in php , hope you can share.. thanks
Unfortunatley I don’t have any examples of this in php - all of my work with this so far has been in Perl and Catalyst, but i’m sure it would be easy to reimpliment in php.
The basic principle is that you have a several functions/scripts each with the following purposes:
After that, it’s all about the javascript (that is detailed above).
If I get time I may have a look into putting something together in php, but there’s no promises…
thanks for the guide. I have implemented the php version and right now i am working on the pagination , it seem to work well, and most important, thanks for your hints that enable me to make it work.
Cheers….
Hi Ethan,
Care to share your code? I’m trying to do the same thing! I may get there myself in time but if you’ve done it already it would be nice!
Thanks to Daz for his work too. I got a similar thing set up with the OpenRico grid, which is good, but doesn’t have the
‘edit-in-place’ functionality.
Cheers,
Colin
Ok, forget it, I got it sorted! Took a while, as I thought, but I got there
Thanks again to Daz for the code examples.
Colin
Please can you share the php code…i am trying to do the same thing for a few days.
Thanks
I had a similar issue with TableKit, but it presented itself differently. After I created the initial table when I returned a new table with Ajax.Updater it would run javascript which would first check the window for the existence of my table object. In your case I would be looking for window.users_table and attempt to call the sort method on that object thinking it would resort the data. I kept getting errors that different variables in the object were empty since it would lose its reference to the table.
This issue has arisen for me quite often with other javascript objects which reference an ID that is later updated using Prototype’s Ajax.Updater method. It seems that Prototype does not append the data which is returned, but rather updates the innerHTML of the id which you provide. This makes sense since not all returned data will need to be appended to the DOM because it may be JSON, plain text, or something other then HTML. To fix similar issues I have moved away from using Ajax.Updater and instead used Ajax.Request with the onComplete method set to append the data which is returned as a child of the target id. This adds the returned HTML to the DOM making accessible in the manner you would expect. This may help to update the table in a more predictable and reusable manner. I have not tried this will TableKit, but I thought I would offer this a hint that may help move towards a solution that reuses the table instead of creating new one each time you update.
Moving forward it would also be nice if TableKit was able to rebuild the table body using JSON data retrieved via AJAX so it could perform more light weight updates of larger datasets.