In MVC3, I've been able to rely on Html.DisplayForModel() to generate display's for my data. Using this method, and various templates, I have a single View for displaying several of my Models. What I'm wondering though, is there a way I can get this to work on Lists for my models?
For example, I have a model called Networks. My view to list out multiple networks looks like this:
#model PagedList<Network>
<div id="networkList">
#Html.Grid(Model).Columns(column => {
column.For(x => Html.ActionLink(x.Id.ToString(), "NetworkDetails", new { id = x.Id })).Named("Network ID");
column.For(x => x.Name);
column.For(x => x.Enabled);
}).Attributes(Style => "text-align: center")
#Html.AjaxPager(Model, new PagerOptions() { PageIndexParameterName="page", ShowDisabledPagerItems = false, AlwaysShowFirstLastPageNumber=true },
new AjaxOptions() { UpdateTargetId = "networkList" })
</div>
I'm wondering if it is possible to use a single template when generating lists for my models. I could rely on attributes to know which properties I would like to generate in my list, ie: [ListItem].
The head scratcher for me is, how can I pass a dynamic model to an extension method? If it's of any help, the Html.Grid extension is from MVCContrib. Has anyone else done something similar? It would be great to rely on a template as it would really chop down on the amount of code.
You can achieve it for EditorFor() using the following (it might be similar with your Grid extension method assuming it can take the template name parameter):
// in the main view
#Html.EditorFor(o => o.InvoiceId, "TemplateName", new { Property = "InvoiceId" })
#Html.EditorFor(o => o.Title, "TemplateName", new { Property = "Title" })
// in the template view
#model object
#{ var property = (string)this.ViewData["Property"]; }
Alternatively you can just pass in the name of the template and use this code in the template
var prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
if (prefix.EndsWith("InvoiceId")) { ... }
Related
I am using MVC 5 and the latest version of Telerik (formerly Kendo) UI for ASP.NET MVC. I am working with hierarchical data. I am attempting to create the TreeView in the _Layout view and populate it with urls or action links.
My current code:
In the _Layout View:
#Html.Partial("_ProductTree")
"_ProductTree" Partial View:
#(Html.Kendo().TreeView().Name("ProductTree")
.DataSource(d => d
.Model(m => m
.Id("ProductId")
.HasChildren("Categories"))
.Read(r => r.Action("_ProductTree", "Home")))
.DataTextField("ProductName"))
Action Method:
[ActionName("_ProductTree")]
public JsonResult GetProductTree()
{
var products = _productBusinessService.GetProducts();
var result = products.Select(p => new
{
p.ProductID,
p.ProductName,
Categories= c.Categories.Any()
}).OrderBy(t => t.ProductName);
return Json(result, JsonRequestBehavior.AllowGet);
}
I am having a number of issues:
When I expand a parent node that has children, the TreeView is hitting the action method and appending the entire tree to the child, instead of just displaying the children.
I need to be able to nest the TreeView two-deep, for example Product > Category > Type.
I am trying to figure out how to aggregate or project the data using LINQ to do a two-deep higherarchy.
I tried turning off LoadOnDemand but that made the TreeView call the action method once for each record in the Product list.
I have tried inserting the TreeView Razor code directly into the _Layout view (not using a partial view). I realize that I may need to move the action method into a base controller class and inherit it in every controller to stop it from appending the list to the parent node. If I cant get this working soon, I may have to either use Kendo UI Professional or an open source alternative.
Some of you may be tempted to say that this question has been answered elsewhere. None of the posts I have found address the issues of populating and displaying nested (more than one deep) hierarchical data using the Telerik TreeView.
There is this post
Nested DataSources and TreeView with kendo ui
But it is for the JavaScript version of the TreeView (not the UI for MVC) version and it has not been answered.
Thank you in advance for your help!
The solution in code:
The _Layout view:
#Html.Partial("_ProductTree")
or
#RenderSection("productTree", false)
then in the content view
#section productTree
{
#Html.Partial("_ProductTree")
}
the _ProductTree partial view
#(Html.Kendo().TreeView().Name("ProductTree")
.DataSource(d => d
.Model(m => m
.Id("Id")
.HasChildren("HasChildren")
.Children("Children"))
.Read(r => r.Action("_ProductTree", "Home")))
.DataTextField("Name"))
I moved the action method to a BaseController abstract class that can be inherited by any controller that needs to display the ProductTree TreeView. The data was pulled from a ProductService and a CategoryService and aggregated using LINQ projection into anonymous objects:
[ActionName("_ProductTree")]
public JsonResult GetProductData()
{
var products = _productBusinessService.GetProducts();
foreach (var product in product)
{
foreach (var category in product.Categories)
{
category.ProductTypes =
_productService.GetProductTypes(category.CategoryId);
}
}
var productTreeData = products.Select(p => new
{
Id = p.ProductId,
Name = p.ProductName,
HasChildren = p.Categories.Any(),
Children = p.Categories.Select(c => new
{
Id = c.CategoryId,
Name = c.CategoryName,
HasChildren = c.ProductTypes.Any(),
Children = c.ProductTypes.Select(t => new
{
Id = t.ProductTypeId,
Name = t.ProductTypeName,
HasChildren = false
}).OrderBy(t => t.Name).ToList()
}).OrderBy(c => c.Name).ToList()
}).OrderBy(p => p.Name).ToList();
return Json(productTreeData, JsonRequestBehavior.AllowGet);
}
The result is a 3-deep, fully populated, sorted Telerik UI for ASP.NET Treeview containing the names and IDs of Product >> Category >> ProductType. Turning LoadOnDemand on or off did not seem to make a difference in this case. It should make a difference when using Bind in a TreeView.
I hope this helps!
I found posts for populating from database, classes, xml, etc but I cannot seem to find one for populating a dropdownlist straight from the web page.
<div class="editor-field">
#Html.DropDownListFor(x => x.Subject, Model.SubjectList)
#Html.ValidationMessageFor(m => m.Subject)
</div>
I just want to add several items to my dropdownlist without using Model.
Is that possible?
I think I understand what you are asking here. If you just want to hardcode values in the view you can do something like this:
#Html.DropDownList("", new List<SelectListItem> {
new SelectListItem { Text = "Option 1", Value="1"},
new SelectListItem{Text="Option 2", Value="1"}})
You could also create the select list in the controller and store it in the ViewBag.
I have a case where I have a page displaying an order and tabs that display the order details. The order details are quite complex and since they are the same layout, I want to use a partial view or editor template that will generate the form.
The problem is the result is multiple duplicate form input id's are generated (one for each order detail. For example, I have:
foreach (var orderDetail in Model.OrderDetils)
{
#Html.EditorFor(model => orderDetail, "WorkOrder", orderDetail)
}
I've read much about this and see solutions where it is recommended to use an editortemplate, but that solution only works when you have the same form to render, but passing it different model properties so the control id's prefixes will differ...ie. like this solution.
In my case, this won't work as the model property I am passing is always the same.
So how else can I create unique Id's in the partial or editor template that will also bind.
I know instead of:
#Html.EditorFor(model => model.WOHdr.Attribute1)
I could do:
#Html.TextBoxFor(model => model.WOHdr.Attribute1, new { id = Model.Id + "_Attribute1" })
But then it won't bind when it passes to the controller.
Thoughts?
Try this
#Html.TextBoxFor(model => model.WOHdr.Attribute1, new { #id = #Model.Id + "_Attribute1" })
Use "#"+dynamic value. Now You will get unique Id's
In EditorFor you can use like this
#Html.EditorFor(model => model.WOHdr.Attribute1, null, "id=" + #Model.Id + "" )
the id will generate like this
id="id_1", id="id_2" and so on..
<input id="Checkbox1_#(test.TestId)" type="checkbox" />
i hope upper code will help you
I get validation message always when I open this page (even first time), even if I choose value in one of dropdown lists, message doesn't go away. If I choose values in both I can submit form but messages still doesn't go away.
Snippet is Linq to sql class and LanguageID and SnippetTypeID are ints, I assume this happens because I pass empty model to View so LanguageID and SnippetTypeID are null and AFAIK Linq to Sql classes have required on non-nullable ints.
How can I fix this so validation messages doesn't appear before user tries to submit form, and if one of dropdown lists get selected to remove validation message.
View
#model Data.Snippet
#using (Html.BeginForm("Save", "Submit", FormMethod.Post))
{
<h1 class="subtitle">Submit new snippet</h1>
<h4>Title</h4>
#Html.TextBoxFor(snippet => snippet.Title, new { #class = "form-field" })
<h4>Language</h4>
#Html.DropDownListFor(snippet => snippet.LanguageID, new SelectList(#ViewBag.Input.Languages, "ID", "Name", #Model.LanguageID), "Choose Language", new { #class = "form-select" })
<p>#Html.ValidationMessageFor(snippet => snippet.LanguageID , "You must choose language", new { #class= "validation-message"})</p>
<h4>Snipet type</h4>
#Html.DropDownListFor(snippet => snippet.SnippetTypeID, new SelectList(#ViewBag.Input.SnippetTypes, "ID", "Name", #Model.SnippetType), "Choose snippet type", new { #class = "form-select" })
<p>#Html.ValidationMessageFor(snippet => snippet.SnippetTypeID,"You must choose snippet type", new { #class= "validation-message"})</p>
<h4>Text</h4>
#Html.TextAreaFor(snippet => snippet.Text, new { cols = "20", rows = "10", #class = "form-field" })
<input type="submit" value="Submit Snippet" />
}
Controller
//Controllers are not finished Save() should have
//code to actually insert to db after I fix validation
// GET: /Submit/
//
public ActionResult Index()
{
Snippet model = new Snippet();
SubmitModel input = new SubmitModel();
ViewBag.Input = input;
return View(model);
}
public ActionResult Save(Snippet snippet)
{
return View();
}
Model
Model is Linq to Sql class.
Snippet
ID (int, identifier)
Title (string)
SnippetType (int, FK on table SnippetTypes)
LanguageID (int, FK on table Languages)
Text (string)
Alright,
So I think the reason it is failing is because of the custom CSS that you are adding. ValidationMessageFor will put a hidden class when the validation is successful.
if you want to add custom colors or something like that with you CSS i would consider applying the style to the wrapping p tag or adding a wrapping div/span and adding it to that.
You could probably define your messages on the view using only #Html.ValidationMessageFor(snippet => snippet.SnippetTypeID, "ErrorMessage"); However a more proper way is to take your Model and create Data Annotations for it.
Take a look at this article http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validation-with-the-data-annotation-validators-cs for more information about how to do model validation with Data Annotations.
Also I would consider passing in custom classes instead of your linq to sql class so that you can do custom validation based on the view. These custom classes are often refereed to as ViewModels.
I have an MVC3 application with Razor and I created a View that inside renders a Partial View. This is how the main View looks like:
#{Html.RenderPartial("_SearchFilters", Model.SearchFilters);}
#* Other HTML elements *#
Inside the _SearchFilters Partial View I have the following DropDownLists inside a Form element:
Choose Year
#Html.DropDownListFor(m => m.Year, new SelectList(Model.YearsList, "Value", "Text"), DateTime.Now.Year)
Choose Month
#Html.DropDownListFor(m => m.Month, new SelectList(Model.MonthsList, "Value", "Text"), Model.Month.ToString(), new { #disabled = "disabled" })
<input type="submit" value="Display" />
I would like that upon Submit the two DropDownLists keep their status, namely the value selected by the user, when the View is reloaded with the filtered data.
Is there any way to do it without using AJAX?
UPDATE
The ViewModel is as follows:
public class TableSearchFiltersViewModel
{
public bool YTM { get; set; }
public int? Month { get; set; }
public int? Year { get; set; }
public IEnumerable<SelectListItem> YearsList
{
get
{
return Enumerable.Range(2011, (DateTime.Now.Year - 2011 + 4)).Select(m => new SelectListItem
{
Value = m.ToString(),
Text = m.ToString(),
}).OrderBy(m => m.Value);
}
}
public IEnumerable<SelectListItem> MonthsList
{
get
{
return Enumerable.Empty<SelectListItem>();
}
}
}
Thanks
Francesco
When you submit the form to the corresponding controller action, this action should take as input parameter some view model. This view model's properties will be bound from the input fields contained in the form including the selected value of the two dropdowns. Then the controller action could return the same view which will preserve the selected values of the dropdown boxes.
I would recommend you to use Editor Templates though instead of rendering partials as this will ensure proper naming of the dropdowns and eventually preserve selected values:
#Html.EditorFor(x => x.SearchFilters)
I don't have IDE at this time so couldn't test but this might work:
Choose Month
EDIT:
#Html.DropDownListFor(m => m.Month,
Model.MonthsList.Select(
t => new SelectListItem {
Text = t.Name,
Value = t.Value,
Selected = t.Value == Model.Month,
},
Model.Month.ToString(), new { #disabled = "disabled" })
Without ajax not, or you will have to repost the whole form. MVC is a web framework which is not dynamic like a winforms application. You will have to post the changes to your controller and reload the page with the necessary changes, or use ajax to reload these changes.
You could provide the default values for Year and Month properties (to be selected at the first request) and bind those instead of the hardcoded startup values you provided.
So instead of:
#Html.DropDownListFor(m => m.Year, new SelectList(Model.YearsList, "Value", "Text"), DateTime.Now.Year)
Which btw seems erroneous, as selected value (which I suppose DateTime.Now.Year is in your example) should be provided as SelectList's constructor (instead of DropDownListFor method's) argument. DropDownListFor method doesn't have a 'selected value' argument.
You could write:
#Html.DropDownListFor(m => m.Year, new SelectList(Model.YearsList, "Value", "Text", Model.Year))
and analogously in the second dropdown.
This will make dropdowns keeps the selected values when rendered using the posted model (as Model.Year and Model.Month would hold those). So you should make sure those values won't get overwritten with default ones after subsequent submits.