A lot has been going on with liteGrid recently. The application at my day job that necessitated its creation is nearing production, and numerous additional features are being implemented in support of finishing that app. One of the big things that recently came up was making sure delete operations worked when the grid was in “tree grid” mode (meaning the TreeGridModule was loaded). While liteGrid has supported deletes for some time, it didn’t work correctly with tree grids. When I was tasked with fixing this deficiency, I initially thought “this will be easy, I just need to remove the children when the parent is deleted!” As is often the case in software, this was a gross oversimplification of the issue.
Why so complex?
If the grid was static, yeah, it would be a simple matter of deleting children once a parent has been deleted. Unfortunately, the grid is no longer static. Users can add new rows (via RowAdditionModule) and now they can re-arrange rows and change the parent/child relationships (via DraggableRowsModule). Deletes in liteGrid have always been a synchronous operation, while everything else is asynchronous. Why is this bad? Well, think about it for a second. Let’s assume I have the following structure initially:
This is the structure the server knows about: I have 4 widgets that are not related. Now let’s say that I re-arrange things like so:
Let’s assume I haven’t saved yet. At this point, the client has a very different view of the relationships than the server does. The client knows that everything is now nested under Test 1 while the server still thinks everything is a singleton leaf. Under the old model, what happens when I click the delete button for Test 1? The server will remove Test 1, and that’s it. As far as it knows, Test 1 had no children. But the client knows that Test 1 had children and will assume that they’ve been deleted, too. See the problem?
Let’s consider another case. Say we start out with the parts nested as in the second figure, but flatten them out so that they are all singleton leaves as in the first diagram. Again, until we save, the server and client have very different views. If we delete Test 1 on the client, the server will also delete Test 2, 3, and 4, since it thinks those items are nested under Test 1.
The root of the problem is that deletes are synchronous while all other operations are batched.
Once the root problem is well-understood, fixing it was seemingly straight-forward: make deletes a batch operation just like adds, moves, and edits. Batching everything allows the server to update its state so that its view of the world matches the client before it starts nuking things. While this sounds easy, it was, in fact, not.
First, there were the expected problems with Internet Explorer. I won’t dive into too many details, suffice to say that Internet Explorer 8’s table rendering is absolute junk. I don’t know how, but they actually made it worse than it was in IE 7.
Ignoring the IE8 problems, I still had to do a fair amount of work to get the user experience that I wanted. Since deletes would be batched until the user clicked the Save button, I wanted deleted rows to be clearly visible, effectively disabled (meaning they couldn’t be edited or moved), and I wanted the delete operation to be undoable. This would have been fairly simple if not for the tree grid module, which makes everything more complex. HTML tables really aren’t well-suited for rendering nested sets like this.
Disabling the rows was a problem. I didn’t want the delete module to have to know about all the current and future modules that might have added behavior to the grid, so removing the behavior manually was not an option. Instead, I used jQuery’s clone method to create a copy of the deleted row and its children. The clones maintained the classes and data of the originals but none of the event handlers, effectively disabling them. I replaced the original rows with the clones and provided a button to undo the delete operation. When clicked, the rows are re-bound to their data items, which (thankfully) restores all the functionality added by other liteGrid modules. At the end of the day, I had a nice batch solution that was not much more complex than my original solution for handling deletes.
You can check out the latest demo online. Code as always is available on the SVN repository.