How to save data between page changes without posting (MVC 5)? - javascript

I'm using a Session variable to persist data when user goes back to the page that has the data input form. Storing the Session data values is done in the controller, after user submits the form. This works fine, but the problem is if the user changes page without posting the form first, all data is gone. How can I store the data under this condition? Probably I will need a JavaScript that calls an Action in the controller and pass the data to be saved via Ajax call? Is it possible to have an example on how to do this? also, in what event should this be called? I'm fairly new to JavaScript, so any detail would be important. Bellow is part of the code: (For simplicity, I've omitted additional code that test for null, etc...)
// View
<div class="row">
<div class="col-sm-3" style="background-color:peachpuff">
#{Html.RenderPartial("~/Views/Shared/ReservSearch.cshtml", Session["ReservSearchModel"] as Models.ReservSearch);}
</div>
// Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult GetRates([Bind(Include = "DropOffLoc,DropOffDate,PickUpDate")] ReservSearch reservSearch)
{
Session["ReservSearchModel"] = reservSearch;

<script language="JavaScript">
window.onbeforeunload = savedata;
function savedata()
{
var formElement = document.getElementById("myFormElement");
var request = new XMLHttpRequest();
request.open("POST", "MyController/MyAction");
request.send(new FormData(formElement));
}
</script>
Note: This does not demonstrate "the correct way". It just demonstrates a quick and dirty solution for sending the data on the form to your backend, when the user leaves the pages
The backend ASP.NET MVC controller
public class MyController: Controller
{
public void MyAction(FormCollection formCollection)
{
//find your form elements and their value in this formCollection collection
//and process them as required
}
}

Related

Asp.Net MVC app - caching data between page loads

I have a simple Asp.Net MVC app. I have a form, which I'm trying to gather data for, and then submit. However, within that form is a list of selections - each time a selection is made, I go to the server to add that data:
function addSelection(item) {
fetch("/test/selection/" + item,
{
method: "POST"
})
.then(response => {
const name = document.getElementById('Name').value;
localStorage.setItem("name", name);
if (response.ok) {
location.reload();
} else {
console.error("Unable to add");
}
});
}
The server then stores the list in a HttpContext.Session variable.
The target is to keep the name property in place across calls. The code above works great - I set the control on load:
window.onload = function() {
var getName = localStorage.getItem('name');
document.getElementById('Name').setAttribute('value', getName);
}
However, the form itself is eventually submitted, and the entire form, with the selected items, is submitted to the server:
<div>
<input type="submit" value="Submit"
asp-controller="My" asp-action="Create"/>
</div>
The problem that I have is that the local storage still retains the value of name, and so the next time the form is loaded, it's still there. Does anyone have any techniques for achieving the same result without using local storage?
The only solution that I can think of so far is to replace the submit button with a manually coded JS script that resets the value, but that doesn't account for situations where the user just moves away from the form. I feel like I'm heading down a rabbit hole of my own making, and there must be a simpler way to do the same thing.
One solution is to use hidden form fields to persist the data between requests instead of local storage. On each item selection, add the data to a hidden form field on the client-side instead of saving it to local storage. Then, when the form is submitted, the hidden form field value will be included in the form data and can be retrieved on the server-side.
On the server-side, you can retrieve the NameValue from the form data in your action method.
[HttpPost]
public IActionResult Create(string NameValue, ...)
{
...
}

Reload part of the page on button click, Refresh whole page on new URL

I am writing a single page Spring MVC application.
Requirements:
I want it to change the state of the page according to the URL that is entered.
I want it to change the state of the page on a click of a button.
Example use cases:
If I enter a URL "my.site.com/", I want only my site skeleton to be loaded, with no data for the User. (User is an object in my model).
If I enter a URL "my.site.com/users/John", I want the data for "John" to be displayed (the page can be reloaded).
If I enter string "John" in a textbox, and hit button Go!, I want only the part of the page displaying user data to be refreshed and not the whole page reloaded.
Design Question:
I understand that for 1) and 2) I would need to return a new ModelAndView object, and for 3) I could use AJAX. This probably implies I would need three controller methods.
What I don't know is how to avoid conflicting URLs between the MVC and AJAX controller methods, and how to actually then call my AJAX controller method from Javascript, and not the ModelAndView controller method.
Code Example:
What I would need is something like this, except that this, of course, causes conflicting URLs.
/*
* Default view.
*/
#RequestMapping(value = "/users")
public ModelAndView displayDefault() {
return new ModelAndView("userdisplay_default.jsp");
}
/*
* View for a specific user.
*/
#RequestMapping(value = "/users/{username}")
public ModelAndView displaySpecific(#PathVariable(value = "username") String username) {
User user = new User(username);
return new ModelAndView("userdisplay_specific.jsp", "Specific User", user);
}
/*
* AJAX controller method.
*/
#RequestMapping(value = "/users/{username}", produces = "application/json", method = RequestMethod.GET)
public #ResponseBody User getTime(#PathVariable(value = "username") String username) {
return new User(username);
}
In Javascript I would then fetch the POJO like this:
// obtain user
var user = $('#user_input').val(); // this is a text input
$.getJSON("/users/"+user, function() {
//...
});
NOTE: My way of trying to achieve that could be wrong // insufficient // not optimal, so please feel free to also suggest some other way on how to do that.
Could you please give me an explanation along with a code example how what I need should be accomplished?
You can make different methods for your controllers.
For example:
#RequestMapping(value = "/users") and #RequestMapping(value = "/users/{username}") - there are GET methods. But for AJAX make controller with POST:
#RequestMapping(value = "/users/{username}", produces = "application/json", method = RequestMethod.POST)
And JS will be smth like this:
// Send the request
$.post("/users/"+user, data, function(response) {
// Do something with the request
});
Another advice (if it's possible in your situation) - rename url for your rest. E.g. add word api into the url.

Thymeleaf page refresh followup - Now with AJAX

As a followup to my earlier question about using Thymeleaf and preventing page refresh:
http://forum.thymeleaf.org/Preventing-page-refresh-Thymeleaf-amp-Spring-MVC-td4029155.html
Basically I had a working Spring MVC app that uses Thymeleaf to save form data. When the user saves the data the page would refresh (since I wanted to leave them on the page for more edits) and I wanted to eliminate the page refresh.
I have coded up some Javascript to use JQuery Ajax to post the data to my Spring MVC Controller. The trick seemed to be to not use a submit button, just a regular button and bind a JS function to it for sending the data to the server.
It all seems to work perfectly, but I want to make sure I understand what is happening. In particular I'm wondering if Thymeleaf is now redundant. I don't think it is because when I initially load the page Thymeleaf is still bound to the data bean. From using the debugger on the server side in the controller it looks like the post request calls the mapped method and passes in the data to the model.
I would appreciate your comments on whether or not this is the correct way to accomplish this.
Finally, how do I handle an error, say for example the repository fails to persist the data for any reason?
Thanks very much.
Here are the important parts of the form:
<FORM id="adminDataForm" action="#" th:action="#{/admin_ajax}" th:object="${adminFormAjax}" method="post">
<input type="button" value="Save Changes" id="post" onClick="sendData()" />
Here is the Javascript:
function sendData()
{
$.ajax(
{
type: "POST",
data: $("#adminDataForm").serialize(),
cache: false,
url: "/admin_ajax",
success: function(data)
{
alert("Your changes have been saved");
},
error: function()
{
alert("Error - Data not saved");
}
});
}
Here is the controller:
#SessionAttributes("adminFormAjax")
#Controller
public class TestController
{
final static protected long INDEX_RA = 2L;
#Autowired
private AdminDataRepository rep;
#RequestMapping(value="/admin_ajax", method=RequestMethod.GET)
public String adminFormAjax(Model model)
{
AdminData ad = rep.findById(INDEX_RA);
// If there is no configuration record, create one and assign the primary key
if(ad == null)
{
ad = new AdminData();
ad.setId(INDEX_RA);
}
model.addAttribute("adminFormAjax", ad);
return "adminFormAjax";
}
#RequestMapping(value="/admin_ajax", method=RequestMethod.POST)
public #ResponseBody AdminData adminSubmit(#ModelAttribute("adminFormAjax") AdminData ad, Model model)
{
rep.save(ad);
model.addAttribute("adminFormAjax", ad);
return ad;
}
}
So breakdown of answer.
Thymeleaf not redundant, it will still render the HTML page prior to sending to client. Ajax just does the further processing for you on client side.
You can use submit button as well, you just need to ensure your form is properly structured and you have javascript listening for your submit button click e.g.
$("#submitbutton").on('click', function (){//do stuff});
You handle any and all exceptions/issues within your Ajax controller as you would with standard controller. You need to separate issue handling at different levels. e.g. respository level issues should be managed at rep level, controller/pojo should be at controller level (or pojo if you using one for processing). You should also be capturing any exceptions through a global medium (e.g. ControllerAdvice).
Any issues/errors you pick up you should be communicating back via your return call in adminSubmit, and managing the relevant client response in ajax.

Passing data from MVC controller to Angular view without showing the data value in the final html

I am working a MVC+angular front end project. In one scenario, the angular page will be launched with a HTTPS GET request, so, it display a log in page. In another scenario, we will get HTTPS POST request with user name and password in the post data. In this case, we need to have the MVC controller pass these information to the angular controller, and start the angular page view differently. I read several articles on how to pass data from MVC controller to the angular controller, angular view. Here is what I have
MVC controller:
public class HomeController : Controller
{
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult Index(string name, string password)
{
if (name == null)
{
name = "test";
password = "pass";
}
UserInfoModel infoModel = new UserInfoModel(name, password);
var model = SerializeObject(infoModel);
return View(model);
}
public static IHtmlString SerializeObject(object value)
{
using (var stringWriter = new StringWriter())
using (var jsonWriter = new JsonTextWriter(stringWriter))
{
var serializer = new JsonSerializer
{
// Let's use camelCasing as is common practice in JavaScript
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
// We don't want quotes around object names
jsonWriter.QuoteName = false;
serializer.Serialize(jsonWriter, value);
return new HtmlString(stringWriter.ToString());
}
}
Index.cshtml
<!DOCTYPE html>
<html ng-app="MVCApp" ng-controller="LandingPageController">

<body>
<h1>{{models.username}}</h1>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
#Scripts.Render("~/bundles/MVCApp")
#model IHtmlString
<script>
angular.module("MVCApp", []).value("data", #Html.Raw(Model));
</script>
</body>
</html>
Angular application MVCApp.js is like:
var MVCApp = angular.module('MVCApp', []);
MVCApp.controller('LandingPageController', LandingPageController);
Angular controller is like:
var LandingPageController = function ($scope, data) {
console.log(data);
$scope.models = {
username: data.userName
};
}
LandingPageController.$inject = ['$scope', 'data'];
When I run it, I can see the data did get passed to the LandingPageController. But if I look at the html of the page, I see the following in the html.
angular.module("MVCApp", []).value("data", {userName:"test",password:"pass"});
I don't won't the data(at least some portion, like password) to be visible/readable in the html.
Can I pass the data without having it visible in the html?
#Html.Raw(Model) outputs the contents of your model when the page is rendered.
If you load the data via a separate ajax call after the page has loaded (and not with #Html.Raw(Model)), then it won't be visible by viewing the page source.
It can still very easily be found with a javascript debugger, so this will do absolutely nothing in terms of security. But if someone views the source, they won't see pages of JSON data.
The other downside is that you're making the user wait longer instead of providing the initial data when the page is first served.
Also, I don't know what you're ultimately trying to do, but passing cleartext passwords to a browser is almost never correct.

redirect to POST with javascript in asp.net mvc

I have 2 controller methods:
[HttpGet]
public ActionResult SomeAction(SomeModel model, string someString)
{
//..
return View()
and
[HttpPost]
public ActionResult SomeAction(SomeModel model)
I'm trying to find a way what would allow me to navigate directly to Post method from a controller (some other controller). I've heard the is a way to do it in javascript, but I can't find it anywhere. The idea would be to set a filed in model, and check it at beginning of a view - if it's set, do the javascript thing to redirect immediatly to Post action in controller. If anyone knows how to do it, I would aprreciate it
It sounds to me like you want to check, on page load, if the form has been filled out and if so immediately post it to the appropriate action?
If so
$(function() {
if () { // determine here if the form has been filled out and completed
$('#theform').submit(); // this posts the form, which will hit the post action
}
});

Categories