AngularJS select doesn't honour selected value - asp.net-mvc

I have an ASP.NET MVC controller with a VideModel I serialize into JSON and send to the client, along with a view and marry those two together using AngularJS. The view model has a lookup (array) that is bound to a Select element. It all works fine and I can select a value, post the form back to the MVC controller and get the selected value there.
When I send back a view model with a value already selected, the value is not selected in the Select element. I can see that the value is correctly set on the client side (using Batarang) but the Select still doesn't show that as selected. Here's a link to the sample showing this. Any ideas?
Here's the code snippets to give you some ideas (angular controller doesn't have any code, just a mean to add new item with default values to the destination array).
MVC Controller:
public Sources {
First,
Second,
Third
}
public ActionResult MyGetAction()
{
var vm = new MyViewModel {
Sources = SelectListExtension.ToSelectListItem<Sources>() //Would return IEnumerable<SelectListItem> (with proper Value and Text for all enum entries)
Destination = new List<DestinationViewModel>(); //Has int? SourceId
};
vm.ClientData = SerializeToJson(vm);
return View(vm);
}
[HttpPost]
public ActionResult MyPostAction(MyViewModel model)
{
var selectedSource = model.Destination[0].SourceId; //works fine
model.Sources = SelectListExtension.ToSelectListItem<Sources>(); //populate the lookup again
//model still has 1 destination with the SourceId correctly set
return View(model);
}
My View bound to the JSON object:
#using (Html.BeginForm())
{
<div ng-repeat="destination in Destinations">
<select ng-init="destination.Source = { Value: destination.SourceId }"
ng-model="destination.Source"
ng-options="s.Text for s in Source track by s.Value"
ng-change="destination.SourceId = destination.Source.Value"
name="Destinations[{{$index}}].SourceId"
id="Destinations_{{$index}}__SourceId">
<option value=""></option>
</select>
</div>
<input type="submit">
}

Related

How to make binding work on additional parameter with a Get request

My model has property
public int SelectedContactId { get; set; }
The view has a dropdown, and on changing selection it refreshes the view:
<select asp-for="SelectedContactId"
asp-items="Model.Contacts"
class="form-control ddlContractorContacts bg-light"
onchange="location.reload(true)">
</select>
The HttpGet Edit() action method is called:
// GET: Contractors/Edit/5
public ActionResult Edit(int? contractorId, int SelectedContactId)
But SelectedContactId is not bound, and I cannot figure out how it can be done.
P.S. If I use [FromBody] attribute before int SelectedContactId, I get an error on the first loading the page: HTTP ERROR 415.
First of all, from reading your comment
// GET: Contractors/Edit/5
public ActionResult Edit(int? contractorId, int SelectedContactId)
it looks like the contractor id is 5 and it will be always there with the URL since you're editing a contractor. So you should change it to
// GET: Contractors/Edit/5
public ActionResult Edit(int Id, int SelectedContactId)
to take advantage of MVC default route mapping - the integer after / should automatically bind to Id.
And then I think your SelectedContactId should be marked as optional, because when you first land on the edit page, there shouldn't be any selected contact. And you want to automatically bind the selected contact as additional parameter on the URL as soon as the user picks a contact. That's what you want to accomplish right?
If that's the case, you should change SelectedContactId to optional:
// GET: Contractors/Edit/5
public ActionResult Edit(int Id, int? SelectedContactId)
Now let's get to the point. There are many ways to manipulate query parameters. Some of them are:
Submit a GET form
Use JavaScript to manually append inputs to the URL
Here I am showing you the #1 - Submit a GET form.
Submit a GET form
If you want the server to get the input selections on the view, you need submit the <form /> that contains all the inputs.
Whenever the user makes the selection and hits the submit button, the form will send the input data back to the server. And your MVC will bind the request to your action's parameters.
Using JavaScript location.reload(true) will only refresh the current page. It doesn't send the inputs back to the server.
View Model
public class EditContractorViewModel
{
[Display(Name = "Contact")]
public int? SelectedContactId { get; set; }
public IEnumerable<SelectListItem> Contacts { get; set; }
}
Controller
public class ContractorController : Controller
{
public IActionResult Edit(int id, int? selectedContactId)
{
// In theory, you need to take the id and look that up in your persistent storage
// and then you build up the view model and pass it back to the View
var vm = new EditContractorViewModel
{
SelectedContactId = selectedContactId,
Contacts = new List<SelectListItem>
{
new SelectListItem("Contact 1", "1"),
new SelectListItem("Contact 2", "2"),
new SelectListItem("Contact 3", "3"),
new SelectListItem("Contact 4", "4"),
new SelectListItem("Contact 5", "5")
}
};
return View(vm);
}
}
View
<!-- The View -->
#model EditContractorViewModel
<form asp-area="" asp-controller="ContractorController" asp-action="Edit"
method="get">
<div class="form-group">
<label asp-for="SelectedContactId"></label>
<select asp-for="SelectedContactId"
asp-items="Model.Contacts"
class="form-control ddlContractorContacts bg-light">
<option value="">- select -</option>
</select>
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
Result
First landing on the page:
Select a contact and hit submit:
If you want the form to be automatically submitted on the dropdown change, you can get rid of the submit button on the view and write some JavaScripts to submit the form manually:
$(function() {
$('select.ddlContractorContacts').change(function() {
let $form = $(this).closest('form');
$form.submit();
return false;
});
});
Since you use location.reload(true),it will always redirect to /Contractors/Edit/5 and your SelectedContactId on Edit action will always be 0 since you do not pass the selected Id in query string or somewhere else.
A simple way is that you could pass the Id using query string like /Contractors/Edit/5?SelectedContactId=1.
Refer to below code of the view:
<select asp-for="SelectedContactId"
asp-items="Model.Contacts"
id="mySelectedContactId"
class="form-control ddlContractorContacts bg-light"
onchange="reload()"></select>
#section Scripts{
<script>
function reload() {
var selectedContactId = document.getElementById("mySelectedContactId").value;
var url = window.location.href.split(/[?#]/)[0];
url += '?SelectedContactId=' + selectedContactId
window.location.href = url;
}
</script>
}

How to configure an MVC dropdown depending on which view calls it

I have two views, BatchReceipt and Receipt which utilise the same model. Until now they have used the same display template of ReceiptType. But I want to have one exclude certain items and the other to have the full list (so essentially a second .cshtml display template called ReceiptTypeFull). How do I configure each of these views in Visual Studio to utilise the different Display Templates?
Some additions to show the code being used:
I have file ReceiptType.cshtml being used as a DisplayTemplate which contains the following to setup the receipt dropdown
#using Clinton.Web.Helpers.EnumHelpers
#{
var item = EnumsHelper.GetNameFromEnumValue(Model);
}
I want to use a different DisplayTemplate, call it ReceiptTypeFull.cshtml
#using Clinton.Web.Helpers.EnumHelpersFull
#{
var item = EnumsHelper.GetNameFromEnumValue(Model);
}
#item
The difference is in calling the enumhelper or the enumhelperfull to vary the query populating the dropdown. My problem is that I cannot see how to redirect the view to use the different enumhelper/displaytemplate/
Thanks
I think I understand what you are getting at. You want to control which template is used for an Enum in the view.
I will explain using editor templates but it works the same way if you use display templates. You should be able to follow and apply for your scenario.
The idea is to use this overload of the editor html helper.
public static MvcHtmlString Editor(this HtmlHelper html, string expression, string templateName);
It is called like this
#Html.Editor("{property name}", "{template name}").
Below is an example to show it being used.
Suppose we have this enum
public enum MyItems
{
Item1 = 1,
Item2 = 2,
Item3 = 3
}
This helper
public static class MyEnumHelper
{
public static List<MyItems> GetAllItems()
{
return new List<MyItems>()
{
MyItems.Item1,
MyItems.Item2,
MyItems.Item3
};
}
public static List<MyItems> GetSomeItems()
{
return new List<MyItems>()
{
MyItems.Item1,
MyItems.Item2
};
}
}
This controller
public class HomeController : Controller
{
public ActionResult AllItems()
{
return View();
}
public ActionResult SomeItems()
{
return View();
}
}
We have these 2 editor templates, which are put in views/shared/editortemplates
First one called MyItems.cshtml which is the all one
#model MyItems?
#{
var values = MyEnumHelper.GetAllItems().Cast<object>()
.Select(v => new SelectListItem
{
Selected = v.Equals(Model),
Text = v.ToString(),
Value = v.ToString()
});
}
#Html.DropDownList("", values)
Second one called MyItems2.cshtml which is the some one
#model MyItems?
#{
var values = MyEnumHelper.GetSomeItems().Cast<object>()
.Select(v => new SelectListItem
{
Selected = v.Equals(Model),
Text = v.ToString(),
Value = v.ToString()
});
}
#Html.DropDownList("", values)
Then in the AllItems.cshtml to get the MyItems.cshtml template called we need
#model MyItemsViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.MyItem)
<submit typeof="submit" value="submit"/>
}
And in the SomeItems.cshtml to get some of the items by calling MyItems2.cshtml we use
#model MyItemsViewModel
#using (Html.BeginForm())
{
#Html.Editor("MyItem", "MyItems2") #* this bit answers your question *#
<submit typeof="submit" value="submit" />
}

ASP.NET MVC passing value from selectlist

I have a .NET Core 2 MVC project, and I am very new to this. I have a Details View that contains data for a selected user. I am using a ViewModel to display that selected user's data and it is all working fine. What I am trying to ultimately do is allow for the cloning of one user's Identity Roles to the current selected user. So I have in my View a panel that contains a SelectList to where you can select some other user (I'm calling them the source user) to get their currently assigned Identity Roles. I then want to have a button that says "Clone" and on click remove all Roles for the current user and then add them to the Roles that the selected source user is assigned to.
Here is what I am using for my drop-down list:
View:
<select asp-for="EmployeeList" class="form-control"
asp-items="#Model.EmployeeList"
onchange="this.form.submit()">
<option disabled selected>-</option>
</select>
Controller:
var employeeList = _employeeRepository.GetEmployeeList().Select(e => new
{
Id = e.EmployeeId,
Value = e.PreferredFirstName != null ? e.LastName + ", " + e.PreferredFirstName : e.LastName + ", " + e.FirstName
});
ViewModel:
public SelectList EmployeeList { get; set; }
In my View I have a form for the post:
#using (Html.BeginForm("Details", "ApplicationUser", FormMethod.Post))
So I need to pass the selected id (string) of my source user drop-down list to the [HttpPost] Details action in my controller. I also want to maintain the selected option in my drop-down list in the View. I also want to maintain all of the data that is being displayed in my ViewModel.
Your help is greatly appreciated.
As said before, your view is really not making sense. However we can fix that.
Viewmodel:
public List<SelectListItem> EmployeeList { get; set; }
View:
<select id="employee-select" asp-for="EmployeeId" class="form-control" onchange="postEmployee()" asp-items="#Model.EmployeeList">
<option disabled selected>-</option>
</select>
<script>
function postEmployee() {
var id = $('#employee-select').val();
if (id !== '' && id != null) {
var url = '../CONTROLLERNAME/UpdateUser/' + id;
var settings = {
"url": url,
"method": 'POST'
};
$.ajax(settings);
}
}
</script>
Controller:
[HttpPost("UpdateUser/{id}")]
public IActionResult UpdateUser([FromRoute] int id)
{
// Do things with id
}

Return an mvc action with a viewmodel type different to the passed one

I have an ItemsController with an Index action returning and rendering items on the client like
return View(itemViewModels);
Each itemViewModel has some bootstrap tabs. In each tab a partialView is rendered.
When the user edits a partialView and send the data to the Tab1Controller how can I return the View for the whole itemViewModel showing validation errors for the one partial view inside that tab1?
I have made the View work requesting/responsing with the sent itemViewModel but then Only the single item`s html is returned not the full items html.
AND when I return the Index View to the ItemsViewModels then I can NOT return my passed itemViewModels to show the validation errors.
[HttpGet]
public ActionResult Index(ItemViewModel viewModel)
{
return View("ItemsViewModels", viewModel);
}
This code does not work because the View does not match to the viewmodel type.
But I need to show the Html of the ItemsViewModels with the invalid (data-val attributes) html of the single edited itemViewModel.
I can NOT post and return the ItemsViewModels because it has many other properties which would make the modelstate invalid...
Do you have any idea?
UPDATE
I am NOT allowed to use ajax else the problem would be done quickly... this is for a common website and the customer wants a postback no ajax/SPA behavior.
At the moment I get the whole items from the service again and render it but then every ItemViewModel`s html has an invalid e.g. textbox. But I want that only a certain ItemViewModel is invalid.
[HttpPost]
public virtual async Task<ActionResult> SaveLanguage(ItemViewModel itemViewModel)
{
var viewModels = GetItemsViewModelsFromSErvice();
viewModels.Items.ElementAt(0).Settings = itemViewModel;
return View(MVC.Test.Items.Views.Index,viewModels );
}
If you are forced to do a full postback, but each partial view contains just a form with the elements for that 1 item then you'll have to reconstruct the other items in your controller before returning.
Your controller method would be something like
public ActionResult Index()
{
var vm = GetAllItems(); //method to get all your items for first load
return View(vm);
}
[HttpPost]
public ActionResult Index(ItemViewModel viewModel)
{
//get the full list of items, and then replace just the altered one
var vm = GetAllItems(); // assume returns a list
var index = vm.FindIndex(x => x.ID == viewModel.ID);
vm[index] = viewModel;
//might have to rename items in the ModelState.Errors dictionary
//so they are associated with the correct item index.
//first get the list of errors. As viewModel is not a list for this method
//they will have keys like "PropertyName".
//For a listItem need renaming to something like "[#].PropertyName" (# = index)
var errs = from ms in ModelState
where ms.Value.Errors.Any()
let fieldKey = ms.Key
let errors = ms.Value.Errors
from error in errors
select new {fieldKey, error.ErrorMessage};
//clear the ModelState, and then re-add any model-errors with the renamed key
ModelState.Clear();
foreach(var item in errs)
{
ModelState.AddModelError(
String.Format("[{0}].{1}", index, item.fieldKey), item.ErrorMessage);
}
return View("ItemsViewModels", vm);
}
In addition you might need to rename your form elements so that the model binder treats them as list items after postback. I'm not 100% sure this is necessary though.
If you can use ajax this becomes neater...
It looks like your index Model is a List<ItemViewModel>
Then your Main view (Index.cshtml) would be something like..
#model List<ItemViewModel>
...
#for(int i = 0; i < Model.Count; i++)
{
<div id="#String.Format("partialContainer{0}", Model[i].ID)">
#Html.Partial("Partial1", Model[i])
</div>
}
(notice that the ID of the container div is something that we can reference as the ajax update target)
And then have your partial Views use the relevant partial Models
Eg Partial1.cshtml:
#model ItemViewModel
... etc
#using (Ajax.BeginForm("RefreshPartial", "Home", null, new AjaxOptions() {
UpdateTargetId = String.Format("partialContainer{0}", Model.ID), HttpMethod = "Post" }, null))
{
#Html.TextBoxFor(m => m.Property1);
#* form controls... *#
<input type="submit" value="Submit" />
}

get dropdownlist selected value in controller MVC3 Razor

Hi in my MVC3 Project with RAZOR, i Have one doubt.
i have a page named CatlogPage.cshtml. in that page i have a Dropdownlist control.
#(Html.Telerik().DropDownListFor(m => m.CatalogName)
.BindTo(Model.CatalogName).HtmlAttributes(new { style = "width:235px" }))
<input type="submit" value="Next" />
I have a controller named Hierarchy.cs:
in that controller,
public ActionResult Hierarchy()
{
// Need to get the selected value in DropDownList
return View("Hierarchy");
}
How to get the value(CatalogName) from dropDownList to the controller?
This is my model code.
public List<SelectListItem> GetCatalogNameModel()
{
try{
var cat = from s in _entities.Catalogs.ToList()
select new SelectListItem()
{
Text = s.CatalogName,
Value = s.CatalogName
};
return cat.ToList();}
catch (Exception ex)
{
CreateLogFiles.ErrorLog(HttpContext.Current.Server.MapPath("~/Logs/ErrorLog"), ex, "CatalogService", "GetCatlogName");
return null;
}
}
So assuming that the first code snippet is from a strongly typed view (object DatabaseModel.CatalogModel) and that you are submitting the form to the Hierachy method, then passing in a CatalogModel and accessing the CatalogName should be what your after?
i.e.
public ActionResult Hierarchy(DatabaseModel.CatalogModel inputModel)
{
inputModel.CatalogName; //This will be the value from the drop down list
return View("Hierarchy");
}
For DropDownList, I use an Int prop to receive the selected Id. So My answer is:
Add this property to your ViewModel:
public Int32 SelectedCatalogId {get;set;}
And bind it to the DropDownList:
#Html.DropDownListFor(m => m.SelectedCatalogId, Model.GetCatalogNameModel())

Resources