Set the scene:
New to .NET; drinking from firehose
ASP.NET MVC app, SQL Server back
Editable table in browser with a single SAVE button.
User can right-click to add or delete rows.
Table won't ever have more than approx. 30 rows.
My question :
I'm saving everything upon the Save button click but would it be better to save row by row, AJAX style, as the user makes updates?
I don't like the look of separate buttons for each row, which is why I've designed it this way.
Is this mostly a UI issue? Am I missing any technical gotchas here, such as backend failure during the mass saving of the rows?
Additionally, assume I do save the entire table at once, is it better to create an ADO DataTable object or just loop through, inserting/updating each row as I go by calling a stored procedure. I suppose I could add LINQ to the firehose, but that would make this question even less "answerable".
You don't have a huge volume of data here, so saving all 30 rows at the end of the table is a reasonable approach. But you should be prepared for a failure, particularly if you are changing existing rows when it will fail more often due to other apps/users changing the same data. Just make sure that you wait for confirmation from the SQL server that the changes have been committed.
What I've done before with these sorts of big table views is when somebody clicks on a cell they'd like to edit, run some ajax to display a text field with that text, they can edit, then listen to onmouseout and the enter button to send off the ajax request to modify the single row.
When the response from the ajax call comes back you can add a tooltip or something that it was saved, and then change the cell to the new val.
Assuming you have SQL2005, you could build up an XML document with all of your data rows, then call a single stored proc and pass it your XML. Then the stored proc could save all of the rows at once.
Related
I am using an editorfor template so that users can edit row information inline when the information is displayed as a table. The user then has a save button below the table which when pressed sends the whole Model to a HttpPost method which then iterates over each row and saves the row back to the database. As you can imagine this can become quite slow when the row number increases as it has to iterate over every row then save the information to the database.
I was looking for some way of saving a single row back to the database if a field is edited within that row or maybe setting a flag to true which when the user presses the save button it only iterates over the rows that have been edited and only saves those rows back to the database?
Any help would be much appreciated.
Regards,
Jay
Why you don't use Ajax to update each row?
You can associate the button to a javascript (jquery) function responsible to update (post) the modified row.
In this way you only update the modified row.
To avoid problems you can use a curtain or a spinner during the execution of the update.
Regards,
André
On Telerik demo site we can see an example of how to implement kind of functionality: "check all checkbox in a grid's column". But in my case it has 2 disadvantages:
It didn't check all checkbox on all pages.
It didn't save a state of checkboxes on different pages.
Is anybody know how to resolve these issues? Thanks in advance.
As long as I know there's no built-in functionality to do so. The same problem happens when you select records on page one and change to page two, you loose whatever you selected before.
To achieve that functionality you have 2 options (I've used both on previous projects)
1) On each check make an Ajax call to one of your controllers and store whatever you selected on a Session Variable (This can be inefficient if you have a lot of records)
2) Create a javascript variable and store your selections there, and send back to the controller using a json variable or a comma separated values string
As I said, I've used both approachs so it depends on if this works for you or not
Hope it helps
I can't test this, so I'm not 100% sure, but looking at Telerik's example, one reason it's not persisted is because every "page" of the grid requires a postback, and in the controller action result method, they aren't passing in the model (or view model) for the items that are bound to the grid, they're only returning that list of items back to the view, so it will never "save" which items are checked/selected and which ones aren't. You should be able to get around this by making your view model a parameter into the HttpPost action result method and then passing that list back to the view after the post so that it retains which items are selected instead of creating a new one. This won't solve the issue with not selecting all the items, but it should at least retain which ones are selected throughout the pages. I think the reason for it not working with all items is it can only select the ones that are actually being displayed at the time. You may want to do a post (or ajax) to select "all" items.
One of the major reasons for using paging in grids is so that you don't have to retrieve all of the data from the data store and generate a lot of HTML to push to the client.
It's been my experience that most users understand that a "select all" check box only checks the items on the current page. I've not seen a site where checking such a check box would actually check all records, even those I can't see.
If you have an action which will affect more than the current page of records, I would suggest that you add a button which clearly indicates that the action will affect all records, then send a command to your data layer which will perform that action. This will perform better (you don't have to send a potentially long list of ids across the wire) and allow users to understand the repercussions of their action.
I have two ADO Tables linked as master/details tables, tblCategory (master) and tblItems (details). Both tables have its own grid, and displayed in the same form. I also have data aware controls (dbedits).
Say, currently I'm at: Category=Books, No of Items=10 records, and pointing at record number 5 in the grid. I want to add a new record to the item, so I use:
tblItems.Insert;
The problem is, instead of adding a new row, the grid and the db aware controls are showing the current record (rec No 5). Not inly that, it seems the record is in edit mode too. After I cancel it and repeat the Insert command, only then the new row appeared.
How to fix this, so each time I use tblItems.Insert it always add a new and empty row :)
Nevermind, I think I know what caused it. It's the db aware controls. After the insert command, user will input data. This makes the db aware control receives focus and it automatically sets its position to the current record and displays it.
The solution is to use non-db-aware controls instead, and set the behavior programatically
Here's a simple problem: users want to edit products in grid-like manner: select and click add, select and click add... and they see updated products list... then click "Finish" and order should be saved.
However, each "Add" have to go to server, because it involves server-side validation. Moreover, the validation is inside domain entity (say, Order) - that is, for validation to happen I need to call order.Add(product) and then order decides if it can add the product.
The problem is, if I add products to order, it persists changes so even if users do not click "Finish" the changes will still be there!
OK, I probably shouldn't modify the order until users click Finish. However, how do I validate the product then? This should be done by the order entity - if product is already added, if product does not conflict with other products, etc.
Another problem, is that I have to add product to order and "rebuild view/HTML" based on its new state (as it can greatly change). But if I don't persist order changes, the next Add will start from the same order each time, not from the updated one. That is, I need to track changes to the order somehow.
I see several solutions:
Each time the user click Add, retrieve order from database, and add all new products (from the page), but do not persist it, just return View(order). The problem is I cannot redirect from POST /Edit to GET /Edit - because all the data only exists in the POST data, and GET lose it. This means that Refresh page doesn't work in a nice way (F5 and you get duplicated request, not to mention the browser's dialog box)).
Hm, I thought I can do redirect to GET using TempData (and MvcContrib helper). So after POST to /Edit I process business logic, gets new data for view, and do RedirectToAction<>(data) from MvcContrib that passes data via TempData. But since TempDate is... temp... after F5 all the data is lost. Doesn't work. The damn data should be stored somewhere, this way or another.
Store "edit object" in Session with the POST data (order, new products info). This can also be database. Kind of "current item - per page type". So page will get order ID and currently added products from this storage. But editing from multiple pages is problematic. And I don't like storing temp/current objects in Session.
Marking products as "confirmed" - if we do /order/show, we first cleanup all non-confirmed products from the order. Ugly and messy logic.
Make a copy of the order - a temporary one - and make /Edit work with it. Confirm will move changes from temp order to persisted. A lot of ugly work.
Maybe some AJAX magic? I.e. "Add" button won't reload page but will just send new + already added products to server, server will validate as order.Add(products + newproduct) but will not persist changes, will just return updated order information to re-build the grid. But Refresh/F5 will kill all user-entered info.
What else?
Is this problem common? How do you solve similar ones? What's the best practices?
It depends a lot on how you implement your objects/validation, but your option number 5 is probably the best idea. If AJAX isn't your thing, you can accomplish the same thing by writing the relevant data of already-added-but-not-saved entries to hidden fields.
In other words, the flow ends up something like this:
User enters an item.
Item is sent to the server and validated. The view is returned with the data entered by the user in hidden fields.
User enters a second item.
Item is sent to the server, and both items are validated. The view is returned with the data for both items in hidden fields.
etc.
So far as F5/Refresh killing entered data... In my experience this isn't too much of a problem. A more pressing concern is the back/forward buttons, which need to be managed with something like Really Simple History.
If you DO want to make the page continue to work after a refresh, you need to do one of the following:
Persist the records to the database, associated with the current user in some way.
Persist the records to session.
Persist the records to the query string.
These are the only storage locations available that persist through both redirection and refreshes.
If I were you, I would come up with something which resembles option 5. And since you say that you are comfortable with Ajax you can try this. But before you do this, you should move your validation logic outside the Order.Add() method. Maybe you can move it to another public function called Validate() which returns a bool. And you can still call the same Validate() in the Add() method, thereby doing the necessary validation before you add the order.
Try to do the validation on the client side. If you are using jQuery, you can use the jquery validate plugin. But, if this is not possible for some reason (such as when you need to validate stuff against a database). You should do your validation on the server side and just return a JSON object with the 'success' boolean flag and an optional message, just a way to mark that the data is valid. You would allow the user to add a new product only if the previous Order was valid.
And when the user hits finish send the product to the server and do the validation again, but persist the order in this round-trip.
Now, If I had a complete say in this, I wouldn't even go to the extent of doing validation whenever a product is added/edited. I would just do the validation whenever the customer hits finish. That would be the simplest solution. But, maybe I am missing something.
I'm coming from the delphi world and I want to make a master/detail interface, like Order and Products.
I already made actions to display the data using fields and a jqGrid. What I want know is how make possible to add lines, edit or remove them, but, just make the changes in db when the user confirm the changes in the master.
On delphi I would use a TClientDataSet with all the in memory changes and just after the confirmation would execute them inside a transaction like:
BEGIN
Master.Post
FOREACH Line IN Lines Line.Post
COMMIT
So in resume, I don't know how keep in memory the array of lines in the grid and how send them back to server to commit.
Any help will be appreciated. Thanks in Advance.
You'll need to keep track of the changes client side, perhaps using some hidden fields and/or form fields in your grid. When a line is deleted (that previously existed in the db), you'll need to add it's id to a field containing lines to delete. Lines that are added need to have associated form fields containing their data. When the master is committed you roll the whole set of fields up into a POST and send that back to the server.
Using LINQ to SQL, you'd create a data context, get the master object, then delete the related objects (from the hidden field of ids) that are so marked and create/add new related objects that didn't exist before taking the values from the appropriate form fields. Then you'd do a SubmitChanges and all of the statements would be executed within a single transaction.