How do I process drop down list change event asynchronously? - ajax

I have a drop down list that I need to react to asynchronously. I cannot get the Ajax.BeginForm to actually do an asynchronous postback, it only does a full postback.
using (Ajax.BeginForm("EditStatus", new AjaxOptions { UpdateTargetId = "divSuccess"}))
{%>
<%=Html.DropDownList(
"ddlStatus",
Model.PartStatusList.OrderBy(wc => wc.SortOrder).Select(
wc => new SelectListItem
{
Text = wc.StatusDescription,
Value = wc.PartStatusId.ToString(),
Selected = wc.PartStatusId == Model.PartStatusId
}),
new { #class = "input-box", onchange = "this.form.submit();" }
)%>
<div id='divSuccess'></div>
<%
}
When the user selects an item from the list, it does a full postback and the controller method's return value is the only output on the screen. I am expecting the controller method's return value to be displayed in "divSuccess".
[AjaxAwareAuthorize(Roles = "Supplier_Administrator, Supplier_User")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditStatus(PartPropertiesViewModel partPropertiesViewModel)
{
var part = _repository.GetPart(partPropertiesViewModel.PartId);
part.PartStatusId = Convert.ToInt32(Request.Form["ddlStatus"]);
_repository.SavePart(part);
return Content("Successfully Updated Status.");
}

How about doing this the proper way using jQuery unobtrusively and getting rid of those Ajax.* helpers?
The first step is to use real view models and avoid the tag soup in your views. Views should not order data or whatever. It's not their responsibility. Views are there to display data that is being sent to them under the form of a view model from the controller. When I see this OrderBy in your view it's just making me sick. So define a clean view model and do the ordering in your controller so that in your view you simply have:
<% using (Html.BeginForm("EditStatus", "SomeControllerName", FormMethod.Post, new { id = "myForm" }) { %>
<%= Html.DropDownListFor(
x => x.Status,
Model.OrderedStatuses,
new {
#class = "input-box",
id = "myDDL"
}
) %>
<% } %>
<div id="divSuccess"></div>
and finally in a completely separate javascript file subscribe for the change event od this DDL and update the div:
$(function() {
$('#myDDL').change(function() {
var url = $('#myForm').attr('action');
var status = $(this).val();
$.post(url, { ddlStatus: status }, function(result) {
$('#divSuccess').html(result);
});
});
});
This assumes that in your controller action you would read the current status like this:
[AjaxAwareAuthorize(Roles = "Supplier_Administrator, Supplier_User")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditStatus(PartPropertiesViewModel partPropertiesViewModel, string ddlStatus)
{
var part = _repository.GetPart(partPropertiesViewModel.PartId);
part.PartStatusId = Convert.ToInt32(ddlStatus);
_repository.SavePart(part);
return Content("Successfully Updated Status.");
}
And when you see this you might ask yourself whether you really need a form in this case and what's its purpose where you could simply have a dropdownlist:
<%= Html.DropDownListFor(
x => x.Status,
Model.OrderedStatuses,
new Dictionary<string, object> {
{ "class", "input-box" },
{ "data-url", Url.Action("EditStatus", "SomeControllerName") },
{ "id", "myDDL" }
}
) %>
<div id="divSuccess"></div>
and then simply:
$(function() {
$('#myDDL').change(function() {
var url = $(this).data('url');
var status = $(this).val();
$.post(url, { ddlStatus: status }, function(result) {
$('#divSuccess').html(result);
});
});
});

Related

How to get selected Drop down list value in view part of MVC?

I want to pass selected Drop down list value to Ajax Action Link which is I am using in Controller. Every time When I will change drop down list value. I want that respective value pass to the action link.
What I need to write here in Ajax Action Link ????
Drop Down List
<div class="form-group">
#Html.DropDownListFor(model => model.ComponentId, ((List<string>)ViewBag.Cdll).Select(model => new SelectListItem { Text = model, Value = model }), " -----Select Id----- ", new { onchange = "Action(this.value);", #class = "form-control" })
</div>
Ajax Action Link
<div data-toggle="collapse">
#Ajax.ActionLink("Accessory List", "_AccessoryList", new { ComponentId = ???? }, new AjaxOptions()
{
HttpMethod = "GET",
UpdateTargetId = "divacc",
InsertionMode = InsertionMode.Replace
})
</div>
Controller
public PartialViewResult _AccessoryList(string ComponentId)
{
List<ComponentModule> li = new List<ComponentModule>();
// Code
return PartialView("_AccessoryList", li);
}
Here is a new post. I do dropdowns a little different than you, so I am showing you how I do it. When you ask what to pass, I am showing you how to pass the dropdown for 'component' being passed. I also show how to pass from ajax back to the page.
Controller/Model:
//You can put this in a model folder
public class ViewModel
{
public ViewModel()
{
ComponentList = new List<SelectListItem>();
SelectListItem sli = new SelectListItem { Text = "component1", Value = "1" };
SelectListItem sli2 = new SelectListItem { Text = "component2", Value = "2" };
ComponentList.Add(sli);
ComponentList.Add(sli2);
}
public List<SelectListItem> ComponentList { get; set; }
public int ComponentId { get; set; }
}
public class PassDDLView
{
public string ddlValue { get; set; }
}
public class HomeController : Controller
{
[HttpPost]
public ActionResult PostDDL(PassDDLView passDDLView)
{
//put a breakpoint here to see the ddl value in passDDLView
ViewModel vm = new ViewModel();
return Json(new
{
Component = "AComponent"
}
, #"application/json");
}
public ActionResult IndexValid8()
{
ViewModel vm = new ViewModel();
return View(vm);
}
View:
#model Testy20161006.Controllers.ViewModel
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>IndexValid8</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#btnClick").click(function () {
var PassDDLView = { ddlValue: $("#passThis").val() };
$.ajax({
url: '#Url.Action("PostDDL")',
type: 'POST',
data: PassDDLView,
success: function (result) {
alert(result.Component);
},
error: function (result) {
alert('Error');
}
});
})
})
</script>
</head>
<body>
<div class="form-group">
#Html.DropDownListFor(m => m.ComponentId,
new SelectList(Model.ComponentList, "Value", "Text"), new { id = "passThis" })
<input type="button" id="btnClick" value="submitToAjax" />
</div>
</body>
</html>

ActionLink to submit Model value

I want my Ajax.ActionLink to pass a viewModel property to action.
Here is my ViewModel
public class ViewModel
{
public string Searchtext { get; set; }
}
My .cshtml
#Ajax.ActionLink("Bottom3", "Bottom3",new { name = Model.Searchtext}, new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "pointsDiv"
})
using(Html.BeginForm("Bottom3", "Home", FormMethod.Get))
{
#Html.TextBoxFor(x => x.Searchtext)
<button type="submit">Search</button>
}
<div id="pointsDiv"></div>
}
My Controller action:
public PartialViewResult Bottom3(string name)
{
var model = db.XLBDataPoints.OrderBy(x => x.DataPointID).Take(3).ToList();
return PartialView("Partial1", model);
}
But the name parameter passed to the action is always null. How do I solve this?
In your code... you have 2 different ways of posting to the server: the link and the form button.
The problem is that the ActionLink has no way to get the value from the input in client side... just the original value.
If you press the Search button, you will see a value posted.
Now, you can use some jQuery to modify a standard ActionLink (not the Ajax.ActionLink):
https://stackoverflow.com/a/1148468/7720
Or... you can transform your Form in order to do a Ajax post instead of a normal one:
https://stackoverflow.com/a/9051612/7720
I did this for a model of mine like so. I ONLY supported the HttpPost method. So add the HttpMethod="POST" to your Ajax.ActionLink
[HttpPost]
public ActionResult Accounts(ParametricAccountsModel model)
{
if (model.Accounts == null)
{
GetAccountsForModel(model);
}
if (model.AccountIds == null)
{
model.AccountIds = new List<int>();
}
return View(model);
}
On the razor view
#Ajax.ActionLink(
"Add Account to Order", "Accounts", "Parametric", null,
new AjaxOptions() { InsertionMode = InsertionMode.Replace, UpdateTargetId = "...", HttpMethod = "POST" },
new { #id = "AddParametricAccountLink" })
The model has a list of selected account ids. So in javascript, I modified the href of the action link dynamically.
function UpdateParametricAccountAction() {
var originalLink = '/TradeNCashMgmt/Parametric/Accounts';
var append = '';
var numberOfRows = $('#ParametricAccounts').find('.parametric-account- row').size();
for (var i = 0; i < numberOfRows; i++) {
if (i != 0) {
append += '&';
}
else {
append = '?';
}
var idValue = $('#NotionalTransactionsAccountId_' + i).val();
append += 'AccountIds%5B' + i + '%5D=' + idValue;
}
$('#AddParametricAccountLink').attr('href', originalLink + append);
}
Since the model binder looks for parameter names in the query string and form submission, it will pick up values using the href. So I posted a model object using the querystring on my Ajax.ActionLink. Not the cleanest method, but it works.

Paging/Sorting not working on web grid used in Partial View

I have a partial view where I am showing a web grid depending upon a value selected from a page.
For drop down I have used
#Html.DropDownListFor(
x => x.ItemId,
new SelectList(Model.Items, "Value", "Text"),
new {
id = "myddl",
data_url = Url.Action("Foo", "SomeController")
}
)
For drop down item select I have used
$(function() {
$('#myddl').change(function() {
var url = $(this).data('url');
var value = $(this).val();
$('#result').load(url, { value: value })
});
});
and below is my action
public ActionResult Foo(string value)
{
SomeModel model = ...
return PartialView(model);
}
everything works good, but when I try doing a paging or sorting on my webgrid which is on my partial view I am showing a new window with the web grid.
I wanted to be able to sort and page on the same page without postback
Please help
The following example works fine for me.
Model:
public class MyViewModel
{
public string Bar { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Foo(string value)
{
var model = Enumerable.Range(1, 45).Select(x => new MyViewModel
{
Bar = "bar " + value + " " + x
});
return PartialView(model);
}
}
Index.cshtml view:
<script type="text/javascript">
$(function () {
$('#myddl').change(function () {
var url = $(this).data('url');
var value = $(this).val();
$.ajax({
url: url,
type: 'GET',
cache: false,
data: { value: value },
success: function (result) {
$('#result').html(result);
}
});
});
});
</script>
#Html.DropDownList(
"id",
new[] {
new SelectListItem { Value = "val1", Text = "value 1" },
new SelectListItem { Value = "val2", Text = "value 2" },
new SelectListItem { Value = "val3", Text = "value 3" },
},
new {
id = "myddl",
data_url = Url.Action("Foo", "Home")
}
)
<div id="result">
#Html.Action("Foo")
</div>
Foo.cshtml partial:
#model IEnumerable<MyViewModel>
#{
var grid = new WebGrid(
canPage: true,
rowsPerPage: 10,
canSort: true,
ajaxUpdateContainerId: "grid"
);
grid.Bind(Model, rowCount: Model.Count());
grid.Pager(WebGridPagerModes.All);
}
#grid.GetHtml(
htmlAttributes: new { id = "grid" },
columns: grid.Columns(
grid.Column("Bar")
)
)
Notice that I have used a GET request to refresh the grid instead of POST because this way the value query string parameter selected in the dropdown is preserved for future sorting and paging.

ASP.NET MVC - Ajax.BeginForm - conditionally pop-up modal dialog upon form submit?

I have a voting control that has several buttons. It is implemented using Ajax.BeginForm that has a hidden field named voteTypeHidden. Any time any of the voting buttons is clicked, it changes the value of voteTypeHidden, to update what type of vote is being cast. This form is submitted to the server, and it reports back with any error using ViewState["VoteError"]. Now, I want to display this error using my javascript function
ModalDialog(button, text, fadeOut)
How to accomplish this? It is very important that the text parameter passed to the ModalDialog function is ViewState["VoteError"].
Btw, it is not necessary, but I have also posted the code that I am using.
Here is the javascript:
<script type = "text/javascript">
var voteClickEnabled = true;
function voteClicked_Set(value) {
var voteType = value;
document.getElementById("voteTypeHidden").setAttribute("value", value);
}
function voteBegin() {
//alert("begin");
if (voteClickEnabled == false) {
return false;
}
else {
voteClickEnabled = false;
return true;
}
}
function voteEnd() {
//alert("end");
voteClickEnabled = true;
}
</script>
Here is the view:
<div id = "updateVotes">
<table>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</table>
Favorite :
<%=Html.Encode(ViewData["FavoriteCount"]) %>
<%//if(((int)ViewData["VoteBits"] & 1) == 0)
{%>
<% using (Ajax.BeginForm("Voting", "Vote", new {voted = ViewData["voted"], favorited = ViewData["favorited"], markedspam = ViewData["markedspam"], VotingOnId = (long)ViewData["VotingOnId"], VoteOn = (int)ViewData["VoteOn"], num_votes = 0, num_favorite = (int?)ViewData["FavoriteCount"], sdg = (int?)ViewData["sdg"], category = (int?)ViewData["category"] }, new AjaxOptions { UpdateTargetId = "updateVotes", OnBegin = "voteBegin", OnComplete = "voteEnd" }))
{%>
<%=Html.Hidden("voteTypeHidden", "temp", new { id = "voteTypeHidden" })%>
<%if ((bool)ViewData["voted"] == false)
{ %>
<%=Html.SubmitImage("voteButton", Url.Content("/Images/thumb_up.png"), new {value = "1", onclick = "voteClicked_Set(this.value)" })%>
<%}
else
{ %>
<%=Html.SubmitImage("voteButton", Url.Content("/Images/cancel.png"), new { value = "2", onclick = "voteClicked_Set(this.value)" })%>
<%} %>
<%if ((bool)ViewData["favorited"] == false)
{ %>
<%=Html.SubmitImage("voteButton", Url.Content("/Images/star_off_32.png"), new { value = "3", onclick = "voteClicked_Set(this.value)" })%>
<%}
else
{ %>
<%=Html.SubmitImage("voteButton", Url.Content("/Images/star_32.png"), new { value = "4", onclick = "voteClicked_Set(this.value)" })%>
<%} %>
<%if ((bool)ViewData["markedspam"] == false)
{ %>
<%=Html.SubmitImage("voteButton", Url.Content("/Images/error.png"), new { value = "5", onclick = "voteClicked_Set(this.value)" })%>
<%}
else
{ %>
<%=Html.SubmitImage("voteButton", Url.Content("/Images/error_go.png"), new { value = "6", onclick = "voteClicked_Set(this.value)" })%>
<%} %>
<%}%>
<%}%>
<div id = "testingAjax">
</div>
<% if (!string.IsNullOrEmpty((string)ViewData["VoteError"])) { %>
<script type = "text/javascript">
ModalDialog("testingAjax", "asd", false);
</script>
<%} %>
</div>
just do normal html button image all with same class but you a html attribute of say data-votetype=""
place a hidden div at top of page called results or feedback or something like that, based on the idea that the button class is vote:
$('.vote').live("click",function(e){
var voteType = $(this).attr("data-votetype");
// do post to JsonResults
$.ajax(
url: "",
type:"POST",
data:{id:$("#idfield").val(),votetype:voteType},
dataType:"json",
success: function(data){
$("#feedback").html(data.Message).show();
},
error:function(a,b,c){ $("#feedback").html("Error description: " + b).show(); }
);
});
if your going to work with ajax, you may as well work with the the data model its designed for and that's a JSON respone, example code:
public class JsonResponseModel {
public bool Success {get;set;}
public string Message {get;set;}
}
public JsonResults CastVote(int id, int votetype){
var model = new JsonREsponseModel{ Success = false };
// do code in try catch. change model to reflect success and message
return Json(model);
}

MVC3 Custom HTMLHelper, partial view or other solution to apply DRY principle

I've got an MVC3 Read Only view that contains a table displaying properties for an Item.
For many of the properties of the Item, we track the changes a Vendor has made to the item. So, for example, a vendor may update a property named 'Color' from a value of 'Blue' to 'Red'. In this View a table lists each property tracked in a table row, with a column showing the 'Old Value' and the 'New Value'. The next column either shows the current change's status (Awaiting Approval, Approved, or Rejected). However, for Admin users, the column will contain Links ('Approve', 'Reject', or 'Reset to Awaiting Approval').
My markup and Razor code for this is very repetitive and getting out of hand. I'd like to create an HTMLHelper for this, or possibly a partial view that I can use to move all the code into and then use it for each Item Property.
Here is an example of the code used for one Property. This code is repeated for another 10 or so properties.
I'm using some jquery and ajax for the actions. For example, when an change is rejected, the user must enter a reason for rejecting the change.
<tr id="rowId-color">
<td>#Html.LabelFor(model => model.Color)</td>
<td>#Html.DisplayFor(model => model.Color)</td>
#if (Model.ChangeLog != null && Model.ChangeLog.Item("Color") != null) {
var change = Model.ChangeLog.Item("Color");
var changeStatus = (ItemEnumerations.ItemChangeStatuses)change.ItemChangeStatusID;
<td>#change.OldValueDisplay</td>
<td id="tdstatusId-#change.ItemChangeID">
#if (changeStatus == ItemEnumerations.ItemChangeStatuses.AwaitingApproval && User.IsInRole("TVAPMgr")) {
#Ajax.ActionLink("Approve", "Approve", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Approve this change?", OnSuccess = "actionCompleted" })
#Html.Raw("|")
<a href="#dialog" name="reject" data-id="#change.ItemChangeID" >Reject</a>
}
else if ((changeStatus == ItemEnumerations.ItemChangeStatuses.Rejected || changeStatus == ItemEnumerations.ItemChangeStatuses.Approved) && User.IsInRole("TVAPMgr")) {
#Ajax.ActionLink("Reset to Awaiting Approval", "Reset", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Reset this change to Awaiting Approval?", OnSuccess = "actionCompleted" })
}
else {
#changeStatus.ToDisplayString()
}
</td>
<td id="tdreasonId-#change.ItemChangeID">#Html.DisplayFor(m => m.ChangeLog.Item(change.ItemChangeID).RejectedReason)</td>
}
else {
<td colspan="3">No Change</td>
}
</tr>
This really sounds more like a DisplayTemplate for the ItemChangeModel type, that way you can just do:
<tr id="rowId-color">
<td>#Html.LabelFor(model => model.Color)</td>
<td>#Html.DisplayFor(model => model.Color)</td>
#Html.DisplayFor(m => m.ChangeLog.Item("Color"))
</tr>
For each ChangeLog cell and the display template then is like a mini-view with a typed model of ItemChangeModel. So your view file would like like this:
#model ItemChangeModel
#if(Model != null) {
<td>#Html.DisplayFor(m => m.OldValueDisplay)</td>
<td id="tdstatusId-#Model.ItemChangeID">
#switch((ItemEnumerations.ItemChangeStatuses) Model.ItemChangeStatusID) {
case ItemEnumerations.ItemChangeStatuses.AwaitingApproval:
if(User.IsInRole("TVAPMgr")) {
#Ajax.ActionLink("Approve", "Approve", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Approve this change?", OnSuccess = "actionCompleted" })
#Html.Raw("|")
<a href="#dialog" name="reject" data-id="#change.ItemChangeID" >Reject</a>
}
break;
case ItemEnumerations.ItemChangeStatuses.Rejected:
case ItemEnumerations.ItemChangeStatuses.Approved:
if(User.IsInRole("TVAPMgr")) {
#Ajax.ActionLink("Reset to Awaiting Approval", "Reset", new { itemChangeID = change.ItemChangeID }, new AjaxOptions { HttpMethod = "POST", Confirm = "Reset this change to Awaiting Approval?", OnSuccess = "actionCompleted" })
} else {
#changeStatus.ToDisplayString()
}
#break;
}
</td>
<td id="tdreasonId-#change.ItemChangeID">#Html.DisplayFor(m => m.RejectedReason) </td>
} else {
<td colspan="3">No Change</td>
}
(Hard to code in editor box, this could use some cleanup, but I think you will get the idea)
You add this display template (with the file name ItemChangeModel.cshtml) to the Views\Shared\DisplayTemplates folder and it will get used whenever a DisplayFor call is made on that type.
Its been noted in comments that you can't use a method in DisplayFor, but you can change that to an indexed property:
public class ChangeLog
{
public ItemChangeModel this[string key] { get { return Item("Color"); } }
}
Then use:
#Html.DisplayFor(m => m.ChangeLog["Color"])
You haven't shown nor explained how your domain and view models look like but I suspect that what you are using here is not an appropriate view model for this specific requirement of the view. A better view model would have been one that has a list of properties to approve which would be shown in the table.
Anyway, one possible approach is to write a custom HTML helper so that your view looks like this:
<tr id="rowId-color">
#Html.DisplayFor(x => x.Color)
#Html.ChangeLogFor(x => x.Color)
</tr>
...
and the helper might be something along the line of:
public static class HtmlExtensions
{
public static IHtmlString ChangeLogFor<TProperty>(
this HtmlHelper<MyViewModel> html,
Expression<Func<MyViewModel, TProperty>> ex
)
{
var model = html.ViewData.Model;
var itemName = ((MemberExpression)ex.Body).Member.Name;
var change = model.ChangeLog.Item(itemName);
if (change == null)
{
return new HtmlString("<td colspan=\"3\">No Change</td>");
}
var isUserTVAPMgr = html.ViewContext.HttpContext.User.IsInRole("TVAPMgr");
var changeStatus = (ItemChangeStatuses)change.ItemChangeStatusID;
var sb = new StringBuilder();
sb.AppendFormat("<td>{0}</td>", html.Encode(change.OldValueDisplay));
sb.AppendFormat("<td id=\"tdstatusId-{0}\">", change.ItemChangeID);
var ajax = new AjaxHelper<MyViewModel>(html.ViewContext, html.ViewDataContainer);
if (changeStatus == ItemChangeStatuses.AwaitingApproval && isUserTVAPMgr)
{
sb.Append(
ajax.ActionLink(
"Approve",
"Approve",
new {
itemChangeID = change.ItemChangeID
},
new AjaxOptions {
HttpMethod = "POST",
Confirm = "Approve this change?",
OnSuccess = "actionCompleted"
}).ToHtmlString()
);
sb.Append("|");
sb.AppendFormat("Reject", change.ItemChangeID);
}
else if ((changeStatus == ItemChangeStatuses.Rejected || changeStatus == ItemChangeStatuses.Approved) && isUserTVAPMgr)
{
sb.Append(
ajax.ActionLink(
"Reset to Awaiting Approval",
"Reset",
new {
itemChangeID = change.ItemChangeID
},
new AjaxOptions {
HttpMethod = "POST",
Confirm = "Reset this change to Awaiting Approval?",
OnSuccess = "actionCompleted"
}
).ToHtmlString()
);
}
else
{
sb.Append(changeStatus.ToDisplayString());
}
sb.AppendLine("</td>");
sb.AppendFormat(
"<td id=\"tdreasonId-{0}\">{1}</td>",
change.ItemChangeID,
html.Encode(model.ChangeLog.Item(change.ItemChangeID).RejectedReason)
);
return new HtmlString(sb.ToString());
}
}
A better approach would be to re-adapt your view model to the requirements of this view and simply use display templates.

Resources