While there are similar questions here, none gave a complete answer, so I am posting a new one.
I have a paged grid - jqgrid - which receives data from server by ajax, N rows (10, 20 and so on, depending on the user selection) each time. There is a boolean value in the grid row model, which is transformed into a checkbox in the displayed row.
When user checks the checkbox and then navigates to the next grid page, the state of the checkbox is obviously lost. What is the best approach to save it? Neither of the possibilities that I see fully satisfies me:
I can save the ids of the selected instances into a global javascript object on checkbox click. Thus when new dataset is obtained, I can iterate through all received instances looking for already selected ones. However this can mean a lot of javascript operations and possible slowdown for the final user, if there are a lot of selected instances.
I can store the selection on the server (session, database, whatever else). This way each time the model is generated, I will populate its boolean parameter with an adequate value. However, this can mean that when the user navigates away from my page without submitting the changes and then returns back, the record states will be restored. I am not sure whether this is good. Generally, I am strongly against storing anything on the server side before user submits the form.
So, what would you choose / offer?
I am using ASP.NET MVC 2.0, C# 4.0, if that matters.
Your question is essentially about preserving state in an ajax scenario that does not involve the evil webforms viewstate.
However, there is nothing wrong with viewstate when it is used to preserve state in the kind of scenarios you are working on (as opposed to providing a means to pretend a web page is a winform).
So, why not go for the best of both worlds and store the values in an encrypted hidden field, a sort of lean, mean, smart man's viewstate?
When you request the next page of data, pass back to the server the existing "viewstate" (if any) plus the new checked items, decrypt the viewstate on the server, see what is in there and if it is relevant to the next page, add the new list of checked items, encrypt that and send the new "viewstate" back to the user.
I haven't done this, so it is just an idea. However, it is logically feasible, and very practical. I say I haven't done this with a grid, but I have done it, with huge success, to design a wizard framework that works a dream.
My wizards preserve their state whilst the user is filling in the forms and only in the final step does anything get persisted (if at all, depending on what the app requires).
This framework is based on the wizard described in Steve Sanderson's book, but extended to work seamlessly with or without ajax. And with a very simple API for controllers derived from my wizard controller.
The code that makes this viewstate work is called from an OnActionExecuting method:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["wizardData"];
if (serialized != null) // Form was posted containing serialized data
{
WizardData = (TModel)new MvcSerializer().Deserialize(serialized);
}
}
And then in the ViewResult returned to the user:
<%= Html.Serialize("wizardData", Model)%>
In your case, as you are just paging data, you would need to serialize and encrypt the equivalent of the wizardData object and send this back with the JSON data to store somewhere in a hidden field.
This is a bit vague, as a wizard is a not a grid of paged data. But the principles (essentially, roll your own viewstate) do apply to both scenarios.
I have addressed this very situation by writing a list of the 'selected' checkboxes in a hidden div when the next page is selected. That way the client maintains the selection list, and no server interaction is required. In addition, when the user finally submits the page, I simply iterate over all the checkboxes in the visible page and the hidden div.
In my system, even in examples where users select hundreds of items, performance is not an issue.
Related
In an MVC4 project, I have a form that contains a partial view which is an index view of languages studied at school. It is a default type view template index, with Add, Delete, Edit links per row etc. When you Add or Edit, it opens an Add or Edit view for a Language. After e.g. adding a language, the updated partial view is returned.
My problem is that if the user opens the Language form, edits and captures on the main form will be lost. I can't just do an Ajax save before opening the Language form, as the main form may only be partially complete and fail validation. What I am thinking of doing though is using an AjaxPreserve action that takes a FormCollection, and stores it in session (o on disk, or anywhere) and therefore no model binding and server validation is performed.
I then have two problems: I will need to disable client validation before calling the AJAX action, and I will need to repopulate the main form using the FormCollection I saved earlier. I think there should surly be some jQuery voodoo to disable client validation, but I am completely stumped on repopulating the form.
ALTERNATE SOLUTION: Instead of using 'sub-forms', I can use editor templates, in pop-ip forms, where the FK IDs are not required, but that us only in certain cases, so my question still stands.
Could you use something like Knockout where you create javascript model and bind it to a grid/dialog edit/template view. I would transform the whole data to a JS model, bind it to a table/grid and then track all changes on the client side. When all is done, just serialize the whole model back to the server and update the data store.
If this is an acceptable scenario, it will save you a lot of trouble.
Familiarity with Knockout is required, but if you've used it before, you will be able to solve this in a very clean and efficient way.
This example on the Knockout website gives an idea of what I'm trying to suggest. Editing, deleting, adding is done on the client side until you send all of the data back to the server. You will need to track flags for each object to know if it's added, edited or deleted.
http://knockoutjs.com/examples/contactsEditor.html
Simple make the sub request for adding language using Ajax and repopulate the dropdown or what ever way you are accepting language on the main form on sucessfully save.
*This will save a lot of effort. *
Why don't you just use javascript?
E.g. You have main form, that stores some data. And when you need to add something specific like languages you open popup using partial view, allow user to fill form, but when user press submit you intercept action with js, save stuff to javascript array/object or whatever else and maybe store it in a hidden field of main form - for final submit
var newData = new Object();
newData.Field1 = $("#yourField1");
...
lanuageData.push(newData);
$("#languageContainer").val(JSON.stringify(languageData));
...
DataAnnotation validation works here as well like:
$.validator.unobtrusive.parse("your_partial_view_container");
When you need to edit some object that was already added to js array - open popup and fill it up with element of you js array. So basically you do all CRUD on client-side, saving changes only on final submit.
To make your code in a conrolller more clear you can use custom model binder which deserialize some string field from JSon to List or any other kind of object -> so it can be validated on server side.
Would saving your values to local storage be acceptable? What about using TempData?
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.
The contents of my MVCContrib grid come from the Model on a strongly typed View. When a post is made, the contents of the grid are not in the model object when it returns to the controller. I can see that this is because the grid renders as just a table with text in cells. Is there something I can do so that when the post occurs, the list data I sent down to the grid comes back in the post?
You can use TempData to persist this information server side. The information in TempData will persist for one request. I do not really like this option however.
Can you not repopulate your model from the db? If the user is not changing the information why do you need to post back all the same unchanged information? Just grab it again from where you got it before.
If you want to recreate the model as it was serialised into the grid, you will have to embed correctly named form elements within the grid ( or maybe outside the grid ) and within the same form as the one containing the button that is posting back to the action where you want your Model recreated.
While this is doable, you are essentially recreating __VIEWSTATE, and that defeats much of the joy of working with MVC ( read "it's an ugly hack and you should uninstall your IDE for even thinking it").
It is hard to point you in the right direction without having a better understanding of the scenario you are trying to solve. The usual workflow in these situations is
get the model
generate the page
record any changes to the model in a
form on the page
submit the changes to an action
get the model again
use TryUpdate to persist the changes
from the post into the model
If you are suffering performance issues ( you have proved you've got a perf problem right? You aren't optimising prematurely?), address them where they occur ( i.e. caching in your data access ), rather than bending MVC in ways it really shouldn't be.
I am using the Redirect After Post pattern in my ASP.NET MVC application. I have
the following scenario:
User goes to /controller/index where he is asked to fill a form.
Form values are POSTed to /controller/calculate.
The Calculate action performs calculation based on input and instantiates a complex object containing the results of the operation. This object is stored in TempData and user is redirected to /controller/result.
/controller/result retrieves the result from TempData and renders them to the user.
The problem with this approach is that if the user hits F5 while viewing the results in /controller/result the page can no longer be rendered as TempData has been expired and the result object is no longer available.
This behavior is not desired by the users. One possible solution would be instead of redirecting after the POST, just rendering the results view. Now if the user hits F5 he gets a browser dialog asking if he wants to repost the form. This also was not desired.
One possible solution I thought of was to serialize the result object and passing it in the URL before redirecting but AFAIK there are some limitations in the length of a GET request and if the object gets pretty big I might hit this limitation (especially if base64 encoded).
Another possibility would be to use the Session object instead of TempData to persist the results. But before implementing this solution I would like to know if there's a better way of doing it.
UPDATE:
Further investigating the issue I found out that if I re-put the result object in TempData inside the /controller/result action it actually works:
public ActionResult Result()
{
var result = TempData["result"];
TempData["result"] = result;
return View(result);
}
But this feels kind of dirty. Could there be any side effects with this approach (such as switching to out-of-process session providers as currently I use InProc)?
Store it in the Session with some unique key and pass the key as part of the url. Then as long as the session is alive they can use the back/forward button to their heart's content and still have the URL respond properly. Alternatively, you could use the ASP cache, but I'd normally reserve that for objects that are shared among users. Of course, if you used the parameters to the calculation as the key and you found the result in the cache, you could simply re-use it.
I think redirect after post makes much more sense when the resulting Url is meaningfull.
In your case it would mean that all data required for the calculation is in the Url of /controller/result.
/controller/calculate would not do the calculation but /controller/result.
If you can get this done thinks get pretty easy: You hash the values required for the calculation and use it as the key for the cache. If the user refreshes he only hits the cache.
If you cant have a meaningfull url you could post to /controller/index. If the user hits F5 calculation would start again, but a cache with the hash as key would help again.
TempData is generally considered useful for passing messages back to the user not for storing working entities (a user refresh will nuke the contents of TempData).
I don't know of more appropriate place than the session to store this kind of information. I think the general idea is keep session as small as possible though. Personally I usually write some wrappers to add and remove specific objects to session. Cleaning them up manually where possible.
Alternatively you can store in a database in which you purge stale items on a regular basis.
I might adopt a similar idea to a lot of banks on their online banking sites by using one-time keys to verify all POSTs. You can integrate it into a html helper for forms and into your service layer (for example) for verification.
Let's say that you only want to post any instance of a form once. Add a guid to the form. If the form does not post back and the data is committed then you want to invalidate the guid and redirect to the GET action. If say the form was not valid, when the page posts back you need a new (valid) guid there in the form waiting for the next post attempt.
GUIDs are generated as required and added to a table in your DB. As they are invalidated (by POSTS, whether successful or not) they are flagged in the table. You may want to trim the table at 100 rows.. or 1000, depending on how heavy your app will be and how many rendered but not yet posted forms you may have at any one time.
I haven't really fine tuned this design but i think it might work. It wont be as smelly as the TempData and you can still adhere to the PRG pattern.
Remember, with PRG you dont want to send the new data to the GET action in a temp variable of some sort. You want to query it back from the data store, where it's now committed to.
As Michael stated, TempData has a single purpose -> store an object for one trip only and only one trip. As I understand it, TempData is essentially using the same Session object as you might use but it will automatically remove the object from the session on the next trip.
Stick with Session imho rather than push back in to TempData.
I have an application that has two dependent dropdown lists, where if the user selects a value in list box A, it updates the available set of inputs in list box B. Such as make/model of a car. when the user selects the manufacturer, the list of models would update accordingly.
In winforms, this would just be handled in the autopost back event. What technique/approach should I take in asp.net MVC? is done through AJAX ? just trying to get up to speed on MVC and looking to build strategies to handle common tasks I am asked to handle at work.
I wanted to thank everyone who contributed to answering this post.
I'd do this through ajax. If you have these controls:
<select id="makes" /> and <select id="models" />
then you can do this with jquery:
$().ready(function() {
$("select#makes").change(function() {
var make = this.value;
$.getJSON('models/list?make=' + make, function(data) {
//load 2nd dropdown with result
})
});
});
Then you'd just need an action on the ModelsController called List() that returns a JSON data structure.
That you are using ASP.NET MVC is somewhat irrelevant. You basically have three options for this type of UI mechanic on a web page.
If the data in your lists is relatively small and infrequently changing, it can be easiest just to pre-load all possible data on the page in the initial request either in something like a javascript array or hidden elements in the page markup. When the value of Box A changes, javascript just replaces the contents of Box B with the appropriate data. This all happens without any requests back to the server which makes it very fast. But this method breaks down when the size of your data impacts the response time of the initial page load.
If the data in your lists is large or frequently changing (within the time frame a user would be on the page making a decision), the legacy method is to just have the page get reloaded with the new query arguments when the value of Box A changes. Code on the back-end adjusts the output based on the new arguments. Depending on how complex the rest of the rendering code in your page is, this can be expensive.
This is a variation on option 2 and is basically the answer Ben Scheirman gave regarding ajax. You're still loading the contents of Box B on demand when Box A changes. The difference is that you're only reloading the piece of the page that has changed rather than the entire page.
Personally, if your data is small enough to use option 1, that's probably what I would go with due to its performance. Otherwise go with option 3, particularly if you've already got other ajax related things implemented already. Option 2 is seems to be considered a legacy mechanic by many people these days.
Functionally it would work to place those two drop lists in their own partial view and then return just that when the value in ListA is selected. If that isn't feasible for layout purposes, then Ben's method above looks good.