I'm developing an web app using C# and MVC. One of the page has multiple <tr> which will contain information but this information gets updated over time (1 month to 6 month) range. So I only want to show the <tr> which include the information. The information is stored in a database, each <tr> has it's own column. The approach I've gone with is I read the data and apply if conditions in the view.
So something like
#if (!string.IsNullOrEmpty(Model.SomePropertyOne))
{
<tr>
<td>#Html.DisplayNameFor(model => model.SomePropertyOne)</td>
<td>#Html.DisplayFor(model => model.SomePropertyOne)</td>
</tr>
}
#if (!string.IsNullOrEmpty(Model.SomePropertyTwo))
{
<tr>
<td>#Html.DisplayNameFor(model => model.SomePropertyTwo)</td>
<td>#Html.DisplayFor(model => model.SomePropertyTwo)</td>
</tr>
}
...
I have to this 8 times. So my question is, is there a better approach than this or am I stuck with using all these if statements?
Please let me know if you require any further information
You can create a DisplayTemplate containing the condition. In /Views/Shared/DisplayTemplates/ create a partial view (say) MyTemplate.cshtml
#model string
#if (!string.IsNullOrEmpty(Model))
{
<tr>
<td>#Html.DisplayNameFor(m => m)</td>
<td>#Model</td>
</tr>
}
and then in the view
#Html.DisplayFor(m => m.SomeProperty, "MyTemplate")
#Html.DisplayFor(m => m.AnotherProperty, "MyTemplate")
.... //etc
The DisplayFor() will generate the html based on the template, so if the value of the property is null or string.Empty, then nothing will be generated for that property.
Side note: You should not be using <table> elements for layout (refer Why not use tables for layout in HTML? and Why Tables Are Bad (For Layout*) Compared to Semantic HTML + CSS). Instead, use css to style you layout. For example, change the DisplayTemplate to
<div class="field">
<div class="field-label">#Html.DisplayNameFor(m => m)</div>
<div class="field-value">#Model</div>
</div>
and add the following css
.field {
position: relative;
margin: 5px 0;
}
.field-label {
position: absolute;
width: 240px;
color: #808080;
}
.field-value {
margin-left: 250px;
}
You can solve your problem via reflection, something like that:
#foreach(var prop in Model.GetType().GetProperties().Where(x => x.PropertyType == typeof(string)))
{
var value = prop.GetValue(Model);
if (value != null)
{
<tr>
<td>#prop.Name</td>
<td><input value="#value.ToString()" name="#prop.Name" /></td>
</tr>
}
}
But, at this case, you should avoid to use #Html helpers, instead - write corresponding html explicitly.
Related
I'm working on a Blazor component with a table where it makes sense to factor out a couple pieces of the template. However it's not rendering correctly and the td elements I'm producing are not ending up inside the tr element, but instead are at the same level.
Below is a simplified version of the code. The body has the problem while the footer renders correctly. What is the correct way to accomplish what I'm trying to here? I know I could avoid all of the Razor syntax and just create a function that returns a raw MarkupString, but that doesn't seem like it should be necessary for a case like this.
<table>
<tbody>
#foreach (var row in data)
{
#:<tr>
RenderRow(row);
#:</tr>
}
</tbody>
<tfoot>
<tr>
#if (footerRow != null)
{
RenderRow(footerRow);
}
</tr>
</tfoot>
</table>
#{
void RenderRow(Row row)
{
<td>#row.RowNum</td>
RenderRowHalf(row.Left);
RenderRowHalf(row.Right);
}
void RenderRowHalf(RowHalf half)
{
<td>#half.Foo</td>
<td>#(Util.ColorNumber(half.Bar))</td>
}
}
A lot to unpick here - using #: before the <tr>, then calling a C# method is switching context and Blazor will auto-close the tag - get rid of the #: - not needed.
Change your methods to return RenderFragment<T> - the Blazor way of creating a fragment of Razor markup. Call them with # prefix to switch back to C#.
The <text> tag helper just provides a way to group markup in the C# code sectiion.
Use #code for your C# code, otherwise it is scoped to each render cycle.
<table>
<tbody>
#foreach (var row in data)
{
<tr>
#RenderRow(row)
</tr>
}
</tbody>
<tfoot>
<tr>
#if (footerRow != null)
{
#RenderRow(footerRow)
}
</tr>
</tfoot>
</table>
#code
{
RenderFragment<Row> RenderRow => row =>
#<text>
<td>#row.RowNum</td>
#RenderRowHalf(row.Left)
#RenderRowHalf(row.Right)
</text>
;
RenderFragment<RowHalf> RenderRowHalf => half =>
#<text>
<td>#half.Foo</td>
<td>#(Util.ColorNumber(half.Bar))</td>
</text>
;
}
I am working on an ecommerce site where I am stuck on the cart management. Basically before login, products are kept in a session and I am trying to update the product quantity stored in the session using Ajax. I mean whenever I write in the 'Quantity To Change', the changed value should be reflected in the 'Quantity' column.
Note: I've shortened the post and figured out why it wasn't firing while debugging. Actually I was unable to get the id of the associated product. Now it passes the id. That's it. Now I've another issue - The TextBox are being created dynamically with a for loop. I used developer tools to figure out how the TextBoxes are generated dynamically and it is something like this:
For Product 1: cartDetails_0__Quantity
For Product 2: cartDetails_1__Quantity
I am wondering how to grab the quantity or values from the dynamically generated TextBoxes. If I put the generated id from HTML directly to Ajax, then it updates the quantity. Otherwise it doesn't. I've tried to use a loop in Ajax but I think, I am getting it wrong. Please see the View.
The view:
<table border="1" width="100%" cellpadding="4">
<thead>
<tr>
<th style="text-align:center;">Name</th>
<th style="text-align:center;">Price</th>
<th style="text-align:center;">Quantity</th>
<th style="text-align:center;">Quantity To Change</th>
</tr>
</thead>
<tbody>
#if (ViewBag.CartDetails != null)
{
for (int i = 0; i < cartDetails.Count(); i++)
{
<tr>
<td style="text-align: center; display:none;">#Html.DisplayFor(model => cartDetails[i].ProductId)</td>
<td id="ID" style="text-align: center;">#Html.DisplayFor(model => cartDetails[i].ProductName)</td>
<td style="text-align: center;">#Html.DisplayFor(model => cartDetails[i].Price)</td>
<td style="text-align: center;">#Html.DisplayFor(model => cartDetails[i].Quantity, new { #class = "quantityUpdate" })</td>
<td style="text-align: center;">#Html.TextBoxFor(model => cartDetails[i].Quantity, new { #class = "quantity", data_id = cartDetails[i].ProductId } )</td>
</tr>
}
}
</tbody>
</table>
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script type="text/javascript">
var url = '#Url.Action("UpdateCart")';
$(".quantityUpdate").change(function () {
var id = $(this).data('id');
var i = 0;
$('.quantityUpdate').each(function (i, item) {
$.post(url, { id: id, Quantity: $("#cartDetails_"+i+"__Quantity").val() }, function (response) {
if (response) {
$("#TotalPrice").load(window.location + " #TotalPrice");
}
});
})
alert(id);
alert($("#cartDetails_"+i+"__Quantity").val());
});
Here is an image sample that I am trying:
$('.quantity').change(function(){
$('.quantityUpdate').val($('.quantity').val());
// put code here
});
Instant Change
$('.quantity').keyup(function(){
$('.quantityUpdate').val($('.quantity').val());
// put code here
});
If the idea is to call ajax when you change the value in .quality textbox then this is how you should do:
$('.quantity').change(function(){
//your ajax call
});
I'm returning a list of lists to an MVC view, displayed as a set of tables. I'm using nested for loops to try to return the values within each list.
The tables are returning to the view with the expected number of rows in each, but instead of values in each of the rows, they are returning the names of their parameters, rather than the actual value of each parameter.
The code in my view is currently:
#if (Model.Report != null)
{
for(var row = 0; row < Model.Report.Count(); row++)
{
<table>
<tr>
<td><h4>#Html.DisplayNameFor(m=>m.Report[row].ReportData[row].ReportingGroup)</h4></td>
</tr>
<tr class="table-header">
<td style="text-align: center">Name</td>
<td style="text-align: center">Area</td>
<td style="text-align: center">Message</td>
</tr>
#for (var listing = 0; listing < Model.Report[row].ReportData.Count(); listing++)
{
<tr>
<td style="text-align: center">#Html.LabelFor(a=>a.Report[row].ReportData[listing].Name)</td>
<td style="text-align: center">#Html.LabelFor(a => a.Report[row].ReportData[listing].Area)</td>
<td style="text-align: center">#Html.LabelFor(a => a.Report[row].ReportData[listing].Message)</td>
</tr>
}
</table>
}
}
Rather than the actual values of the parameters returning to each row in the view, I'm returning the parameter names("Name", "Area", and "Message") to each. I am wondering where I've gone wrong here.
LabelFor displays a label for the property you pass it. Without further attributes, that's just the property name.
You just want to print the value itself:
#Model.Report[row].ReportData[listing].Message
You should replace your for loop with a foreach so you don't need to do all that.
Change your LabelFor to DisplayFor or TextBoxFor in your for loop.
I'm using a generic Razor view to allow any entity framework object to be edited. Here's a cut down version of it:
#model Object
#using (Html.BeginForm())
{
#foreach (var property in Model.VisibleProperties())
{
#Html.Label(property.Name.ToSeparatedWords())
#Html.Editor(property.Name, new { #class = "input-xlarge" })
}
}
And the VisibleProperties() function goes like this:
public static PropertyInfo[] VisibleProperties(this Object model)
{
return model.GetType().GetProperties().Where(info =>
(info.PropertyType.IsPrimitive || info.PropertyType.Name == "String") &&
info.Name != model.IdentifierPropertyName()).OrderedByDisplayAttr().ToArray();
}
(I'm reusing code from https://github.com/erichexter/twitter.bootstrap.mvc/)
One of my sample controllers goes as follows:
public ActionResult Edit(int id = 0)
{
TaskTemplate tasktemplate = db.TaskTemplates.Single(t => t.TaskTemplateID == id);
return View(tasktemplate);
}
Now the problem:
It all works fine except for where there's an ID property that relates to a 'parent' table, such as UserID. For these fields, the output of the #Html.Editor is simply:
FalseFalseFalseTrueFalse.
The True seems to correspond to the user in question - in this case the 4th user in the database.
Why is it not ouputting a nice textbox with the number 4 (or whatever the UserID) is in it?
I hope I've explained this clearly.
The reason for that is because editor/display templates are not recursing into complex child objects. If you want this to happen you could write a custom editor template for the object type (~/Views/Shared/Object.cshtml) as illustrated by Brad Wilson in this blog post (more specifically the Shallow Dive vs. Deep Dive section towards the end).
So:
<table cellpadding="0" cellspacing="0" border="0">
#foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
#Html.Editor(prop.PropertyName)
}
else
{
<tr>
<td>
<div class="editor-label" style="text-align: right;">
#(prop.IsRequired ? "*" : "")
#Html.Label(prop.PropertyName)
</div>
</td>
<td>
<div class="editor-field">
#Html.Editor(prop.PropertyName)
#Html.ValidationMessage(prop.PropertyName, "*")
</div>
</td>
</tr>
}
}
</table>
I want to show data in grid view using 3 tables in SQL database.
first I created Model
public class common
{
public Artist Artist { get; set; }
public Album Album { get; set; }
public Genre Genre { get; set; }
}
Then This is the Controller
public ActionResult Show1()
{
var query = from a in DB.Album
join b in DB.Artists
on a.ArtistId equals b.ArtistId
join c in DB.Genre
on a.GenreId equals c.GenreId
where (b.ArtistId == 2)
select new common { Album = a, Artist = b, Genre = c };
return View(query.ToList());
}
}
After that this is my View
#model IEnumerable<test1.Models.common>
#{
ViewBag.Title = "Show1";
}
<h2>Show1</h2>
<div>
#{
var grid = new WebGrid(Model, defaultSort:"Name");
}
#grid.GetHtml()
</div>
But it doesn't show any data?
How can I do it?
I think you need an editorTemplate for your common object model
or use a for sentence and populate an html table
for example...
<table summary="">
<thead>
<tr>
<th></th>
<th>
Account No.
</th>
<th>
Customer Name
</th>
<th class="SingleCheckBox">
Is Approved
</th>
<th class="SingleCheckBox">
Is Locked out
</th>
<th>
Last Login
</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Count(); ++i)
{
var item = Model[i];
bool isSelected = item.AccountNo == selectedAccountNo;
<tr>
<td>#Html.RadioButton("selectedUserName", item.UserName, isSelected, new { name = "selectedUserName" })</td>
<td>
#Html.DisplayFor(model => model[i].UserName)
#Html.HiddenFor(model => model[i].UserName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td class="SingleCheckBox">
#Html.CheckBoxFor(model => model[i].IsApproved)
</td>
<td class="SingleCheckBox">
#if (item.IsLockedOut)
{
#Html.CheckBoxFor(model => model[i].IsLockedOut);
}
else
{
#Html.CheckBoxFor(model => model[i].IsLockedOut);
}
</td>
<td class="last-child">
#(TimeZoneInfo.ConvertTime(DateTime.SpecifyKind(item.LastLoginDate, DateTimeKind.Utc), timeZoneInfo).ToString())
</td>
</tr>
}
</tbody>
</table>
I believe that the best answer to your question is yet another question: "Why a WebGrid?"
If you refer to the default functionality of a newly created MVC project, you will see that the Index.cshtml will use a table (as suggested in the answer provided by #hagensoft). It has been my experience that when items are properly scaffolded for an MVC project in Visual Studio I have had to do very little work to get the list of models to display nicely, even paginated if necessary.
To make better use of pagination, if that is what you are after, I have made great use of the PagedList.MVC package available through NuGet (https://www.nuget.org/packages/PagedList.Mvc/). There is plenty of documentation related to the functionality provided by PagedList, and PagedList, along with the table suggestion/default view behavior with new MVC projects in Visual Studio, works wonders along side of any sorting, searching, or similar functionality that you would like to provide within your app.
A fantastic tutorial that I refer to about Sorting, Filtering, and Paging can be found here: https://learn.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application
If you are insistent about using a WebGrid/GridView, I would suggest perhaps moving the database call out of the controller and directly into the Razor view itself, or try sending back an ObervableCollection<>, not a List<>, of a dedicated ViewModel from the Controller.
The moral of the story here is to not venture far from the provided path. Try to use the tools that are given to you and follow the default format for MVC projects.
First Add Jquery in your view
<script src="../../Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
If you want to style Add Style
<style type="text/css">
.webGrid { margin: 4px; border-collapse: collapse; width: 500px; background-color:#FCFCFC;}
.header { background-color: #C1D4E6; font-weight: bold; color: #FFF; }
.webGrid th, .webGrid td { border: 1px solid #C0C0C0; padding: 5px; }
.alt { background-color: #E4E9F5; color: #000; }
.gridHead a:hover {text-decoration:underline;}
.description { width:auto}
.select{background-color: #389DF5}
</style>
Add Model in your view
#{
test1.Models.common Common = new test1.Models.common();
}
Add Code in your view
#{
var grid = new WebGrid(Model, canPage: true, rowsPerPage: 5, selectionFieldName: "selectedRow",ajaxUpdateContainerId: "gridContent");
grid.Pager(WebGridPagerModes.NextPrevious);}
<div id="gridContent">
#grid.GetHtml(tableStyle: "webGrid",
headerStyle: "header",
alternatingRowStyle: "alt",
selectedRowStyle: "select",
columns: grid.Columns(
grid.Column("Id", "Id"),
grid.Column("Name", "Name"),
grid.Column("Description", "Description"),
))
#if (grid.HasSelection)
{
common= (test1.Models.common)grid.Rows[grid.SelectedIndex].Value;
<b>Id</b> #common.Artist.Id<br />
<b>Name</b> #common.Album.Name<br />
<b>Description</b> #common.Album.Description<br />
}
</div>
Edit this section According to your Model details
#if (grid.HasSelection)
{
common= (test1.Models.common)grid.Rows[grid.SelectedIndex].Value;
<b>Id</b> #common.Artist.Id<br />
<b>Name</b> #common.Album.Name<br />
<b>Description</b> #common.Album.Description<br />
}
Because of your model it doesn't show anything. Webgrid doesn't know how to render your Artist or Album or Genre table model. You need to create new model with basic variables like string, int, decimal
public ActionResult Show1()
{
var query = from a in DB.Album
join b in DB.Artists
on a.ArtistId equals b.ArtistId
join c in DB.Genre
on a.GenreId equals c.GenreId
where (b.ArtistId == 2)
select new common { AlbumName = a.AlbumName, ArtistName = b.ArtistName, GenreName = c.GenreName /* ... other props */ };
return View(query.ToList());
}
public class common
{
public string ArtistName { get; set; }
public string AlbumName { get; set; }
public string GenreName { get; set; }
//you need to use basic variables like string, int, decimal...
}