How to make this Razor View work like the ASPX one does? - asp.net-mvc-3

I have been struggling to get a partial View working in Razor. The View engine cannot make sense of the code below but it is simple using the ASPX View engine. Can anyone show me how to get this to work with Razor? Note that I am just writing out a calendar so the <tr> tag happens at the end of every week. The first sign of a problem is that the Razor code will not format in the VS editor and it complains that the 'while' block is missing its closing brace. I have tried all kinds of combinations, even using a delegate. (I think the cause of the problem may be the conditional TR tag because it is highlighted as an error because it is not closed.)
Razor (doesn't work)
<table class="calendarGrid">
<tr class="calendarDayNames">
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
#{
var loopDate = gridStartDate;
}
#while (loopDate <= gridEndDate)
{
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
<tr class="calendarWeek">
}
<td class="calendarDay">
<span class="calendarDayNumber">#loopDate.Day</span>
#if (Model.AllCalendarDays.ContainsKey(loopDate.Date))
{
foreach (var ev in Model.AllCalendarDays[loopDate.Date])
{
<span class="calendarEvent">#ev.Venue</span>
}
}
</td>
#{
loopDate = loopDate.AddDays(1);
#if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
</tr>
}
}
}
ASPX (works)
<table class="calendarGrid">
<tr class="calendarDayNames">
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
<%
var loopDate = gridStartDate;
while (loopDate <= gridEndDate)
{
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
%>
<tr class="calendarWeek">
<%} %>
<td class="calendarDay">
<span class="calendarDayNumber">
<%: loopDate.Day %></span>
<% if (Model.AllCalendarDays.ContainsKey(loopDate.Date))
{
foreach (var ev in Model.AllCalendarDays[loopDate.Date])
{ %>
<span class="calendarEvent">
<%: ev.Venue %></span>
<% }
} %>
</td>
<% {
loopDate = loopDate.AddDays(1);
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{ %>
</tr>
<% }
}
} %>
</table>
Working solution in Razor based on #jgauffin's view model suggestion and #dommer's ugly raw html solution. Combined together they're almost aesthetically acceptable. :)
View model now has iterator
public IEnumerable<Tuple<DateTime, IList<CalendarEventDto>>> GridItems()
{
var loopDate = GridStartDate;
while (loopDate <= GridEndDate)
{
yield return new Tuple<DateTime, IList<CalendarEventDto>>(loopDate.Date, AllCalendarDays[loopDate.Date]);
loopDate = loopDate.AddDays(1);
}
}
Okay, the Tuple is lazy but I will probably create another model to hold more complex information about the date and events (IsPast/greyed, etc).
The pesky View
#foreach (var item in Model.GridItems())
{
if (item.Item1.DayOfWeek == DayOfWeek.Monday)
{
#Html.Raw("<tr class=\"calendarWeek\">");
}
#Html.Raw("<td class=\"calendarDay\">");
#Html.Raw(string.Format("<span class=\"calendarDayNumber\">{0}</span>", item.Item1.Day));
foreach (var ev in item.Item2)
{
#Html.Raw(string.Format("<span class=\"calendarEvent\">{0}</span>", Server.HtmlEncode(ev.Venue)));
}
#Html.Raw("</td>");
if (item.Item1.DayOfWeek == DayOfWeek.Sunday)
{
#Html.Raw("</tr>");
}
}
Note that when I reformat the View source in VS, it gets egregiously tabbed, with the if statement having about 10 tabs to the left of it, but there are no compilation warnings and it does what I want. Not nice, or easy though. I think the Razor devs should provide some support for explicit breakout and breakin to code and markup so that when the parser cannot parse it unambiguously, we can tell it what we intended.
#Andrew Nurse's solution
Andrew 'works on the ASP.Net team building the Razor parser!'. His solution runs okay but still produces compiler warnings and is obviously confusing Visual Studio because the code cannot be reformatted without ending up in a big glob on a few lines:
<tbody>
#foreach (var calDay in Model.GridItems())
{
if (calDay.DayOfWeek == DayOfWeek.Monday)
{
#:<tr class="calendarWeek">
}
<td class="calendarDay">
<span class="calendarDayNumber">#calDay.Day</span>
#foreach (var ev in calDay.CalendarEvents)
{
<span class="calendarEvent">#ev.Venue</span>
}
</td>
if (calDay.DayOfWeek == DayOfWeek.Sunday)
{
#:</tr>
}
}
</tbody>

The primary issues here were these lines:
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
<tr class="calendarWeek">
}
...
#if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
</tr>
}
The problem is that Razor uses the tags to detect the start and end of markup. So since you didn't close the "tr" tag inside the first if, it doesn't actually switch back to code, so it doesn't see the "}" as code. The solution is to use "#:", which lets you put a line of markup without regard for tags. So replacing those lines with this should work and be more concise than using Html.Raw:
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
#:<tr class="calendarWeek">
}
...
#if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
#:</tr>
}

I would move all logic to the viewmodel which leaves the following code in your view:
#while (Model.MoveNext())
{
#Model.WeekHeader
<td class="calendarDay">
<span class="calendarDayNumber">#Model.DayNumber</span>
#foreach (var ev in Model.CurrentDayEvents)
{
<span class="calendarEvent">#ev.Venue</span>
}
</td>
#Model.WeekFooter
}
And the new model:
public class CalendarViewModel
{
private DateTime _currentDate;
public string WeekHeader
{
get
{
return _currentDate.DayOfWeek == DayOfWeek.Monday ? "<tr class="calendarWeek">" : "";
}
}
public string WeekFooter
{
get
{
return _currentDate.DayOfWeek == DayOfWeek.Monday ? "</tr>" : "";
}
}
public IEnumerable<DayEvent>
{
get
{
return AllCalendarDays.ContainsKey(loopDate.Date) ? AllCalendarDays[loopDate.Date] ? new List<DayEvent>();
}
}
public bool MoveNext()
{
if (_currentDate == DateTime.MinValue)
{
_currentDate = gridStartDate;
return true;
}
_currentDate = _currentDate.AddDays(1);
return _currentDate <= gridEndDate;
}
}

MAJOR EDIT: Okay, what happens if you do this?
<table class="calendarGrid">
<tr class="calendarDayNames">
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
#{
var loopDate = gridStartDate;
while (loopDate <= gridEndDate)
{
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
#Html.Raw("<tr class=\"calendarWeek\">");
}
#Html.Raw("<td class=\"calendarDay\">");
#Html.Raw("<span class=\"calendarDayNumber\">" + loopDate.Day + "</span>");
if (Model.AllCalendarDays.ContainsKey(loopDate.Date))
{
foreach (var ev in Model.AllCalendarDays[loopDate.Date])
{
#Html.Raw("<span class=\"calendarEvent\">" + ev.Venue + "</span>");
}
}
#Html.Raw("</td>");
loopDate = loopDate.AddDays(1);
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
#Html.Raw("</tr>");
}
}
}

Have you tried adding <text> tags around the contents of the blocks?
I think the Razor parse only works when it's obvious where the blocks end. It may be getting confused by the fact you have an if, a td and then some more code, all inside the block.
There's more info on this here: http://weblogs.asp.net/scottgu/archive/2010/12/15/asp-net-mvc-3-razor-s-and-lt-text-gt-syntax.aspx

Related

Apply different DIV in For each loop

I am working on MVC 5. Using WEB API, fetch the data now it is time to apply the HTML PAGE design and CSS.
#foreach (var item in Model)
{
if (Model.First() == item)
{
///APPLY FIRST RECORD CSS:- Works FINE
}
else {
<div class="row">
<div class="col-sm-4">
</div>
</div>
}
}
In the else portion, every time it generates the new ROW for a SINGLE record. But I am interested to display record 2 3 4 in SECOND ROW. 5 6 7 Record in the THIRD ROW and so on.
If it is the first item, open a div and then put the items in it. Close the div when the number of columns is 3 or close the div if the item is at the end of the list.
The following algorithm does this for you
#{
int i = 0;
int columnCounter = 1;
bool newRow = false;
}
#foreach (var item in Model)
{
//add and open div row
if (i == 0 || newRow)
{
newRow = false;
#:<div class="row" style="border:2px solid red;">
}
<div class="col-md-4" style="padding:0;">
<div style="height:40px;background:#f6f6f6;width:100%;text-align:center;">
<span>Column #i</span>
</div>
</div>
//close div row if column count == 3 or reach of end list
if (columnCounter == 3 || i == Model.Count - 1)
{
newRow = true;
columnCounter = 1;
#:</div>
}
else
{
columnCounter = columnCounter + 1;
}
}
result:

How to represent Attributes with Anonymous Alphabets in view blade in Laravel

I have this code in Laravel-5.8:
public function index()
{
$userCompany = Auth::user()->company_id;
$departments = HrDepartment::where('company_id', $userCompany)->get();
return view('hr.departments.index')->with('departments', $departments);
}
view
#foreach($departments as $key => $department)
<tr>
<td>
{{$key+1}}
</td>
<td>
{{$department->dept_name ?? '' }}
</td>
<td>
{{isset($department->dept_code) ? $department->dept_code : 'N/A'}}
</td>
</tr>
#endforeach
I don't want the user to see the real department name ( {{$department->dept_name ?? '' }}), but to make it anonymous.
That is, it can randomize {{$department->dept_name ?? '' }} and represent each with alphabets. The first one can be A, second B, C, .... Z
How do I achieve this?
Thanks
You can use this simple function using the ID of the department
function toAlphabet($integer){
$alphabet = array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
$alpha_flip = array_flip($alphabet);
if($integer <= 25){
return $alphabet[$integer];
}
elseif($integer > 25){
$dividend = ($integer + 1);
$alpha = '';
$modulo;
while ($dividend > 0){
$modulo = ($dividend - 1) % 26;
$alpha = $alphabet[$modulo] . $alpha;
$dividend = floor((($dividend - $modulo) / 26));
}
return $alpha;
}
}
you can then do
toAlphabet($department->id);
//26 will give "AA"
//25 will give "Z"
//19682 will give "ACCA"

htmlagilitypack remove row attributes

How do we remove the inline height attribute from html?
<tr style="height:2px;">
</tr>
<tr style="height:2px;">
</tr>
I want only height attributes to be removed from all tr tags.
Thanks a lot in advance,
You can:
If your trs have no other styles other than height, you can simply remove strip them from their style attribute (the line I commented out)
Otherwise, you can write something like the snippet below to filter which style keys you want to remove
string html = #"<tr style='height:2px;'>
</tr>
<tr style='height:2px;'>
</tr>";
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);
var trs = doc.DocumentNode.SelectNodes("tr");
foreach (var tr in trs)
{
Console.WriteLine(tr.OuterHtml);
//tr.Attributes.Remove("style");
var filteredStyles = GetStyles(tr.GetAttributeValue("style"), "height");
tr.SetAttributeValue("style", string.Join(":", filteredStyles));
Console.WriteLine(tr.OuterHtml);
}
Helper function:
private static List<string> GetStyles(string style, params string[] keysToRemove)
{
List<string> styles = new List<string>();
var stylesKeyPairs = style.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
if (keysToRemove != null)
{
foreach (var styleKeyPair in stylesKeyPairs)
{
var styleKeys = styleKeyPair.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (!keysToRemove.Contains(styleKeys.FirstOrDefault()))
styles.Add(styleKeyPair);
}
}
else
styles.AddRange(stylesKeyPairs);
return styles;
}
Output (for both solutions, in this case):

Razor and Html.DropDownList: Setting selected values for a dropdown for each row in a list?

This seems like it should be simple, but I can't figure out how to make it work.
My data model has a "Server" table, and a "ServerType" table. PKs for both tables are ints, and Server has a field ServerTypeId which is a fk to ServerType.Id.
I have a Razor List.cshtml that is typed to IEnumerable:
#model IEnumerable<Server>
<table border="1">
<tr>
<th>
Server Type
</th>
<th>
Name
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DropDownListFor(modelItem => item.ServerTypeId, (IEnumerable<SelectListItem>)ViewData["ServerType"])
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
</tr>
}
</table>
My controller has:
public ActionResult List()
{
var s = GetServers();
ViewData["ServerType"] = GetServerTypes();
return View("List", s);
}
private List<SelectListItem> GetServerTypes()
{
string id;
SelectListItem si;
List<SelectListItem> sl = new List<SelectListItem>();
IQueryable<ServerType> items = (from t in _entities.ServerTypes select t);
foreach (var item in items)
{
id = item.Id.ToString();
si = new SelectListItem { Value = id, Text = item.Description };
sl.Add(si);
}
return sl;
}
This displays the data, but the value in the dropdown is not selected. I've tried both Html.DropDownList and Html.DropDownListFor, with different permutation of names for the ViewData property, with and without the Id at the end.
Do I need to create a viewmodel that has copies of the ServerType in order to set the Selected property? Or is it a problem because my ids are ints, and the SelectItemList Value property is a string?
For anyone else still looking for he answer. I had to do 3 things to get this to work
User #for instead of #foreach. This is so it has an index to work
with when naming things.
Don't have the ViewBag variable name the
same as the property. It tries to help you out and binds things too
early if they have the same name.
Pass the current value in the constructor for SelectList. My
code ended up as:
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DropDownListFor(modelItem => Model[i].operatorToken,
new SelectList(ViewBag.operatorTokensList, "Value", "Text", Model[i].operatorToken),
"Select", htmlAttributes: new { #class = "form-control" })
...ect...
}
Notice the 4th parameter to SelectList() sets the selected value.
My operatorTokensList was valued with:
new[] { new { Value = ">", Text = ">" },
new { Value = ">=", Text = ">=" },
new { Value = "=", Text = "=" },
new { Value = "<=", Text = "<=" },
new { Value = "<", Text = "<" } };
(The user was selecting "greater than", "greater than or equal", etc.)
At no point in your population of the List in GetServerTypes() do you specify that any of the items are selected. This is something you need to do manually, as MVC3 isn't smart enough to infer it for you in the DropDownListFor method. This is further complicated by the fact that you are not using a single model.
A better way to do this might be:
(Keep in mind in the below code, I'm assuming that the Server class has a primary id called "Id")
For the controller code:
public ActionResult List()
{
IEnumerable<Server> s = GetServers();
ViewData["ServerTypes"] = GetServerTypes(s);
return View("List", s);
}
private Dictionary<int, SelectList> GetServerTypes(IEnumerable<Server> s)
{
Dictionary<int, SelectList> sl = new Dictionary<int, SelectList>();
IEnumerable<ServerType> items = (from t in _entities.ServerTypes select t);
foreach (Server srv in s) {
sl.Add(srv.Id, new SelectList(items, "Id", "Description", srv.ServerTypeId));
}
return sl;
}
For the view code:
(Also note below how the I've corrected the arguments used in the lambda functions)
#model IEnumerable<Server>
<table border="1">
<tr>
<th>
Server Type
</th>
<th>
Name
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DropDownListFor(modelItem => modelItem.ServerTypeId, (IEnumerable<SelectListItem>)(ViewData["ServerTypes"][item.Id]))
</td>
<td>
#Html.DisplayFor(modelItem => modelItem.Name)
</td>
</tr>
}
</table>
#TreyE correctly points out that you never specify that any particular select list item should be selected when displayed in the view.
There are several ways you can do this. First is to use the SelectList object and use its constructor that allows you to pass in the object that should be selected, it's the overload SelectList(IEnumerable, String, String, Object) MSDN SelectList.
SelectList is only supported on .NET 3.5+ though FYI.
Second, in GetServerTypes() you could write:
private List<SelectListItem> GetServerTypes()
{
List<SelectListItem> sl = new List<SelectListItem>();
IQueryable<ServerType> items = (from t in _entities.ServerTypes select t);
foreach (var item in items)
sl.add(new SelectListItem { Value = item.id, Text = item.Description, Selected = item.isSelected } );
return sl;
}
Also remember that only one item should be selected, so make sure that if you do try to use some boolean property it is not possible that more than one item could have its isSelected property set to true.
Alternatively, if you need to use some type of if statement to decide if Selected = true (i.e. your item has no isSelected boolean) then you can add that in the foreach loop.
foreach(var item in items)
{
if //condition
sl.Add(new SelectListItem { Value = item.id, Text = item.Description, Selected = true });
else
sl.Add(new SelectListItem { Value = item.id, Text = item.Description, Selected = false });
}

prototype findElements querySelectorAll error

i'm call the "down" function but am getting an invalid argument using 1.6.1_rc2
here's the html snippet:
<TR id=000000214A class="activeRow searchResultsDisplayOver" conceptID="0000001KIU">
<TD>
<DIV class=gridRowWrapper>
<SPAN class=SynDesc>Asymmetric breasts</SPAN>
<DIV class=buttonWrapper>
<SPAN class=btnAddFav title="Add to Favorites"> </SPAN>
</DIV>
</DIV>
</TD>
</TR>
here's the code:
var description = row.down('span.SynDesc').innerHTML;
row is a dom reference to the element.
prototype is appending a # then the id of the element:
findElements: function(root) {
root = root || document;
var e = this.expression, results;
switch (this.mode) {
case 'selectorsAPI':
if (root !== document) {
var oldId = root.id, id = $(root).identify();
id = id.replace(/[\.:]/g, "\\$0");
e = "#" + id + " " + e;
}
results = $A(root.querySelectorAll(e)).map(Element.extend); <-- e = "#000000214A span.SynDesc"
root.id = oldId;
return results;
case 'xpath':
return document._getElementsByXPath(this.xpath, root);
default:
return this.matcher(root);
}
i get an "invalid argument" error?
if i put a breakpoint before the offending line and change e to be equal to "span.SynDesc" it works fine.
help. :)
I ran into this. Changing the TR's ID to start with a letter should fix the problem. It turns out that legal HTML IDs match /^[A-Za-z][A-Za-z0-9_:.-]*$/.

Resources