How to set dynamic column header in webgrid? - asp.net-mvc-3

I have used web grid to display employee name, and the projects in a drop down against each employee. For Projects drop down column I have to display the date as column heading which is returned from a HTML helper. i.e. as per the below code instead of column heading "SelectedDate", I have to display the value returned by Html helper (DateTime)#Html.GetNextDate((DateTime)item.SelectedDate, 0)).
Below is the snap shot of View
#{
var grid = new WebGrid(Model.employeeProjectsMapper);
}
#grid.GetHtml(
columns: grid.Columns(
grid.Column("EmployeeName"),
grid.Column(
header: "SelectedDate",
format:
#<span>
#{ var index = Guid.NewGuid().ToString(); }
#Html.Hidden("employeeProjectsMapper.Index", index)
#Html.Hidden("employeeProjectsMapper[" + index + "].EmployeeID", (Int64)item.EmployeeID)
#Html.Hidden("employeeProjectsMapper[" + index + "].SelectedDate",
(DateTime)#Html.GetNextDate((DateTime)item.SelectedDate, 0))
#Html.DropDownList("employeeProjectsMapper[" + index + "].SelectedProject",
Model.ProjectList)
</span>
)
)
)
Any help is much appreciated.
Thanks
Suma

You could add a property to your view model to which your view is strongly typed and which will contain the selected date of the first record for example (since you said in the comments section that all employees have the same value for SelectedDate):
public class MyViewModel
{
public IEnumerable<SelectListItem> ProjectList { get; set; }
public IEnumerable<EmployeeViewModel> employeeProjectsMapper { get; set; }
public DateTime SelectedDate { get; set; }
}
and then inside your controller action simply populate the value for this property:
public ActionResult Index()
{
var model = new MyViewModel();
model.ProjectList = ...
model.employeeProjectsMapper = ...
// set the SelectedDate property from the first employee record
// that will be used as header in the selected project column
model.SelectedDate = model.employeeProjectsMapper.First().SelectedDate;
return View(model);
}
and finally in your view you could use this property to set the header text using the custom HTML helper:
#model MyViewModel
#{
var grid = new WebGrid(Model.employeeProjectsMapper);
}
#grid.GetHtml(
columns: grid.Columns(
grid.Column("EmployeeName"),
grid.Column(
header: ((DateTime)Html.GetNextDate(Model.SelectedDate, 0)).ToShortDateString(),
format:
#<span>
#{ var index = Guid.NewGuid().ToString(); }
#Html.Hidden("employeeProjectsMapper.Index", index)
#Html.Hidden("employeeProjectsMapper[" + index + "].EmployeeID", (Int64)item.EmployeeID)
#Html.Hidden("employeeProjectsMapper[" + index + "].SelectedDate", (DateTime)Html.GetNextDate((DateTime)item.SelectedDate, 0))
#Html.DropDownList("employeeProjectsMapper[" + index + "].SelectedProject", new SelectList(Model.ProjectList, "Value", "Text", item.SelectedProject))
</span>
)
)
)

Related

Telerik Blazor Grid: GridAutoGeneratedColumns set the width of a column in the model using Data annotation

I am using Telerik Blazor Grid and GridAutoGeneratedColumns feature to generate a grid and its columns regarding the properties of the model.
here's the question:
I remember I saw something like adding a Data annotation to the model's property which defines the ColumnWidth for instance to indicate a specific width for a column. But I cannot find it anymore.
So, generally, is there any way to define a specific column width for a property in the model so the auto generated columns can render it automatically and dynamically?
Have a look on the code so it'll be more clear:
#page "/test"
#using System.ComponentModel.DataAnnotations;
<TelerikGrid Data=#GridData
AutoGenerateColumns="true"
Pageable="true"
Sortable="true"
Groupable="true"
OnUpdate="#UpdateItem"
OnDelete="#DeleteItem"
OnCreate="#CreateItem">
<GridToolBar>
<GridCommandButton Command="Add" Icon="add">Add Employee</GridCommandButton>
</GridToolBar>
<GridColumns>
<GridColumn Field="#nameof(Employee.EmployeeId)" Title="Employee Id" Width="120px" Editable="false" />
<GridAutoGeneratedColumns />
<GridCommandColumn>
<GridCommandButton Command="Edit" Icon="edit">Edit</GridCommandButton>
<GridCommandButton Command="Delete" Icon="delete">Delete</GridCommandButton>
<GridCommandButton Command="Save" Icon="save" ShowInEdit="true">Update</GridCommandButton>
<GridCommandButton Command="Cancel" Icon="cancel" ShowInEdit="true">Cancel</GridCommandButton>
</GridCommandColumn>
</GridColumns>
</TelerikGrid>
#code {
public class Employee
{
public Employee()
{
HireDate = MeetingDate = DateTime.Now;
}
[Display(AutoGenerateField = false, Name = "Employee #")]
public int? EmployeeId { get; set; }
[Editable(false)]
[Display(Name = "Employee Name")]
public string Name { get; set; }
[Display(Name = "Age In Years")]
public int? AgeInYears { get; set; }
[Display(Name = "Graduate Grade")]
public decimal? GraduateGrade { get; set; }
[Display(Name = "HireDate")]
public DateTime HireDate { get; set; }
[Display(AutoGenerateField = false, Name = "Meeting Date")]
public DateTime MeetingDate { get; set; }
}
public List<Employee> GridData { get; set; }
protected override void OnInitialized()
{
GridData = new List<Employee>();
var rand = new Random();
for (int i = 0; i < 100; i++)
{
GridData.Add(new Employee()
{
EmployeeId = i,
Name = "Employee " + i.ToString(),
AgeInYears = rand.Next(10, 80),
HireDate = DateTime.Now.Date.AddDays(rand.Next(-20, 20)),
MeetingDate = DateTime.Now.Date.AddDays(rand.Next(20, 40)),
GraduateGrade = i % 4 + 3
});
}
}
private void CreateItem(GridCommandEventArgs args)
{
var argsItem = args.Item as Employee;
argsItem.EmployeeId = GridData.Count + 1;
argsItem.Name = "Employee " + argsItem.EmployeeId;
GridData.Insert(0, argsItem);
}
private void DeleteItem(GridCommandEventArgs args)
{
var argsItem = args.Item as Employee;
GridData.Remove(argsItem);
}
private void UpdateItem(GridCommandEventArgs args)
{
var argsItem = args.Item as Employee;
var index = GridData.FindIndex(i => i.EmployeeId == argsItem.EmployeeId);
if (index != -1)
{
GridData[index] = argsItem;
}
}
}
And the result is something like:
And what I expect is setting a specific column width for instance, something like this:
[Editable(false)]
[Display(Name = "Employee Name")]
[ColumnWidth="200px"]
public string Name { get; set; }
Thank you in advance and stay healty and productive.
The column width feature of the autogenerated columns is the ColumnWidth parameter that lets you set the same width for all of them. You can read more about it in the AutoGenerated Columns - Customization section
There is no "column width" attribute out-of-the-box in C# and so it would be strange for the grid to use it. What I can suggest you consider is that you create a custom attribute (see an example here) and use a loop to create the columns in a fashion similar to this example (you can use reflection to get the fields from the model if you don't use an expando object), and then read their attributes and set the Width parameter - essentially, make your own column generation.

MVC3 RadioButtonFor value is not binded to the model

I have a MVC3 Razor form. It have a radiobutton list and some another text fields. When I press submit controller post action get the view model, which have all fields seted correctly, except RegionID.
Model:
namespace SSHS.Models.RecorderModels
{
public class CreateViewModel
{
...
public int RegionID { get; set; }
...
}
}
Controller:
namespace SSHS.Controllers
{
public class RecorderController : Controller
{
...
public ActionResult Create()
{
EntrantDBEntities db = new EntrantDBEntities();
List Regions = new List(db.Region);
List Schools = new List(db.School);
List Settlements = new List(db.settlement);
CreateViewModel newEntr = new CreateViewModel();
ViewBag.Regions = Regions;
ViewBag.Schools = Schools;
ViewBag.Settlements = Settlements;
return View(newEntr);
}
[HttpPost]
public ActionResult Create(CreateViewModel m)
{
EntrantDBEntities db = new EntrantDBEntities();
Entrant e = new Entrant()
{
FatherName = m.FatherName,
Lastname = m.LastName,
LocalAddress = m.LocalAddress,
Name = m.Name,
RegionID = m.RegionID,
PassportID = m.PassportID,
SchoolID = m.SchoolID,
SettlementID = m.SattlementID,
TaxID = m.TaxID,
};
db.Entrant.AddObject(e);
db.SaveChanges();
return RedirectToAction("Index");
}
}
View:
#model SSHS.Models.RecorderModels.CreateViewModel
#using SSHS.Models
#using (Html.BeginForm("Create", "Recorder", FormMethod.Post))
{
#foreach (Region item in ViewBag.Regions)
{
#Html.RadioButtonFor(m => m.RegionID, item.RegionID)
#Html.Label(item.RegionName) - #item.RegionID
}
...
...
}
The Create(CreateViewModel m) method gets data from all textboxes normaly, but RegionID always is 0.
How are you planning to fill radio button with int ? It have two states: checked and not. Could you tell us, what are you trying to do? Make radio group? Use bool for RadioButtonFor.
Added:
You need to write something like this: CheckboxList in MVC3.0 (in your example you will have radio buttons)

MultiSelectList does not highlight items in the selectedValues list

In a ASP.NET MVC (Razor) project, I'm using a ListBox with Multi Select option in a Edit View, there was a problem in highlighting the previously selected items by using selectedValues in MultiSelectList, so I asked a question on SO previously. According to the answers given for that question I decided to use a ViewModel (with AutoMapper) for passing the data to the View, without using the ViewBag, but still I have the same problem.. It does not select the items given in the selectedValues list
this is my new code
MODELS
public class Post
{
public int Id { get; set; }
...
public string Tags { get; set; }
}
public class PostEditViewModel
{
private DocLibraryContext db = new DocLibraryContext();
public int Id { get; set; }
..
public MultiSelectList TagsList { get; set; }
}
Controller
public ActionResult Edit(int id)
{
Post post = db.Posts.Find(id);
PostEditViewModel postEditViewModel = Mapper.Map<Post, PostEditViewModel>(post);
var tagsQuery = from d in db.Tags
orderby d.Name
select d;
postEditViewModel.TagsList = new MultiSelectList(tagsQuery, "Id", "Name", post.Tags.Split(','));
return View(postEditViewModel);
}
VIEW
<div class="editor-field">
#Html.ListBoxFor(model => model.Tags, Model.TagsList as MultiSelectList)
</div>
What am I doing wrong here? Please help....
UPDATE 1 :
changed controller to
public ActionResult Edit(int id)
{
Post post = db.Posts.Find(id);
PostEditViewModel postEditViewModel = Mapper.Map<Post, PostEditViewModel>(post);
var tagsQuery = from d in db.Tags
orderby d.Name
select d;
var selectedIds = post.Tags.Split(',').Select(n => tagsQuery.First(t => t.Name == n));
postEditViewModel.TagsList = new MultiSelectList(tagsQuery, "Id", "Name", selectedIds);
return View(postEditViewModel);
}
but I get the same results.
UPDATE 2:
I tried changing code (as in this tutorial), which worked, But I need to use previous method..
MODELS
public Post Post { get; set; }
public MultiSelectList TagsList { get; set; }
public PostEditViewModel(Post post)
{
Post = post;
var tagsQuery = from d in db.Tags
orderby d.Name
select d;
TagsList = new MultiSelectList(tagsQuery, "Name", "Name", post.Tags.Split(','));
}
Controller
public ActionResult Edit(int id)
{
Post post = db.Posts.Find(id);
return View(new PostEditViewModel(post));
}
VIEW
<div class="editor-field">
#Html.ListBox("Tags", Model.TagsList as MultiSelectList)
</div>
What makes the difference...??
The problem is with the construction of your MultiSelectList:
new MultiSelectList(tagsQuery, "Id", "Name", post.Tags.Split(','));
You are specifying that the values for the elements will be taken from each tag's Id property, but then for the actual selected values you are passing in an array of strings which presumably corresponds to the Names of the tags. It doesn't matter that you also specify Name to be the property from which the display text will be determined; the selectedValues parameter matches against values, not display text.
To fix this, project each tag name into its corresponding Id:
var selectedIds = post.Tags.Split(',').Select(n => tagsQuery.First(t => t.Name == n).Id);
new MultiSelectList(tagsQuery, "Id", "Name", selectedIds);
Update:
Oops, there was a mistake in the code above.
I edited the answer to add a required .Id at the end of the selectedIds initialization -- the previous version was selecting tags, not ids (and of course they were comparing unequal, apples and oranges).
I had the same problem, I used my own extention method to generate the html and problem solved
public static MvcHtmlString ListBoxMultiSelectFor<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
object htmlAttributes)
{
return ListBoxMultiSelectFor(helper, expression, selectList, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString ListBoxMultiSelectFor<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
IDictionary<string, object> htmlAttributes)
{
string name = ExpressionHelper.GetExpressionText(expression);
TagBuilder selectTag = new TagBuilder("select");
selectTag.MergeAttributes(htmlAttributes);
selectTag.MergeAttribute("id", name, true);
selectTag.MergeAttribute("name", name, true);
foreach (SelectListItem item in selectList)
{
TagBuilder optionTag = new TagBuilder("option");
optionTag.MergeAttribute("value", item.Value);
if (item.Selected) optionTag.MergeAttribute("selected", "selected");
optionTag.InnerHtml = item.Text;
selectTag.InnerHtml += optionTag.ToString();
}
return new MvcHtmlString(selectTag.ToString());
}

WebGrid grid.Columns Format Error

#{
var grid = new WebGrid(Model.Auctions, rowsPerPage: Model.PagingInfo.ItemsPerPage, defaultSort: "AddedDate");
}
#grid.GetHtml(
columns: grid.Columns(
**grid.Column(columnName: "", header: "Type", format: (auction) => AuctionListViewModel.GetAuctionType(auction)),**
grid.Column(columnName: "OwnerReference", header: "Owner reference")
)
);
public class AuctionListViewModel
{
public IEnumerable<Auction> Auctions { get; set; }
public IEnumerable<Item> Items { get; set; }
public PagingInfo PagingInfo { get; set; }
public string Title { get; set; }
public string Action { get; set; }
public static string GetAuctionType(Auction auction)
{
var type = string.Empty;
if (auction is LubAuction)
{
type = "Lowest unique wins";
}
else if (auction is EsfAuction)
{
type = "Highest wins";
}
return type;
}
}
With the above view code and model, get the following error on the line marked in bold, why is this?
The best overloaded method match for 'UI.Models.AuctionListViewModel.GetAuctionType(UI.AuctionService.Auction)' has some invalid arguments
In the grid.Column method's format parameter's parameter (in your case auction) you get the actual item (an Auction) but it's wrapped into a dynamic wrapper called WebGridRow.
You can use your properties on this wrapper and it delegates to the actual item e.g: auction.Title will work, but if you want to get the whole item (the Auction) you need to use the Value property of the WebGridRow.
format: auction =>
uctionListViewModel.GetAuctionType(((WebGridRow)auction).Value)
Due to the dynamic (weak) typing of the WebGrid helper you need a cast:
grid.Column(
columnName: "",
header: "Type",
format: (auction) => AuctionListViewModel.GetAuctionType((Auction)auction.Value)
)
I would recommend you using better grid helpers such as MvcContrib Grid and Telerik Grid which will give you strong typing and compile time safety.

Best way to sort a DropDownList in MVC3 / Razor using helper method

Hi so I'm pretty new to MVC3 and Razor and I've been trying to get my head around it the past few days. I've been given a task by my project architect to create a helper method that sorts a drop down list in an MVC View. I have a View that retrieves various data from a Controller and I'm returning some values that I want to appear in a drop down list. I've been told not to sort it in the Controller and also to pass the field that we want to sort by into the helper method. I could do it like below but the architect wants to keep the view free of c sharp code:
#Html.DropDownListFor(model => model.StudyName, new SelectList(ViewBag.StudyTypes, "Value", "Text").OrderBy(l => l.Text))
So I've created some sample code and some extension methods to try and get it to work. My idea is to replicate the existing Html.DropDownList method and allow the passing of 'object htmlAttributes' so I can set the style as part of the method call.
Here's my code so far. I'm returning the data for the drop down in ViewBag.StudyTypes in the Edit Controller method:
public ActionResult Edit(int id)
{
IEnumerable<SelectListItem> mySelectList = new List<SelectListItem>();
IList<SelectListItem> myList = new List<SelectListItem>();
for (int i = 0; i < 5; i++)
{
myList.Add(new SelectListItem()
{ Value = i.ToString(), Text = "My Item " + i.ToString(), Selected = i == 2 }
);
}
mySelectList = myList;
ViewBag.StudyTypes = mySelectList;
StudyDefinition studydefinition = db.StudyDefinitions.Find(id);
return View(studydefinition);
}
Here's my View code:
#model MyStudyWeb.Models.StudyDefinition
#using MyStudyWeb.Helpers
#{
ViewBag.Mode = "Edit";
}
<div>
#Html.DropDownListSorted(new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
#Html.DropDownListSorted("MyList", new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
</div>
Finally below are the extension methods I'm trying to get to work. The first extension method does nothing, I just get a blank space at that point in the View. The second method kind of works but it's ugly. For the 3rd method I don't know how to specify an 'order by' parameter as the OrderBy on an IEnumerable expects a Linq expression.
namespace StudyDefinition.Helpers
{
public static class HtmlHelperExtensions
{
// 1st sort method: sort the passed in list and return a new sorted list
public static SelectList DropDownListSorted(this HtmlHelper helper, IEnumerable<SelectListItem> selectList)
{
var x = new SelectList(selectList.ToList()).OrderBy(l => l.Text);
return x as SelectList;
}
// 2nd sort method: return IHtml string and create <select> list manually
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList)
{
StringBuilder output = new StringBuilder();
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
// 3rd sort method: pass in order by parameter - how do I use this?
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList, string orderBy)
{
StringBuilder output = new StringBuilder();
//How do I use the orderBy parameter?
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
}
}
I really don't know the best approach to take, there may be a much simpler way that I'm totally missing and I might be at the point where I can't see the wood for the trees anymore. Some questions
Should I return a SelectList or an MvcHtmlString, or something else entirely?
For the first extension method how do I get the returned SelectList to render in the View?
How to I pass in a parameter to my extension methods that specifies the sort order?
How do I pass an 'object htmlAttributes' parameter, and how do I apply this object / parameter to the SelectList?
If anyone has some ideas or suggestions then I'd appreciate some feedback :)
The first and most important part of your code would be to get rid of any ViewBag/ViewData (which I personally consider as cancer for MVC applications) and use view models and strongly typed views.
So let's start by defining a view model which would represent the data our view will be working with (a dropdownlistg in this example):
public class MyViewModel
{
public string SelectedItem { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
then we could have a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
// I am explicitly putting some items out of order
Items = new[]
{
new SelectListItem { Value = "5", Text = "Item 5" },
new SelectListItem { Value = "1", Text = "Item 1" },
new SelectListItem { Value = "3", Text = "Item 3" },
new SelectListItem { Value = "4", Text = "Item 4" },
}
};
return View(model);
}
}
and a view:
#model MyViewModel
#Html.DropDownListForSorted(
x => x.SelectedItem,
Model.Items,
new { #class = "foo" }
)
and finally the last piece is the helper method which will sort the dropdown by value (you could adapt it to sort by text):
public static class HtmlExtensions
{
public static IHtmlString DropDownListForSorted<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> items,
object htmlAttributes
)
{
var model = helper.ViewData.Model;
var orderedItems = items.OrderBy(x => x.Value);
return helper.DropDownListFor(
expression,
new SelectList(orderedItems, "Value", "Text"),
htmlAttributes
);
}
}
Just add in the sorting before you return the items to the dropdown list.
Do this:
Models: StudyViewModel.cs
public class StudyViewModel {
public string StudyName { get; set; }
public string StudyTypes { get; set; }
}
Controller: StudyController.cs
using System.Web.Mvc;
public class StudyController
{
public List<SelectListItem> studyTypes()
{
List<SelectListItem> itemList = new List<SelectListItem>();
for (var i=0; i<5; i++)
{
itemList.Add = new SelectListItem({
Value = i.ToString();
Text = "My Item";
});
}
// You can sort here....
List<SelectListItem> sortedList = itemList.OrderBy(x=>x.Text);
return sortedList;
}
public ActionResult Edit(int id)
{
//You won't need this because you get it using your
//controller's routine, instead
//ViewBag.StudyTypes = studySlots.OrderBy(e => e.Value);
//-- unless you need to add these values to the model for
// some reason (outside of filling the ddl), in which case....
// StudyViewModel svm = new StudyViewModel();
// svm.StudyTypes = studySlots.OrderBy(e => e.Value);
// svm.StudyName = "My Item";
// return View(svm);
// Otherwise, just....
return View();
}
}
View: Edit.cshtml
#Html.DropDownListFor(model => model.StudyName)
.OptionLabel('Select...')
.DataTextField('Text')
.DataValueField('Value')
.Datasource(source =>
{
// This is where you populate your data from the controller
source.Read(read =>
{
read.Action("studyTypes", "Study");
});
})
.Value(Model.StudyName != null ? Model.StudyName.ToString() : "")
)
This way will avoid ViewBags and just use a function to fill in the values, directly.
If you are using a database you can use a query to define the sort element
using (BDMMContext dataContext = new BDMMContext())
{
foreach (Arquiteto arq in dataContext.Arquitetos.SqlQuery("SELECT * FROM Arquitetos ORDER BY Nome"))
{
SelectListItem selectItem = new SelectListItem { Text = arq.Nome, Value = arq.Arquiteto_Id.ToString() };
//
list.Add(selectItem);
}
}

Resources