<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<StudentInfo>>" %>
<% int i = 0; %>
<% foreach(var e in Model){%>
<div>
<% if(i==0) { %>
<% Html.RenderAction("student", "home", new { #et = e}); %>
<% break;
} %>
<div>
<span>
<% Html.RenderAction("studentDetails", "home", new { #et = e }); %>
</span>
</div>
</div>
<%i++; } %>
Here my intension was to execute Renderction Student only once and Studentdetails should be multiple times.
But int value is always taking i =0 bec each time page is loading its considering 0 always.
Can anybody tell me how to do this?
but int value is allways taking i =0 bec each time page is loading its considering 0 allways.
That's how it works. That's how pretty much all web platforms work. Every time your page code you're working a new instance of the object. Once a page request is fully rendered, anything used to build that request that you haven't explicitly saved somewhere like the Session is disposed.
Related
Hi I'm using silverstripe 2.4.7 and I'm having difficulty getting the pagination to work. I've created a function in my page.php to get the latest articles like so
function AllNewsPosts($num=1) {
$news = DataObject::get_one("NewsHolder");
return ($news) ? DataObject::get("NewsEntry", "ParentID > 0", "Date DESC", "", $num) : false;
}
Then when i put this function into the control and pagination tags one article shows up however the links to the concurrent articles do not work - essentially the pagination is not working and I'm not sure how to fix it
<% if AllNewsPosts %>
<% control AllNewsPosts %>
<div class="event">
<h2>$MenuTitle |<span class="date"> $Date.Time $Date.Long</span></h2>
<p>$Content.FirstParagraph</p>
See more about this event
</div>
<% end_control %>
<% else %>
<div class="no-entry">'There are no entries'</div>
<% end_if %>
<% if AllNewsPosts.MoreThanOnePage %>
<div id="PageNumbers">
<p>
<% if AllNewsPosts.NotFirstPage %>
<a class="prev" href="$AllNewsPosts.PrevLink" title="View the previous page"><span class="yellow-background">Prev</span></a>
<% end_if %>
<span>
<% control AllNewsPosts.PaginationSummary(0) %>
<% if CurrentBool %>
<span class="current">$PageNum</span>
<% else %>
<% if Link %>
$PageNum
<% else %>
…
<% end_if %>
<% end_if %>
<% end_control %>
</span>
<% if AllNewsPosts.NotLastPage %>
<a class="next" href="$AllNewsPosts.NextLink" title="View the next page"><span class="yellow-background">Next</span></a>
<% end_if %>
</p>
</div>
<% end_if %>
Any help is much appreciated
Note: The following answer is for Silverstripe 2.4. This should not be used for Silverstripe 3.0+ sites. From 3.0 and onwards the PaginatedList object makes pagination much easier.
You are not setting a limit on how many entries to retrieve in your query, or where to start from.
The following tutorial explains how to apply pagination to a set of data objects exactly as you are trying to do:
http://www.ssbits.com/tutorials/2010/paginating-a-filtered-dataobjectset/
Here is an attempt at altering your function to include limit and start as needed for pagination:
PHP
function AllNewsPosts() {
if(!isset($_GET['start']) || !is_numeric($_GET['start']) || (int)$_GET['start'] < 1)
{
$_GET['start'] = 0;
}
$SQL_start = (int)$_GET['start'];
$newsEntries = DataObject::get('NewsEntry', '', 'Date DESC');
$doSet = new DataObjectSet();
foreach ($newsEntries as $newsEntry) {
if ($newsEntry->canView()) {
$doSet->push($newsEntry);
}
}
$doSet->setPageLimits($SQL_start, 10, $doSet->Count());
return $doSet;
}
Note the above will display 10 items per page. You can change this to however you need per page.
I am using the DataObjectsAsPage module. It returns a Datalist ($Items) to the holder page which loops through each $Item. I am also trying to develop a partial caching strategy for the page. I read in the docs that you cannot place cache blocks inside of a loop, so in my DataObjectsAsPageHolder Page, I have the following:
<% cached 'items', LastEdited, CacheSegment %>
<% loop $Items %>
$Me
<% end_loop %>
<% end_cached %>
I checked the silverstripe-cache/cache directory and this seems to be caching the $Items list.
The problem is that I have added a DataExtension to each $Item that allows the admin to set whether or not an $Item is viewable based on the CurrentMember's group. So within each $Me template I have the following:
<% if HasAccess %>
<% end_if %>
I have two issues:
Given the cache key above, if an authorized member is the first to view a page, then the page gets cached and exclusive material gets shown to non-members in subsequent page views.
If I adjust the cache key to the following:
<% cached 'items', Items.max(Created), CacheSegment unless CurrentMember %>
<% loop $Items %>
$Me
<% end_loop %>
<% end_cached %>
Then the content in each $Me template is never cached for members - which is the largest portion of my sites viewers.
Is there a way I can cache the $Items list for members and non-members and still be able to use the HasAccess check on $Item within the loop?
The simplest solution is probably to add the current member's ID to the cache key.
<% cached 'items', LastEdited, CacheSegment, CurrentMember.ID %>
<% loop $Items %>
$Me
<% end_loop %>
<% end_cached %>
However, this will cache the block uniquely for each registered member. To make the caching a little more useful, you should use the current member's groups in the cache key. Unfortunately, there's no easy way that I know of to get a template cache key ready list of groups for a member.
The easiest way to get a round this issue is probably to add a GroupsCacheKey function to your Page class. It's a bit of a dirty solution, but it should work effectively.
// Untested function
public function GroupsCacheKey() {
if ($member = Member::currentUser()) {
return implode('', $member->Groups()->map('ID', 'ID')->toArray());
}
return 'nonmember';
}
Then in your template:
<% cached 'items', LastEdited, CacheSegment, GroupsCacheKey %>
<% loop $Items %>
$Me
<% end_loop %>
<% end_cached %>
There is a better solution than this out there somewhere, but it will work.
I am staring at the worst mess, my monitor isn't tall enough to see what is going on, and VS 2010 is no help at all.
I do not have any idea how to refactor this trash.
It's 11 AM and I feel like pouring a drink already.
Is this just the final proof that I have no business coding? Be honest.
<div id="followedFriends">
<% if (Model.FollowedFriends.Count() > 0)
{
foreach (var friend in Model.FollowedFriends)
{ %>
<div id="friendContainer">
<div class="followedFriend">
<div class="userName">
<%= Html.ActionLink(friend.FoFriend.UserName, "Visitor", "Home", new {userID = friend.FoFriend.UserId}, null)%></div>
Currently reading:
<br />
<div class="bookLinks">
<% if (friend.BookCount != 0)
{ %>
<% if (friend.BookCount <= 5)
{ %>
<%= friend.BookLinks%>
<%}
else
{ %>
<%:Html.ActionLink(friend.BookCount + " different books.", "Visitor", "Home", new {userID = friend.FoFriend.UserId}, null)%>
<%}
}
else
{ %>
Nothing, it appears...
<%}%>
</div>
<%if (friend.ReviewCount != 0)
{%>
New review for:
<div class="reviewLinks">
<%if (friend.ReviewCount <= 5)
{ %>
<%= friend.ReviewLinks%>
<%}
else
{%>
<%: friend.ReviewCount %>
different books
<%}%></div>
<%}
if (friend.QuoteCount != 0)
{%>
<span class="highlight">▸</span>
<%: friend.QuoteCount%>
new
<%if (friend.QuoteCount != 1)
{ %>quotes
<%}
else
{ %>
quote
<%} %>
<%}%>
</div>
</div>
<%}
}%>
</div>
<%} %>
Update
Since someone was asking, here is the pertinent portion of the View Model:
public class FollowedFriend
{
public aspnet_User FoFriend { get; set; }
public string BookLinks { get; set; }
public int BookCount { get; set; }
public string ReviewLinks { get; set; }
public int ReviewCount { get; set; }
public int QuoteCount { get; set; }
public FollowedFriend(Guid userID, DateTime lastVisit)
{
using (var context = new BookNotesEntities())
{
FoFriend = context.aspnet_Users.SingleOrDefault(u => u.UserId == userID);
var reading = context.Books.Where(b => b.UserID == userID && b.CurrentlyReading == true).ToList();
BookCount = reading.Count;
if (BookCount <= 5)
BookLinks = Book.ConvertBooksToLinks("Book/Details", reading);
else
BookLinks = "";
var recentBooks = context.Books.Where(b => b.UserID == userID
&& b.Review.DateCreated >= lastVisit).OrderByDescending(b => b.DateCreated).ToList();
if (recentBooks.Count <= 5)
ReviewLinks = Book.ConvertBooksToLinks("/Book/Details", recentBooks);
else
ReviewLinks = "";
ReviewCount = recentBooks.Count;
QuoteCount = context.Quotes.Count(q => q.UserID == userID && q.DateCreated >= lastVisit);
}
}
}
Let's start with a maxim that was initially proposed by Rob Conery: if there's an embedded "if" somewhere in your View, it's probably an indicator that you should either A.) create an HtmlHelper or B.) retract the logic from the view and insert it into your ViewModel.
By doing so, you can clean up your View to look something like this (you get the idea):
<div id="followedFriends">
<% foreach (var friend in Model.FollowedFriends) { %>
<div id="friendContainer">
<div class="followedFriend">
<div class="userName">
<%: Html.ActionLink(friend.FoFriend.UserName, "Visitor", "Home", new {userID = friend.FoFriend.UserId}, null)%>
</div>
Currently reading:
<br />
<div class="bookLinks">
<%: Html.DisplayBooklinks(friend) %>
</div>
<div class="bookReviews">
<%: Html.DisplayBookReviews(friend) %>
</div>
<div class="bookQuotes">
<%: Html.DisplayQuotes(friend) %>
</div>
</div>
</div>
<% } %>
</div>
At this point, if there's any chance that this piece of UI might be consumed in some other page, you should consider throwing it into a user control. By doing so, your view could now look something like this:
<% Html.RenderPartial("FriendDetails", Model.FollowedFriends); %>
Ultimately, whenever your View starts to look like soup, it's because your view is doing too much thinking. Which, in turn, means: some other layer of your application isn't thinking enough. By pinpointing logic in your Views, and by determining what abstractions might aid you in your attempt to remain DRY, your Views will become much more readable and much more maintainable.
I would factor some of that inline logic to an HtmlHelperExtension method and then create a DisplayTemplate "Friend"
<div id="followedFriends">
<% if (Model.FollowedFriends.Any()) {
foreach (var friend in Model.FollowedFriends) {
<%=Html.DisplayFor(friend) %>
<% } %>
<% } %>
</div>
And your DisplayTemplate "Friend" would look something like this:
<div id="friendContainer">
<div class="followedFriend">
<div class="userName">
<%= Html.ActionLink(friend.FoFriend.UserName, "Visitor", "Home", new {userID = friend.FoFriend.UserId}, null)%></div>
Currently reading:
<br />
<%=Html.BookInformation(friend) %>
<%=Html.BookReview(friend) %>
<%=Html.Quotes(friend) %>
</div>
</div>
</div>
Rather than making HtmlHelperExtensions you could just create more DisplayTemplates and specifically call them: DisplayFor("BookReview", friend)
Let me start by saying, we've all been there...
For your particular problem, I'd consider a few of the following "tools":
Partial Views: when you have a lot of nested <div> tags, for instance, you may want to consider creating some partial views where appropriate.
Use the Controller for Business Logic: Some of your logic should be done in the controller, where it's easier to break things up and call different helper methods to apply the logic. Consider passing to your view a custom object (a ViewModel if you will) that has all the business logic parsed. For example, things like displaying "quote" vs "quotes" could be done in a ViewModel object's "DisplayValue"-type property.
HTML Helpers: These little extensions can help take care of extracting the many if/else statements that you have into an easily readable method - and they could be re-used on other pages if written generally enough.
Lastly, I'd say, go get some fresh air and come back with only one thing in mind: simplify! Often we get caught up with a long list of tasks that need to be done and we get lost in our own soup or spaghetti or what-have-you. You'll need to approach the code with only refactoring in mind, until it's clean enough that you can tell where the next piece of logic should go.
I'm new to ASP.Net MVC, so I try to find coding conventions for me, too:
write <% %> tags in a separate line (except for <%: and <%=)
eliminate sequences of %><% (except for <%: and <%=)
keep C# and HTML indentation separate
indent <%= and <%: as if they where HTML tags (if they do represent HTML tag/s)
I'm not a ASP expert, but like in PHP isn't there a way to echo things? In place of using so much open and close tags? I should place everything above the code, and make variables that you use in your HTML?
In my opinion, this isn't awful. I think the key here would be to try to spend some time walking through it and commenting where blocks start and then their corresponding end tags. It'll be painful now, but it'll save you some time later on.
I'm assuming you're looking for a code review. In that case:
remove the empty ELSE block with the comment "nothing, it appears"
Combine the nested IF conditions into a single check IF( count != 0 && count < 5 )
I am building a .NET MVC app that has a page with a list of delete buttons, one for each item in a list. The problem I'm having is that the foreach variable "item" is not visible inside the LoginView, which results in the following error:
Compiler Error Message: CS0103: The name 'item' does not exist in the current context
Below is a simplified version of the view. The error occurs at the "new {id=item.Id}" in the LoggedInTemplate - the reference to "item" in the ActionLink works fine:
<% foreach (var item in Model) { %>
<%= Html.ActionLink("Item", "Details", new { id = item.Id })%>
<asp:LoginView runat="server">
<LoggedInTemplate>
<% using( Html.BeginForm( "Delete", "Items", new {id=item.Id}, FormMethod.Post))
{ %>
<input type="submit" value="Delete" runat="server" />
<% } %>
</LoggedInTemplate>
</asp:LoginView>
<% } %>
To clarify the problem is not that the Model has not been successfully passed to the View. The Model is visible from both inside and outside the LoginView. The foreach loop as no problem in iterating through the items in the Model (which is a List). The problem is that the iteration variable "item" is not accessible from within the LoginView - though the original Model is.
Is there any way to pass "item" through to the LoginView's templates? Or is building LoginViews within a foreach loops the wrong way of doing things?
Is there a scoping rule that prevents using local variables within controls - perhaps because the control is rendered at a different time to the main page?
With ASP.NET MVC you really shouldn't use user/custom controls, so if you omit the <asp:LoginView/> and write a line of code to check if the user is authenticated, you are good to go.
Instead of your current code:
<asp:LoginView runat="server">
<LoggedInTemplate>
<div>Show this to authenticated users only</div>
</LoggedInTemplate>
</asp:LoginView>
Just use an if-statement and the value of Request.IsAuthenticated:
<% if (Request.IsAuthenticated) { %>
<div>Show this to authenticated users only</div>
<% } %>
Are you passing the Model to the view and are you also inheriting from the model within that view?
So if this is a View then in your C# code you need to return the list of items like return View(listofitems);
If this is a partial view then <% Html.RenderPartial("MyPartial", listofitems) %>
And in the view you need to
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IQueryable<ListOfItems>>" %>
If all that is in place then it should work no probs.
<% foreach (var item in Model) { %>
<%= Html.ActionLink("Item", "Details", new { id = item.Id })%>
<%= if( Request.IsAuthenticated ) {
using( Html.BeginForm( "Delete", "Items", new {id=item.Id}, FormMethod.Post))
{ %>
<input type="submit" value="Delete" runat="server" />
}
} %>
<% } %>
There is no need to use the LoginView, its not really giving you anything. Use something like the above instead.
Alternatively, you can move the decision of whether to show the delete option for the specific item into the controller, so instead of doing if( Request.IsAuthenticated ) you would do if( item.ShowDelete ) ... assuming item's type is a view model. Another option is to use an extension method for the same, item.ShowDelete(). I prefer the earlier, because there might be logic associated to deciding whether to show delete for a given item, so its better to not have it in the controller or a related logic.
I was wondering if it's possible to render an Html Helper in a View inside a codeblock. So instead of:
<% = Html.TextBox("sometextbox", "somethingelse") %>
I want to do:
<%
switch(SomeParameter)
{
case "blah":
Html.TextBox("sometextbox", "somethingelse")
break;
}
%>
And have this render. Of course as it is, it wont render, so is there a way to programically decide if a textbox can be added without having to have a million delimiters in the page to accomplish this?
Thanks in advance!
<%
switch(SomeParameter)
{
case "blah":
%><%=Html.TextBox("sometextbox", "somethingelse")%><%
break;
}
%>
<%= %> is just a shorthand notation for Response.Write() though so the following should work too.
<%
switch(SomeParameter)
{
case "blah":
Response.Write(Html.TextBox("sometextbox", "somethingelse"));
break;
}
%>
All the HtmlHelpers return a string and don't output to the response stream directly by design.
Is this what your looking for?
<% switch (SomeParameter)
{
case "blah": %>
<%= Html.TextBox("sometextbox", "somethingelse") %>
<% break;
} %>