Bulk Inserting Data with the ListView Control
I am working on a project that needs a new screen for bulk entry tasks. We have a few business scenarios coming up where we have people inputting 5 to 8 records of data at a time. I met with a few of our analysts last week and hashed out a rough outline of what they were looking for. About 10 times or so during the 30 minute meeting some form of the phrase 'kind of like excel' was mentioned. Our data entry personnel are familiar with excel and our analysts thought an excel styled grid would make a lot of sense. 'An excel grid with a Submit button' was the phrase I had underlined twice in my notes.
I wasn't too worried about the grid's styling, but I wasn't quite sure what the best way to handle the 'bulk insert' requirement. Most of our other grids require an explicit mouse click to insert an item - and often times this click includes navigating the user to a separate page that is made to handle inserts. Of course for mass data entry tasks this can get a little old - tabbing through the cells is much nicer. So I took my notes back to my desk and started building a small prototype. You can check out the live demo to watch the grid in action. And a few of my implementation notes can be found under the screen shots below.
Live Demo (IE6, IE7, FF, Opera) | Download
Configuring the ListView
My excel styled grid only has one mode - bulk insert. It would also support mass updating as well, but for our use cases this is a one way data stream. I want to use the ListView because I like how the templates are structured, but the ListView doesn't natively support bulk inserting data (the ListView supports defining an InsertItemTemplate, but the ListView will only render this template once - so this template isn't useful for my scenario). I decided I would try using the regular ItemTemplate, but render elements that support editing instead of the usual read-only controls (i.e. TextBox's instead of Labels). I still use the Bind syntax because I want 2 way databinding, but because I am using the ItemTemplate for an insert scenario I will be responsible for telling the ListView when my data needs to be moved out of the controls and back to my data source. Below is the markup for my ListView.
Setting Up My DataSource
Next, I setup my data source. For this example I am using the ObjectDataSource control. When my data source is first bound to the ListView, I want to render placeholder objects for 8 records (i.e. I want my grid to show 8 empty rows by default). So when my Select method fires, I return a collection that contains 8 Customer objects that have their property values set to their default values (null strings for my example).
Next, I handle the Submit button's click event handler and loop through all of the ListViewDataItems and invoke the ListView's UpdateItem function which will cause the Update method on my ObjectDataSource to fire - moving the data back out from the controls and back into memory. Finally, after the data is moved back to memory, I persist the batch to the database (for the demo I am persisting it to memory, but when this page goes live it will obviously be put in the database).
To make sure I don't persist records with all empty values, I added a validation check to my Customer business object that makes sure the object has at least one data value before the row is submitted. If validation passes, I move the customer data into the database.
Toggling the DataTable's Skin
For a bit of flair I included a couple of radio buttons for changing the grids theme from the default 2003 style excel shown below (found a nice article here on creating this skin) to the 2007 version which is a bit softer (see screen shot at the beginning of the article).
To change the grids skin I attached event handler's to the radio button's click events and use the Sys.UI.DomElement.addCssClass and removeCssClass to toggle the CSS class that is applied to the grid. Below is the JavaScript that does this bit of work for me.
That's it. Enjoy!
Comments
Another excellent post! It's nice to see you "express yourself" and not just settle for the bare minimum amount of flair ;)
Now excuse me while I go watch that movie!
Posted by: Josh Stodola | May 4, 2008 09:54 PM
Great Article...I learn a lot Thanks :)
Posted by: Khushal Patel | May 4, 2008 10:29 PM
Very nice... Thanks Matt.
Posted by: Dan | May 5, 2008 05:52 AM
Great article.
Very clean and straight forward.
Pete
Posted by: Pete | May 5, 2008 10:27 AM
Once again, your posts are incredibly useful; well thought out and applicable to real-life issues.
Keep it up.
Posted by: Tony Basallo | May 5, 2008 11:05 AM
Hi Matt,
Yet again, bang on target.
Really, a very good article.
Thanks.
Posted by: Vish | May 5, 2008 11:40 AM
Matt,
This is a great example, and something I was working on myself. I'm a bit confused with your use of delegates however in the Javascript code. It was my understanding that the main reason for wrapping delegates in Javascript was to allow for disposing and to allow instance methods to serve as delegates. Since you aren't using the returned delegate at all from the Function.createDelegate() call, I'm not sure what the point of using it is. Seems like the following would be equivalent (with less code):
$addHandler($get('rdoOffice2007'), 'click', onSkinChanged());
$addHandler($get('rdoOffice2003'), 'click', onSkinChanged());
Posted by: Michael McGuire | May 5, 2008 01:19 PM
Great stuff. If you could get it to accept multiple cells pasted from Excel, it'd be outstanding!
Posted by: Anthony | May 5, 2008 02:06 PM
Nice
is there a VB version
Posted by: AsX | May 5, 2008 02:12 PM
Thanks again for another neat example Matt. The excel skinning functionality takes this yet another notch in being user-friendly. I was thinking of how to modify this for export to Word/RTF format?
Posted by: Yubi | May 6, 2008 06:42 AM
In a Borat Voice:
Very Nice...I Like
Posted by: JoeBo | May 6, 2008 01:21 PM
What's the purpose of the delegate in the Save method?
Posted by: Tim | May 6, 2008 01:23 PM
Awesome!
Posted by: Agha | May 12, 2008 06:44 AM