I'm currently having difficulty using Ajax to update a partial view without having to refresh the whole page. I'm using MVC and entity framework to scaffold views.
I'll try and include as much as possible to help explain myself:
I have a div which is going to be used to hold a list view of all my comments
<div id="ContainAnnouncementComments"> </div>
This div gets populated using the following:
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<script src="~/Custom_Scripts/BuildAnnouncement.js"></script>
#Scripts.Render("~/bundles/jqueryval")
$(document).ready(function () {
$.ajax({
url: '/Comments/BuildAnnouncementComment',
data: { AnnouncementId: #Model.AnnouncementId},
success: function (result) {
$('#ContainAnnouncementComments').html(result);
}
});
});
Which calls the BuildAnnouncementComment() method in my controller:
public ActionResult BuildAnnouncementComment(int? AnnouncementId)
{
return PartialView("_AnnouncementComment", db.Comments.ToList().Where(x => x.AnnouncementId == AnnouncementId));
}
I then have a second div container which is used to hold a text box for a user to enter some information which 'should' then update the ContainAnnouncementComments using Ajax replace call:
<div id="ContainAnnouncementCommentCreate">
#using (Ajax.BeginForm("AJAXCreate", "Comments", new { announcementId = Model.AnnouncementId }, new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "ContainAnnouncementComments"
}))
{
<div class="form-group">
#Html.AntiForgeryToken()
<div class="col-md-10">
#Html.EditorFor(model => model.Message, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Message, "", new { #class = "text-danger" })
</div>
</div>
}
</div>
The Ajax method calls the AJAXCreate method in the controller:
public ActionResult AJAXCreate(int announcementId, [Bind(Include = "CommentId, Message")] Comment comment)
{
if (ModelState.IsValid)
{
comment.UserId = User.Identity.GetUserId();
comment.AnnouncementId = announcementId;
db.Comments.Add(comment);
db.SaveChanges();
}
return PartialView("_AnnouncementComment", db.Comments.ToList());
}
From here, when running, I try to create a new comment, but when submitted, instead of the partialView being updated, all that is being displayed is the partialview.
Not sure if I've explained this properly so if i'm missing any information please let me know and i'll update accordingly.
After 3 hours of staring at my code I realised that nothing was actually wrong with the implementation and all that was wrong was I hadn't installed AJAX through the NuGet manager.
Joys.
Very new to both MVC and jQuery, so I'm not sure how to get this to work. I've cobbled together a (somewhat) working modal dialog form with an ajax postback. Been searching for two days and not finding great MVC+jQuery examples out there. The data gets inserted as expected, it's just the UI I'm having a hard time with.
I've got two views: Index and Create. Index lists all records inside a plain table, looping the results with Razor. Create is the insert form which I'm loading into a modal dialog:
#model IEnumerable<MyProject.Models.StockIndex>
#{
ViewBag.Title = "Admin: Stock Index Home";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>View Stock Indices</h2>
<p>
Create New
<div id="createStockIndexForm"></div>
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Description)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>
#section Scripts {
<script>
$('#createLink').on('click', function () {
$("#createStockIndexForm").dialog({
autoOpen: true,
position: { my: "center", at: "top+350", of: window },
width: 600,
resizable: false,
title: 'Create Stock Index',
modal: true,
open: function () {
$(this).load('#Url.Action("Create", "AdminStockIndex")');
}
});
return false;
});
</script>
}
Controller actions:
public ActionResult Create()
{
var model = new StockIndexEditViewModel()
{
StockIndices = GetIndices()
};
return View(model);
}
[HttpPost]
public ActionResult Create(StockIndexEditViewModel model)
{
if (ModelState.IsValid)
{
...
}
return PartialView(model);
}
The "Create" form that was loaded into the dialog (above):
#model MyProject.Models.StockIndexEditViewModel
#{
ViewBag.Title = "CreatePartial";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>CreatePartial</h2>
#using (Html.BeginForm("Create", "AdminStockIndex", FormMethod.Post, new { id = "createForm" }))
{
#Html.AntiForgeryToken()
<div id="result"></div>
<div class="form-horizontal">
<h4>StockIndexEditViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SelectedParentId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SelectedParentId, Model.StockIndices, "- Select One -", new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" id="createButton" class="btn btn-default" />
</div>
</div>
</div>
}
#section Scripts {
<script>
$("#createForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: this.action,
type: this.method,
async: true,
data: $(this).serialize(),
success: function (data) {
if (data) {
var createForm = $("#createStockIndexForm").dialog();
createForm.dialog("close");
}
else {
$("#result").append("Something went fail.");
}
}
});
});
</script>
}
The modal dialog always goes blank, rather than closing. In testing in Firefox using Firebug, I occasionally see this error, but not every time:
InvalidAccessError: A parameter or an operation is not supported by
the underlying object
Upon searching, I see that it's a CORS problem where FF is enforcing the rules and other browsers may not be. It's frustrating that the error doesn't always occur - appears to be random. Behaves the same in Chrome but doesn't ever throw an error in the JS console.
Firstly, how would I pull this off? Secondly, is there a way to refresh the table on the parent page via ajax, without plugins?
UPDATE:
With Eckert's help, I've made a little progress.
I'm trying to avoid the MVC Ajax helpers and sticking with a "pure" jQuery approach. I replaced the list of records on Index with a div, which contains a PartialView:
<div id="stockIndices">
#Html.Partial("_StockIndices", Model)
</div>
I've got the underlying table refresh working by reloading the div with the close property of the jQuery dialog:
$('#createLink').on('click', function () {
$("#createStockIndexForm").dialog({
autoOpen: true,
position: { my: "center", at: "top+400", of: window },
width: 600,
resizable: false,
title: 'Create Stock Index',
modal: true,
open: function () {
$(this).load('#Url.Action("Create", "AdminStockIndex")');
},
close: function () {
$("#stockIndices").load('#Url.Action("GetStockIndices", "AdminStockIndex")');
}
});
return false;
});
Upon closing the modal dialog manually, the div reloads just how I wanted. Great! Now if I could get the dialog to close when the form posts, I'd be set. This does not work:
$("#createStockIndexForm").dialog("close");
Firebug reports the error:
Error: cannot call methods on dialog prior to initialization;
attempted to call method 'close'
The modal dialog always goes blank, rather than closing.
It's probably behaving improperly because you're creating a variable based on the dialog method of an object, and not the object itself. Try this instead:
if (data) {
$("#createStockIndexForm").dialog("close");
}
Secondly, is there a way to refresh the table on the parent page via
ajax, without plugins?
There sure is, but it may require you to change some things, including the code to close your dialog. Here's how I would handle it:
1) your table of records should be a partial view within the main Index view. The reason for this is because when we submit your form, we'll use the ajax-option "InsertionMode" coupled with the target-id to replace your old records table with the updated one from the form.
2) rather than handling your dialog-close code in the on-submit method, we'll instead use the ajax-option "OnSuccess" to do that (and also "OnFailure" to handle your 'Something went fail' error).
So here's your new Index view:
#model IEnumerable<MyProject.Models.StockIndex>
#{
ViewBag.Title = "Admin: Stock Index Home";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>View Stock Indices</h2>
<p>
Create New
<div id="createStockIndexForm"></div>
</p>
<div id="recordsDiv">
#Html.Partial("RecordsPartial", model)
</div>
// all your script stuff can still go at the end
Most of the Index view is unchanged, with the exception that we're now using a that contains a partial view. This partial view will include the table of records, coded here:
Create a new partial view named "RecordsPartial":
#model IEnumerable<MyProject.Models.StockIndex>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Description)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
Now your "Create" view will be updated to use the mvc-ajax helper rather than use all that additional javascript code:
#model MyProject.Models.StockIndexEditViewModel
#{
ViewBag.Title = "CreatePartial";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>CreatePartial</h2>
#using (Ajax.BeginForm("CreateRecord", "AdminStockIndex", new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "recordsDiv", OnSuccess = "postSuccess", OnFailure = "postFailed" })
{
#Html.AntiForgeryToken()
<div id="result"></div>
<div class="form-horizontal">
<h4>StockIndexEditViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
/* form fields here */
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" id="createButton" class="btn btn-default" />
</div>
</div>
</div>
}
We changed your form to be ajax-post, and we've added ajax-options to handle what happens after your form has posted. The data that returns after the post (our updated records partial) will replace the current contents of the target-id "recordsDiv". OnSuccess function "postSuccess" will handle closing the dialog box. OnFailure function "postFailed" will report that something bad happened. The last thing to mention is that we changed the post-action from "Create" to "CreateRecord". I prefer to have unique action names when working with ajax-data returns. It's just a cleaner approach.
Before we define the new "CreateRecord" post-action, we need to implement our success and failure functions. Just create them at the bottom of your script-section block in the main "Index" view:
#section Scripts {
<script>
// ... other stuff that was already here ...
function postSuccess() {
$("#createStockIndexForm").dialog("close");
}
function postFailed() {
alert("Failed to post"); // you can define your own error
}
</script>
}
Lastly, we create the "CreateRecord" post-action, which will process our form and return an updated "records partial" view:
[HttpPost]
public ActionResult CreateRecord(StockIndexEditViewModel model)
{
if (ModelState.IsValid)
{
... create record here ...
}
var records = db.Indexes.ToList(); // or whatever your table name is
return PartialView("RecordsPartial", records);
}
This is repeating some of your existing "Create" action. We simply process our post data, then we get a new updated list of records, and lastly we return that list of records back to our "RecordsPartial" view, which will be inserted into our "recordsDiv" as we specified in our ajax-options.
Very clean and simple solution. If you have questions, feel free to ask.
In your main Index view, rather than calling for your Create view to be inserted into your view, have it initially present at view load, and hide it within a div:
<div id="createStockIndexForm">
#Html.Action("Create", "AdminStockIndex")
</div>
In your Index script section, we're creating the dialog OUTSIDE your click event. We're also turning the "autoOpen" value to false so the div hides at view load.
Index script section:
#section Scripts {
<script>
$("#createStockIndexForm").dialog({
autoOpen: false,
position: { my: "center", at: "top+350", of: window },
width: 600,
resizable: false,
title: 'Create Stock Index',
modal: true
});
$('#createLink').on('click', function () {
$("#createStockIndexForm").show();
});
</script>
}
Also, when you use the PreventDefault() command, it appears to be interfering with your modal-close command (after some of my own testing). I suggest that you change your form's "Create" button to type="button" rather than "submit", and then use the ID of the button to handle your ajax-post method.
<input type="button" id="createButton" value="Create" class="btn btn-default" />
Add this to your main Index script section at the bottom:
$("#createButton").on("click", function () {
$.ajax({
url: this.action,
type: this.method,
async: true,
data: $(this).serialize(),
success: function (data) {
if (data) {
$("#createStockIndexForm").dialog("close");
}
else {
$("#result").append("Something went fail.");
}
}
});
});
Make sure to direct your "close" command at the dialog object itself.
Here's a fiddle I created that shows you the gist of how it should be:
http://jsfiddle.net/ch5aLezg/
So to recap on the script stuff, you should have NO SCRIPTS in your "Create" partial. All the scripts go into the main Index view, as I've detailed in this answer.
I found a way to get everything I wanted, while retaining the "pure" jQuery Ajax approach, instead of resorting to the MVC Ajax helpers, suggested by Eckert. His suggestions lead me to the solution, however. Big thanks!
I created a PartialView in the controller:
public ActionResult GetStockIndices()
{
_service = new StockIndexService();
var data = _service.GetAll();
return PartialView("_StockIndices", data);
}
...and its view:
#model IEnumerable<MyApp.Models.StockIndex>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Description)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>
Then I changed the modal dialog script to load the partial view when closed. Here's the entire Index view, for posterity:
#model IEnumerable<MyApp.Models.StockIndex>
#{
ViewBag.Title = "Admin: Stock Index Home";
Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}
<h2>View Stock Indices</h2>
<p>
Create New
<div id="createStockIndexForm"></div>
</p>
<div id="stockIndices">
#Html.Partial("_StockIndices", Model)
</div>
#section Scripts {
<script>
var _dialog;
$('#createLink').on('click', function () {
_dialog = $("#createStockIndexForm").dialog({
autoOpen: true,
position: { my: "center", at: "top+400", of: window },
width: 600,
resizable: false,
title: 'Create Stock Index',
modal: true,
open: function () {
$(this).load('#Url.Action("Create", "AdminStockIndex")');
},
close: function () {
$("#stockIndices").load('#Url.Action("GetStockIndices", "AdminStockIndex")');
}
});
return false;
});
</script>
}
Notice the global "_dialog" variable. This gave me access to the dialog from the Create form, so it could be closed:
<script>
$("#createForm").on("submit", function (event) {
event.preventDefault();
$.ajax({
url: this.action,
type: this.method,
async: true,
data: $(this).serialize(),
success: function (data) {
if (data) {
if (_dialog) {
_dialog.dialog("close");
}
}
else {
$("#result").append("Error! Record could not be added.");
}
},
error: function (error) {
console.error(error);
}
});
});
</script>
I have a textbox on my MVC 4 view, and I would like to let the user press Enter on changing the text and call a controller's action method. Thanks.
Here is a part of my view:
#using (Html.BeginForm())
{
<div class="editor">
#Html.LabelFor(m => m.FolderPath)
#Html.TextBoxFor(m => m.FolderPath, new { #Id = "FolderPath", #style="width:500px;" })
#Html.ValidationMessageFor(m => m.FolderPath)
</div>
And a part of the controller:
[HttpPost]
[MultipleButton(Name = "action", Argument = "Refresh")]
public ActionResult Refresh(EdiFileModel ediFileModel)
{
if (Directory.Exists(ediFileModel.FolderPath))
{
FolderPath = ediFileModel.FolderPath;
}
else
{
ModelState.AddModelError("FolderPath", "This folder does not exist!");
}
ediFileModel = Load();
return View("Index", ediFileModel);
}
Add a submit button...
<input type="submit" value="Submit"/>
inside your form.
I have a view with two partials in it that use ajax to post their forms to actions. The onsuccess callback redirects the user to another url. However I don't want this onsuccess function to be called when the modelstate is not valid. I've tried returning a 400 level error from my controller to trigger the onfailure function but it causes some weird behavior and the validation errors don't display. Here's my code
Action:
[AllowAnonymous]
[DisableCache]
public ActionResult Login(string returnUrl ="/") //This is the view that contains the two other partial views.
{
var path = VirtualPathUtility.ToAbsolute(returnUrl);
var url = new Uri(Request.Url, path).AbsoluteUri;
ViewBag.ReturnUrl = url;
return View();
}
[AllowAnonymous]
public ActionResult _LoginPartial()
{
return PartialView(new LoginModel());
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult _LoginPartial(LoginModel model)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return PartialView();
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
Response.StatusCode = 400;
return PartialView(model);
}
Login View:
<hgroup class="title">
<h1>#ViewBag.Title.</h1>
</hgroup>
#section CustomScripts {
#if (ViewData.ModelState.IsValid){
<script type ="text/javascript">
function OnSuccess() {
var returnUrl = #Html.Raw(Json.Encode(ViewBag.ReturnUrl))
window.location = returnUrl;
}
function OnFailure() {
alert("Fail");
}
</script>
}
}
<section id="loginForm">
#{
if(!WebSecurity.IsAuthenticated){
<h2>Use a Creative Works account to log in.</h2>
#Html.Action("_LoginPartial")
#Html.ActionLink("Forgot your password?", "ResetPassword")
}
}
</section>
<section id ="RegisterForm">
#{
if(!WebSecurity.IsAuthenticated){
<span>Don't have an account? Make one!</span>
#Html.Action("RegisterPartial", new { returnUrl = ViewBag.ReturnUrl })
}
}
</section>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
_LoginPartial view
#model Cwo.Domain.Entities.LoginModel
#{
ViewBag.Title = "LoginPartial";
}
#using (Ajax.BeginForm("_LoginPartial",
new AjaxOptions(){UpdateTargetId = "loginForm",
InsertionMode = InsertionMode.Replace, OnSuccess = "OnSuccess", OnFailure = "OnFailure"
})) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Log in Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
</li>
<li>
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
</li>
<li>
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe, new { #class = "checkbox" })
</li>
</ol>
<input type="submit" value="Log in" />
</fieldset>
}
The formatting got kinda messed up pasting in here, sorry about that. Instead of an alert("fail") response I want the validation errors to display. If there's a better way than returning a 400 level error from the action please teach me!
I think another way to word you problem is you don't want the contents of the onsuccess function to be executed? if this is the case then move your #if (ViewData.ModelState.IsValid){ ... } to inside the OnSuccess function. if state is invalid that function would have no guts so nothing gets done.
personally I like keeping javascript separate from razor...even more, the javascript should reside in its own js file. in this case you could use razor to set a bool javascript variable to a function or object constructor, which would then do your success work only if variable was true.
how can i do, for example, i create a user in users.cshtml view, that it validates to ActionResult Create(RegisterModel um) and if its all ok, i want to return at users.cshtml but always with one javascript alert or similar, with the value of a variable from the action. Can i do this one?
I have it this in my view..
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Crear Usuario</legend>
<div class="editor-label">
Username:
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
Password:
</div>
<div class="editor-field">
#Html.PasswordFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password)
</div>
<div class="editor-label">
Repite Password:
</div>
<div class="editor-field">
#Html.PasswordFor(model => model.ConfirmPassword)
#Html.ValidationMessageFor(model => model.ConfirmPassword)
</div>
</div>
<p>
<input type="submit" value="Crear" />
</p>
</fieldset>
And this in my controller action..
public ActionResult Create(RegisterModel um)
{
if (um.Password == um.ConfirmPassword)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(um.UserName, um.Password, um.Email, um.PasswordAnswer, um.PasswordQuestion, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
var alert = MembershipCreateStatus.Success.ToString();
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
var alert = ErrorCodeToString(createStatus);
}
}
//HERE IS WHERE I WANT TO RETURN TO /ADMIN/USERS BUT WITH AN ALERT WITH CONTAINING THE VALUE OF alert IN A JAVASCRIPT OR SIMILAR ALERT WINDOW
return RedirectToAction("Users", "Admin"); ???????
Can i do it something like this?
You could store the message inside TempData just before redirecting to the Users action:
TempData["message"] = "some message that you want to display";
return RedirectToAction("Users", "Admin");
and then inside the Users.cshtml view (which is returned by the Users action to which you redirected) test for the presence of this message and display an alert:
#if (TempData["message"] != null) {
<script type="text/javascript">
alert(#Html.Raw(Json.Encode(TempData["message"])));
</script>
}
i was trying to show error in same view, instead of trying darin's answer i tried other like this using ViewBag and this to show error in another page by returning Partial View
finally i tried using TempData, to maintain the message during post, here i am showing error using bootstrap
action method:
[HttpPost]
public ActionResult RegDetail(User_table usob, FormCollection col)
{
// some other code here
if (!sqlReader.HasRows)
{
TempData["message"] = "Contains some message";
return RedirectToAction("Registration");
}
return RedirectToAction("Index");
}
my registration view:
#if (TempData["message"] != null)
{
<link href="~/css/boostrap.css" rel="stylesheet" />
<script src="~/js/boostrap.min.js"></script>
<div class="alert alert-danger">
<a class="close" data-dismiss="alert">×</a>
<strong style="width:12px">Error!</strong> Thats wrong code no, try entering correct!
</div>
}
if you need to explore more using Tempdata you can check this where Mr.James explains using bootstrap messages, hope helps someone