I have 3 DropDownLists; when I select an item in #1, I want the items in #2 to be filled according the value of #1. And when I select an item in #2, the items in #3 should be filled according the selection in #2. They all are located in a form named GetItemsForLeve1. I've started by using onchange of the drop-down.
<% using (Html.BeginForm("GetItemsForLeve1", "Index"))
{ %>
Main Group: <%:Html.DropDownList("Level1", (SelectList)ViewBag.Level1, "Select One", new { onchange = "$this.form.submit()" })%>
<%:Html.DropDownList("Level2", (SelectList)ViewBag.Level2, "-", new { onchange = "$this.form.submit()" })%>
<%:Html.DropDownList("Level3", (SelectList)ViewBag.Level3, "-", new { onchange = "$this.form.submit()" })%>
<input type="submit" value="Filter" />
<%}%>
Is it possible to fill the level 2 and level 3 drop-down lists without sending the page back to the server?
How can I tell which drop-down list has been clicked in the GetItemsForLevel action?
I am completely new to MVC, so I appreciate telling me in a simple way?
Thank you
As far as I know, there's not really a component that does this for you. But you can use the Ajax.BeginForm helper to build it.
the first ajax form should contain the first select list, and post back to an action that returns a partial view
the partial view in 1. should return the second ajax form with a second select list
and so forth
So the main Razor view should contain something like this:
#using (Ajax.BeginForm("SecondAjaxForm", new AjaxOptions() { UpdateTargetId = "secondFormDiv" })
{
#Html.DropDownList("selectList1", Model.FirstSelectList, new { onchange = "this.form.submit()")
}
<!-- the second AJAX form + drop-down list will get populated here
<div id="secondFormDiv"></div>
And the SecondAjaxForm action:
public ActionResult SecondAjaxForm(string selectList1)
{
SelectList secondSelectList;
// populate the second select list here
return PartialView("SecondAjaxForm", secondSelectList);
}
The partial view SecondAjaxForm should be basically the same as the first form above.
#model SelectList
#using (Ajax.BeginForm("ThirdAjaxForm", new AjaxOptions() { UpdateTargetId = "thirdFormDiv" })
{
#Html.DropDownList("selectList2", Model, new { onchange = "this.form.submit()")
}
<!-- the third AJAX form + drop-down list (if any) will get populated here
<div id="thirdFormDiv"></div>
Related
I'm playing around in an MVC application and am trying to figure out something that seems pretty straight forward.
I have a Index.cshtml file in my Views/Home/ folder that is pretty simple (below)
Index view
...
<div>
Search
#Html.DropDownList("selection", MyProject.Util.Lists.GetMyList(), "Select One")
#Html.ActionLink("Search", "Index", "Search", new { st = xxx }, null)
</div>
...
I also have a Search controller that needs to take a "st" value and looks like this
public class SearchController : Controller
{
// GET: Search
public ActionResult Index(string st)
{
ApplicationDbContext db = new ApplicationDbContext();
List<Report> filteredReports = db.Reports.Where(r => r.Tag == st).ToList();
return View(filteredReports);
}
}
What I'm not sure of how do is grab what ever value is selected from the drop down and add that to my Html.ActionLink where I just have 'xxx' now.
Clicking on the link usuallly does a new GET request to the href attribute value of the link. It will not send any data from your form.
You need to use javascript and hijack the click event on the link and append the selected option value from the SELECT element as querystring to that and send it.
So give an id to the link which you can use later to wireup the click event
#Html.ActionLink("Search", "Index", "Search", null, new {#id="search"})
And the javascript
$(function(){
$("#search").click(function(e){
e.preventDefault();
window.location.href=$(this).attr("href")+"?st="+$("#selection").val();
});
});
Another option is to use the form submit. Wrap your select element inside a form and have a submt button which sends the form to the action method. This method does not need javascript. But your select input element name should match with your parameter name.
#using(Html.BeginForm("Index","Search",FormMethod.Get))
{
<div>
Search
#Html.DropDownList("st", MyProject.Util.Lists.GetMyList(), "Select One")
<input type="submit" value="Search" />
</div>
}
If you do not prefer to have button, but need the link element, you can use javascript to submit the form (like we did in the first approach). With this approach you do not need to manually append the querystring.
You have to call a AJAX POST call to your controller which store selected value, and then when your action link event fire get stored value from there.
AJAX:
$.ajax(
{
url:your url,
data:{"st":dropdown selected value},
type:"post"
});
Controller:
public ActionResult Index()
{
string st=TempData["st"].ToString();
ApplicationDbContext db = new ApplicationDbContext();
List<Report> filteredReports = db.Reports.Where(r => r.Tag == st).ToList();
return View(filteredReports);
}
public void SetSt(string st)
{
TempData["st"] = st;
}
I am using an Ajax.BeginForm inside a For Loop with allows me to post to the controller for each item in a the for loop. Initially the for loop renders each item correctly on page load.
When the controller has handled the incoming View Model which is populated correctly from the Ajax.BeginForm, the method sends back a newly generated view model back to the View, however each item in my For loop is now duplicated, and all the textboxes now have the value of the submitted View Model.
Very confused how to structure this code correctly, I need the submission to work based on ajax and partial view. I did look at using JQuery to submit, but was concerned I may lose the AntiForgeryToken and that the Ajax.BeginForm was a more elegant approach. Any help greatly appreciated. Happy to provide more information also.
VIEW
#model Namespace.Models.MyParentViewModel
#for (int i = 0; i < Model.Items.Count; i++)
{
using (Ajax.BeginForm("SaveItem", "Controller", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "pensions" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<div>
#Html.Hidden("ID", Model.Items[i].ID)
#Html.TextBox("TheName", Model.Items[i].TheName, new { #class = "form-control", #id = "item-" + i})
<input type="submit" value="Save" class="btn save" name="Command" />
</div>
}
}
CONTROLLER
[HttpPost]
[ValidateAntiForgeryToken]
[ActionName("SaveItem")]
public ActionResult SaveItem(MyItemViewModel mivm, string Command)
{
if (ModelState.IsValid)
{
#do some logic
}
// Return a newly populated MyViewModel with updated mivm.
var model = PopulateMyParentViewModel();
return PartialView("_MyPartialView", model);
}
private MyParentViewModel PopulateMyParentViewModel()
{
List<MyItemsViewModel> lstItems = new List<MyItemsViewModel>();
foreach (var item in enity.items.OrderBy(p => p.ID).ToList())
{
var ExistingItem = new MyItemViewModel
{
ID = item.ID,
TheName = item.TheName
};
lstItems.Add(MyItemViewModel);
}
MyParentViewModel.Items = lstItems;
return MyParentViewModel
}
I am not sure what is "pensions" in your code, I guess it is a div surrounding every form generated by the loop? If so your problem is the following:
First, if none of the items are interrelated (that is, triggering some update on an item causes another item to be updated as well), you shouldn't waste resources to send the whole list back to the browser - consider just sending back the updated display of the affected "record" and just update the corresponding item with the response.
What causes items to be duplicated: the AjaxOptions class has a property InsertionMode, and as I can recall, its default value is InsertionMode = InsertionMode.InsertAfter, which causes the duplicates to appear. Why? You send an Ajax request to the server, which sends back an HTML fragment populated by the entire list, instead of just one item, then the browser appends that fragment to the existing records.
Solution
If you remake your project to just send back one record instead of all of them, the just add an uniquely identified (= has an unique id attribute) <div> element around the using(...) block, set InsertionMode to InsertionMode=InsertionMode.Replace and set the UpdateTargetId property to the id of that <div>, like so:
<div id="record-#i">
#using (Ajax.BeginForm("SaveItem", "Controller", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "record-" + i.ToString(), InsertionMode = InsertionMode.Replace }))
{
// ...
}
</div>
That will cause the Ajax response to replace the contents of the container from which the request is sent (by contents I mean that the wrapping element itself is not replaced! You would need to use InsertionMode.ReplaceWith for that behavior). If you mistake this, you end up dumping crazy amounts of nested elements inside the host container, possibly breaking scripts and/or styles which use very specific selectors.
If you stick to sending a whole collection of items back, then just wrap the for loop with a <div> tag, give it an id, set InsertionMode to InsertionMode=InsertionMode.Replace, and the UpdateTargetId property to the id of that <div>.
--------- Update -----------
To answer your comment:
Assume you have a view which - for the sake of simplicity - displays records in a tabular format. Then, the result looks like this:
Name | Age | Salary | Hide
------------------------------------
Peter | 32 | $15k | Hide button
Eva | 28 | $12k | Hide button
Assume what you change with an Ajax request is that whenever you hide a record, you send back a single table row with a button that now displays 'show' instead of 'hide' and the same data about the person which belongs to the button that triggered the request.
Then, when you make a direct (=non-ajax, when you directly navigate to the page) request, you do this:
#model PeopleCollection
...
<table>
<thead>
<tr>
<td>Name</td>
<td>Age</td>
<td>Salary</td>
<td>Hide</td>
</tr>
</thead>
<tbody>
#foreach (Person record in Model) {
#Html.Partial("TableRow_Partial",record)
}
</tbody>
The "TableRow_Partial" view looks like this:
#model Person
#{
AjaxOptions opts = new AjaxOptions() {
UpdateTargetId = "person-row-" + Model.Id.ToString(),
InsertionMode = InsertionMode.ReplaceWith
// etc
};
}
...
<tr id="person-row-#Model.Id">
<td>#Model.Name</td>
<td>#Model.Age</td>
<td>#Model.Salary</td>
<td>
<!-- configure the BeginForm(..) call with the action name and a routeValue anonymous object as "new { id = Model.Id}" -->
#using (Ajax.BeginForm(...)) {
<input type="submit" value="#(Model.IsHidden ? "Show" : "Hide")" />
}
</td>
</tr>
This will simply extract the construction of loops into a separated partial view. The action method procession your Ajax-request will simply return this view, filled with the one single record you have updated, like so:
public ActionResult UpdateRecord(Int64 id)
{
var _record = Repository.Get(id);
// logic to hide and update record
return PartialView("TableRow_Partial",_record);
}
For this to work, you have to set InsertionMode to InsertionMode.ReplaceWith. All this does is that when you send an ajax request to do something with just one record, you only refresh one item - the one that triggered the ajax call, and then you simply return the new row (One row!) and replace the old one with this new one.
If you really want to stick to sending the whole collection of items back to the client every time you do something with the records, just send the entire rendered table to the client and replace the old one with the new one in a similar manner.
I have Select box and when user select item of Select box it fires Partial View Dynamically. My Plan is create ActionLinks inside each option items. I already created my partial views. I don't have plan to create separate controller actions for that partial views.I want to call partial views when user select option items like #Html.ActionLink("Link for my Partial View") method or something like this.
How i do this with Razor? Are there any another ways to do this?
edit:There are 12 partial views available to render, so Is there any way to run code without Action and I want to fire the partial view without click submit button?
you can change using jquery like that.
#Html.DropDownList("DropDownCategory", new SelectList(Model.Category, "ID", "Name"))
#Html.ActionLink("Submit name", "ActionName", "ControllerName", null, new { #id = "SubmitName" })
<script type="text/javascript">
$('#SubmitName').click(function () {
var value = $('#DropDownCategory').val();
var path = '#Url.Content("~/ControllerName/ActionName")' + "?CategoryId=" + value
$(this).attr("href", path);
});
</script>
you can try
- first need to get selected value changed of select box and then call action from your controller
On Controller (HomeController)
public PartialViewResult PartialViewTest()
{
return PartialView();
}
On View
<select>
<option value="0">One</option>
<option value="1">Two</option>
<script>
$("select").change(function () {
$.get('#Url.Action("PartialViewTest", "Home")', function (data) {
$('#detailsDiv').replaceWith(data);
});
}).trigger('change');
Hoping it can help you
I have 3 tables:
RateProfile
RateProfileID
ProfileName
Rate
RateID
RateProfileID
PanelID
Other stuff to update
Panel
PanelID
PanelName
I have models for each of these. I have an edit page using the RateProfile model. I display the information for RateProfile and also all of the Rates associated with it. This works fine and I can update it fine. However, I also added a dropdown so that I can filter Rates by PanelID. I need it to post back on change so that it can display the filtered rates.
I'm using
#Html.DropDownList("PanelID", (SelectList)ViewData["PanelDropDown"], new { onchange = "$('#RateForm').submit()" })
for my dropdownlist. Whenever it posts back to my HttpPost Edit method though, it seems to be missing all information about the Rates navigation property. It's weird because I thought it would do exactly what the input/submit button that I have in the form does (which actually passes the entire model back to my HttpPost Edit action and does what I want it to do). The panelID is properly being passed to my HttpPost Edit method and on to the next view, but when I try to query the Model.Rates navigation property is null (only when the post comes from the dropdown. Everything works fine when the post comes from my submit input).
Get Edit:
public ActionResult Edit(int id, int panelID = 1)
{
RateProfile rateprofile = db.RateProfiles.Single(r => r.RateProfileID == id);
var panels = db.Panels;
ViewData["PanelDropDown"] = new SelectList(panels, "PanelID", "PanelName", panelID);
ViewBag.PanelID = panelID;
return View(rateprofile);
}
HttpPost Edit:
[HttpPost]
public ActionResult Edit(RateProfile rateprofile, int panelID)
{
var panels = db.Panels;
ViewData["PanelDropDown"] = new SelectList(panels, "PanelID", "PanelName", panelID);
ViewBag.PanelID = panelID;
if (ModelState.IsValid)
{
db.Entry(rateprofile).State = EntityState.Modified;
foreach (Rate dimerate in rateprofile.Rates)
{
db.Entry(dimerate).State = EntityState.Modified;
}
db.SaveChanges();
return View(rateprofile);
}
return View(rateprofile);
}
View:
#model PDR.Models.RateProfile
#using (Html.BeginForm(null,null,FormMethod.Post, new {id="RateForm"}))
{
<div>
#Html.Label("Panel")
#Html.DropDownList("PanelID", (SelectList)ViewData["PanelDropDown"], new { onchange = "$('#RateForm').submit()" })
</div>
#{var rates= Model.Rates.Where(a => a.PanelID == ViewBag.PanelID).OrderBy(a => a.minCount).ToList();}
#for (int i = 0; i < rates.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(modelItem => rates[i].RateProfileID)
#Html.HiddenFor(modelItem => rates[i].RateID)
#Html.HiddenFor(modelItem => rates[i].PanelID)
#Html.EditorFor(modelItem => rates[i].minCount)
#Html.ValidationMessageFor(model => rates[i].minCount)
</td>
<td>
#Html.EditorFor(modelItem => rates[i].maxCount)
#Html.ValidationMessageFor(model => rates[i].maxCount)
</td>
<td>
#Html.EditorFor(modelItem => rates[i].Amount)
#Html.ValidationMessageFor(model => rates[i].Amount)
</td>
</tr>
}
<input type="submit" value="Save" />
}
To summarize my problem, the below query in my view only works when the post comes from the submit button and not when it comes from my dropdownlist.
#{var rates= Model.Rates.Where(a => a.PanelID == ViewBag.PanelID).OrderBy(a => a.minCount).ToList();}
How does it look in the rendered page? You may have two elements being rendered with the same name/id:
#Html.DropDownList("PanelID", (SelectList)ViewData["PanelDropDown"], new { onchange = "$('#RateForm').submit()"
and
#Html.HiddenFor(modelItem => rates[i].PanelID)
I suggest you use Firefox/Firebug to examine the actual request that it is being made. I can't imagine that the form will post differently if triggered via the submit vs. the button, but I suppose it's possible.
On a larger note, I would make a couple of comments about your design.
I would use ViewBag properties for your selections rather than key-based access to ViewData. This will be much more readable in both your controller and your view.
Consider having a separate action that populates the information filtered by the dropdown list. Render that action in your view based on the PanelID and call that action via AJAX to get the new HTML rather than doing a full form post.
Avoid applying your handlers in your markup. Rather, add an optional Scripts section to your master page and, when needed, add scripts to your page that apply your behaviors through that section. This allows you to control via the master where view-specific scripts are placed in the document and keeps your behaviors separate from your document structure, a best practice for readability.
I found the problem. It looks like because I was only looping through a subset of my navigation property (Rates filtered by panelID), the model that was returned from the view only had that subset of navigation properties available to it. After saving the changes, I just redefined my model (called the record from the database again) and it looks good now.
Ie. There were supposed to be 140 records in my navigation property, filtering by panelID == 1 narrowed it down to 28 records. For some reason the entity framework decided that it didn't feel like maintaining the relationship to the other 112 records, so when I changed the dropdown to filter by panelID == 2, the only records available all had panelID == 1 and returned null.
So in my view I am dynamically creating dropdown lists in a for loop. I use the same name value in unique id values.
#foreach(var item in Model.Items)
{
#Html.DropDownList("Items" Model.GenerateSelectList(item.id), new { id = item.id })
}
In my controller action method for the post I can get the values of the dropdowns like this:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult ClassicLineup(IList<int> items)
{
}
I cannot figure out how to get BOTH the dropdown id and associated value.
Seems like it should be simple but it has me stumped...
Thanks.
On form submit browser sends only the selected value of a dropdown, so there is no bult in mechanism to read the text of selected item, but you can create one for you.
In below code i have created a hidden input which stores the text of current selected item, and is send to the server on form post.
You can create below list in controller or in view (preferred place in controller);
var items= (from item in Model.Items
select new SelectListItem
{
Value= item.DisplayProperty
Text= item.Value
}).toList();
<form action="controller/test">
#*This will create DD*#
#Html.DropDownList("MyDropDownList", items)
#*Hidden input*#
<input type="hidden" id="ddSelectedName" name="ddSelectedName" />
<br>
<input type="submit" value="submit"/>
</form>
Include jQuery in ur code and then add this
<script type="text/javascript">
$('#MyDropDownList').change(function () {
$('#ddSelectedName').val($(this).find(':selected').text());
});
</script>
Controller
public string test(string MyDropDownList, string ddSelectedName)
{
return MyDropDownList+ "--"+ddSelectedName ;
}
I just ended up looping through the forms collection and parsing the dropdown id (form.key) and the dropdown value (form.key.value).
Apparently there is not an easy way to have MVC bind the dropdown values and id to a collection in the controller action parameter.