Repeated UI functionality in MVC - javascript

I have a few screens which have duplicated complex UI functionality. i.e. Drop down A triggers a JSON call to my Controller, which populates the values in drop down B and C, based on the value selected in drop down A.
So, on the screens, I have drop down A, and then under that, B and C.
This is duplicated UI code on 3 screens (Growing to 5 soon). But they make use of the same Controller call to do the population.
Is there a way I can create a partial view, which I can place in a form (Form is submitted via a normal Form post), and the values of the selections can then be passed back with the values of all the other form items that are submitted? The partial view would have the markup, as well as the Javascript I use to call the controller?
I'm not sure if I can then include the values within the partial view, in the Form post, and have them available to the controller.
I have started investigating EditorTemplates, but all I have is the Model for the template:
public class AccountCombinationTemplateModel
{
public int SourceEntityId { get; set; }
public int DestinationEntityId { get; set; }
public int EntityEventTypeId { get; set; }
public List<SelectListItem> Accounts { get; set; }
public List<SelectListItem> ThirdParties { get; set; }
}
and an empty AccountCombinationTemplate.cshtml file. Not sure if I need a controller etc.
My parent model (The main model I use for the view) now references the new model created above:
public AccountCombinationTemplateModel AccountModel { get; set; }
So all the properties that were in the parent model, have been moved into the AccountCombinationTemplateModel.
On my View, I now have:
#Html.EditorForModel(Model.Details.AccountModel)
That is on the view, where I want my Drop downs to appear.
I created a new empty view called AccountCombinationTemplate.cshtml in my View\Shared\EditorTemplates folder.
That view looks like this:
#model BasicFinanceUI.Models.AccountCombinationTemplateModel
<div class="row">
<div class="form-group col-xs-12 col-lg-3 divAccounts">
#Html.LabelFor(x => x.EntityEventTypeId)
#Html.DropDownListFor(x => x.EntityEventTypeId, Model.EntityEventTypes, new { #class = "EntityEventTypeId form-control", #onchange = "changeDisplay(this)" })
</div>
<div class="form-group col-xs-12 col-lg-5 divAccounts">
#Html.LabelFor(x => x.SourceEntityId)
#Html.DropDownListFor(x => x.SourceEntityId, Model.Sources, new { #class = "SourceEntityId form-control" })
</div>
<div class="form-group col-xs-12 col-lg-5 divAccounts">
#Html.LabelFor(x => x.DestinationEntityId)
#Html.DropDownListFor(x => x.DestinationEntityId, Model.Destinations, new { #class = "DestinationEntityId form-control" })
</div>
</div>
I my existing controller, I have added the new EditorModel above as a property of the main model for the view, and populated it and it's properties.
But when I run the screen, where I have the #Html.Edit..., nothing is rendered. No errors either. It;s as if it can't find the View?

Related

Using C# MVC multiple dynamic models in View

I have a View with several form that I'm using for searching and displaying the results as partial View in like SearchByNumber, SearchByVehicle, etc.
I'm trying to load view and execute search for different forms by posting link with querystring like www.example.com/Search?number=101010 from different view.
For the first form, SearchByNumber I only have one parameter, string number and i'm returning view with dynamic Model and its working like it should, but I only manage to make search for this form.
Here is my controller:
public ActionResult Index(string number)
{
return View(model: number);
}
and in the View I have:
<form id="searchbynumberform">
Search By Any Number:
<div class="input-group input-group-sm">
<input type="text" class="form-control" name="number" id="number" value="#Model">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" name="numbersearch" id="numbersearch" disabled>
Search
</button>
</span>
</div>
</form>
My Question is, if anyone can help me, How to perform search let's say on the second form where I have int type and string name parameters?
Thank You in advance...
At the moment your Model is only the search string that was entered, which seems rather incomplete. It would make a lot more sense if the Model also contained the actual search results, which after all is what the user wants to see. And then you can also add the other search properties.
The MVC approach for this is to create a (View)Model class, somewhere in your project, something like this:
public class SearchModel
{
public string Number { get; set; }
public int? Type { get; set; }
public string Name { get; set; }
public List<SearchResult> SearchResults { get; set; }
}
And then use it e.g. like this:
public ActionResult Index(string number)
{
var model = new SearchModel
{
Number = number,
SearchResults = GetByNumber(number)
};
return View(model);
}
public ActionResult IndexOther(int type, int name)
{
var model = new SearchModel
{
Type = type,
Name = name,
SearchResults = GetByTypeAndName(type, name)
};
return View(model);
}
And in your Index.cshtml:
#model SearchModel
#* You can now use Model.Number, Model.Type, Model.Name and Model.SearchResults. *#

How can I generate a PartialView for each click of a button? [duplicate]

The problem I will be describing is very similar to ones I already found (e.g. this post with nearly identical name) but I hope that I can make it into something that is not a duplicate.
I have created a new ASP.NET MVC 5 application in Visual Studio. Then, I defined two model classes:
public class SearchCriterionModel
{
public string Keyword { get; set; }
}
public class SearchResultModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
}
Then I created the SearchController as follows:
public class SearchController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult DisplaySearchResults()
{
var model = new List<SearchResultModel>
{
new SearchResultModel { Id=1, FirstName="Peter", Surname="Pan" },
new SearchResultModel { Id=2, FirstName="Jane", Surname="Doe" }
};
return PartialView("SearchResults", model);
}
}
as well as views Index.cshtml (strongly typed with SearchCriterionModel as model and template Edit) and SearchResults.cshtml as a partial view with model of type IEnumerable<SearchResultModel> (template List).
This is the Index view:
#model WebApplication1.Models.SearchCriterionModel
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>SearchCriterionModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Keyword, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Keyword, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Keyword, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" id="btnDisplaySearchResults" value="Search" onclick="location.href='#Url.Action("DisplaySearchResults", "SearchController")'" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
<div id="searchResults">
</div>
As you can see, I added a div with id="searchResults" below the standard template and edited the button. What I want is to display the partial view SearchResults.cshtml in the div on the bottom, but only after the button is clicked. I have succeeded in showing a partial view there by using #Html.Partial("SearchResults", ViewBag.MyData), but it is rendered when the parent view is loaded for the first time and I set ViewBag.MyData in the Index() method already, which is not what I want.
Summary: On clicking the button, I will obtain some List of SearchResultModel instances (via database access) and then the partial view should be rendered, using this newly obtained data as model. How can I accomplish this? I already seem fail at the first step, that is reacting to the button click with the above code. Right now, I navigate to the URL ~/Search/DisplaySearchResults, but of course there's nothing there and no code-behind method is called.
In traditional ASP.NET I'd just have added a server-side OnClick handler, set the DataSource for a grid and show the grid. But in MVC I already fail with this simple task...
Update: Changing the button to #Html.ActionLink I can finally enter the controller method. But naturally since it returns the partial view, it's displayed as the whole page content. So the question is: How do I tell the partial view to be rendered inside a specific div on the client side?
Change the button to
<button id="search">Search</button>
and add the following script
var url = '#Url.Action("DisplaySearchResults", "Search")';
$('#search').click(function() {
var keyWord = $('#Keyword').val();
$('#searchResults').load(url, { searchText: keyWord });
})
and modify the controller method to accept the search text
public ActionResult DisplaySearchResults(string searchText)
{
var model = // build list based on parameter searchText
return PartialView("SearchResults", model);
}
The jQuery .load method calls your controller method, passing the value of the search text and updates the contents of the <div> with the partial view.
Side note: The use of a <form> tag and #Html.ValidationSummary() and #Html.ValidationMessageFor() are probably not necessary here. Your never returning the Index view so ValidationSummary makes no sense and I assume you want a null search text to return all results, and in any case you do not have any validation attributes for property Keyword so there is nothing to validate.
Edit
Based on OP's comments that SearchCriterionModel will contain multiple properties with validation attributes, then the approach would be to include a submit button and handle the forms .submit() event
<input type="submit" value="Search" />
var url = '#Url.Action("DisplaySearchResults", "Search")';
$('form').submit(function() {
if (!$(this).valid()) {
return false; // prevent the ajax call if validation errors
}
var form = $(this).serialize();
$('#searchResults').load(url, form);
return false; // prevent the default submit action
})
and the controller method would be
public ActionResult DisplaySearchResults(SearchCriterionModel criteria)
{
var model = // build list based on the properties of criteria
return PartialView("SearchResults", model);
}
So here is the controller code.
public IActionResult AddURLTest()
{
return ViewComponent("AddURL");
}
You can load it using JQuery load method.
$(document).ready (function(){
$("#LoadSignIn").click(function(){
$('#UserControl').load("/Home/AddURLTest");
});
});
source code link

Cascading Dropdown list MVC5 using fluent nhibernate

I have read for 2 days every single question that looks like my problem on here and read multiple pages and tutorials and even watched videos and i just can't understand it or make it work...
What im trying to do is that i have 2 dropdown lists, one is "Departamentos" which you can think of it as a state and "Municipios" which you can think of it as a county. What i need and no matter what i just CAN'T make it work is that when i select a departamento ONLY the municipios from that departamento show up on the dropdown list. Im really a complete noob regarding programming and unfortunately i think i started with something way too big for me, so i' sorry if this is a basic really easy thing to do for you.
The departamento class is :
public virtual int id_departamento { get; set; }
public virtual string descripcion { get; set; }
//i specify Relationship for fluent Nhibernate to municipios since it is a 1-n
public virtual IList<Municipios> Municipios { get; set; }
The municipio class is :
public virtual int id_municipio { get; set; }
public virtual string municipio { get; set; }
public virtual int id_departamento { get; set; }//THis is the FK from Departamento
And heres my main class Sitios where i connect everything to:
This is for the Nhibernate relationships
public virtual Departamentos Departamento { get; set; }
public virtual Municipios Municipios { get; set; }
This is for the lists on that same Sitios class:
public virtual List<Departamentos> Departamentos { get; set; }
public virtual List<Municipios> municipiosLista { get; set; }
Now going to MVC this is the controller i have for the Get create where i populate the lists of Departamento and Municipio to be shown:
using (ISession session = NhibernateHelper.OpenSession())
{
var deptos = session.Query<Departamentos>().ToList();
var munis = session.Query<Municipios>().ToList();
var instanciadelacopia=new Sitios
{
Departamentos = deptos,
municipiosLista = munis
};
return View(instanciadelacopia);
}
And the create view for that specific dropdown part:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Sitios</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<label class="control-label col-md-2"> Departamento </label>
<div class="col-md-10">
#Html.DropDownListFor(model => model.id_departamento, new SelectList(Model.Departamentos, "id_departamento", "descripcion"), "--Select--", new {#id = "depto"})
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2"> Municipio </label>
<div class="col-md-10">
#Html.DropDownListFor(model => model.id_municipio, new SelectList(Model.municipiosLista, "id_municipio", "municipio"), "--Select--", new { #id = "muni" })
</div>
</div>
Everything works fine since it brings me all the values for me to select from the db, where i'm stuck and can't advance is that i need a cascading dropdown for the municipios list that when i select certain departamento ONLY the municipios of THAT selected departamento show up on the list.
So for example i select departamento "atlantida" which it's ID is 1 then only the municipios which have that foreign key from Departamentos = 1 are shown which im guessing Jquery is required.
Would be really good if someone can help me with this, i feel so stressed since i just cant figure out what i need to do. Thanks
All examples i've seen are about using JSON like this one:
http://www.c-sharpcorner.com/uploadfile/4d9083/creating-simple-cascading-dropdownlist-in-mvc-4-using-razor/
but since i already have all the data available on the dropdowns i think i don't need that and instead just a plain jquery function which i cant create.
Solved it with this:
<script type="text/javascript">//Script for Cascading Dropdown
$(function () {
$('#id_departamento').change(function() {
var departmentId = $(this).val() || 0;
$.ajax({
url: '/Sitios/Municipios/',
type: 'POST',
data: { id_departamento: departmentId }, // parametro
dataType: 'json',
success: function (data) {
var options = $('#id_municipio');
$('option', options).remove(); //
options.append($('<option />').val('').text('---'));
$.each(data, function () {
options.append($('<option />').val(this.id).text(this.name));
});
}
});
});
});

HTML form submit a single item from a collection

I have a partial view with a view model that has a collection of sellers. I loop over all of the sellers to render the list. Here is the view model:
public class SellersPartialViewModel
{
public IList<OrderViewModel> Sellers { get; set; }
}
In the partial view I'm using Html.BeginCollectionItem("Sellers") when I loop through the collection and here is my code for the partial (FYI I've stripped away a lot of useless code that doesn't need to be seen):
<div id="sellers-list">
#{
var i = 0;
while (i < Model.Sellers.Count) {
var seller = Model.Sellers[i];
using (Ajax.BeginForm(MVC.Video.PurchaseShares(), purchaseSharesAjaxOptions, new { #class = "seller-form", id = "seller-form-" + i })) {
#using(Html.BeginCollectionItem("Sellers")) {
#Html.TextBoxFor(m => seller.Qty, new { #class = "buyer-qty" })
#Html.ValidationMessageFor(m => seller.Qty)
<input class="buyer-qty-submit" name="Qty" type="hidden" value="" />
<button type="submit">Buy</button>
}
}
}
i++;
}
}
</div>
This works fine for rendering the partial and getting the client-side validation working
however I want each seller to have the inputs named qty and orderId for a controller action called PurchaseShares(int orderId, int qty).
The only problem is the form is being submitted with the odd GUID like Sellers[5b5fd3f2-12e0-4e72-b289-50a69aa06158].seller.Qty which I understand is correct for submitting collections but I don't need to do that.
Right now I have some Javascript that is updating the class="buyer-qty" with whatever they select and it works fine but there has got to be a better way of doing this, no?
Thanks
Why are you using the Html.BeginCollectionItem helper if you don't want to submit collections?
You could have a partial representing your Order collection item (_Order.cshtml):
#model OrderViewModel
#Html.TextBoxFor(m => m.Qty, new { #class = "buyer-qty" })
#Html.ValidationMessageFor(m => m.Qty)
And in your main view simply loop through your collection property and render the partial for each element:
#model SellersPartialViewModel
<div id="sellers-list">
#foreach (var seller in Model.Sellers)
{
using (Ajax.BeginForm(MVC.Video.PurchaseShares(), purchaseSharesAjaxOptions, new { #class = "seller-form" }))
{
#Html.Partial("_Order", seller)
<button type="submit">Buy</button>
}
}
</div>
Now your controller action you are submitting to could directly work with the corresponding view model:
[HttpPost]
public ActionResult PurchaseShares(OrderViewModel order)
{
...
}
because:
[HttpPost]
public ActionResult PurchaseShares(int orderId, int qty)
{
...
}
kinda looks uglier to me but it would also work if you prefer it.
Also please notice that I have deliberately removed the Qty hidden field shown in your code as it would conflict with the input element with the same name. Also don't forget to include an input field for the orderId argument that your controller action is expecting or when you submit it could bomb. Also you could send it as part of the routeValues argument of the Ajax.BeginForm helper if you don't want to include it as an input field.

MVC3 EditorFor() Change Value in JavaScript Model Does Not Recognize Changes

EDIT:
After looking at the way I'm doing this, I decided to change my whole view model, so this question isn't valid any more. I wouldn't mind knowing what I was doing wrong, but I really don't need the answer for my project.
Thanks!
I use Javascript to change the value of a text box when the dropdownlist's value is changed. When the page is posted, the value that was changed in Javascript is always zero.
The Javascript seems to work fine, but the value doesn't make it back to the server.
Here is my view model:
...
[Required(ErrorMessage = "\"{0}\" is required")]
[Display(Name = "Mileage Out")]
public decimal MileageOut { get; set; }
[Required(ErrorMessage = "Vehicle is required.")]
public Guid VehicleID { get; set; }
[Display(Name = "Vehicle")]
public List<SelectListItem> Vehicles { get; set; }
....
Here is my view:
....
<div class="editor-label">
#Html.LabelFor(model => model.Vehicles, "Vehicle")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.VehicleID, new SelectList(Model.Vehicles, "Value", "Text"), "Vehicle is required . . .", new {#onchange="GetLastMileage(this.value);"})
#Html.ValidationMessageFor(model => model.Vehicles)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MileageOut)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MileageOut, null, "txtMileageOut")
#Html.ValidationMessageFor(model => model.MileageOut)
</div>
...
<script type="text/javascript">
function GetLastMileage(vehicleID) {
switch (String(vehicleID))
{
#{
foreach (KeyValuePair<string, decimal> item in Model.VehiclesLastMileage)
{
#Html.Raw("case '" + item.Key + "': document.getElementById('txtMileageOut').value = '" + ((decimal)item.Value).ToString() + "';break;")
}
}
default: return 0;
}
}
</script>
I have found that if I change this:
#Html.EditorFor(model => model.MileageOut, null, "txtMileageOut")
to this:
#Html.EditorFor(model => model.MileageOut)
the the date gets back to the server fine, but the JavaScript doesn't work (no id on the textbox). Obviously I'm using the wrong method for changing a value on the client, but I don't know the right one.
Thanks!
Change the javascript.
document.getElementById('txtMileageOut').value
to
document.getElementById('MileageOut').value
Also, you should use jQuery and move the javascript out to its own external file. It would also simplify the JS code a lot. You could use classed as selectors instead of ID and do a lot more in less code.

Categories