Silverstripe: Template behavior of "Up" - silverstripe

In Silverstripe templating, the $Up tag temporarily breaks out of the current loop/with and gives access to the parent scope. While the following template code works, I don't understand why.
$Checked is a list with objects that I loop, in itself containing a list with $Items of which I need to show the first. The object also has an Owner, which I need to access while in the scope of the first item.
I've got the following silverstripe template code:
<% loop $Checked %>
<% with $Items.First %>
<article class="checked-statement">
<span class="statement-outcome h-bg-true-$Verdict.Value">$Verdict.Name</span>
<div class="checked-statement__statement">
<a href="xxx" class="checked-statement__picture"><img
src="$Up.Up.Owner.Photo.CroppedImage(160,160).Url" alt="$Up.Up.Owner.Name.XML" class="h-full-rounded"/></a>
$Up.Up.Owner.Name zei
<h3>
$Up.Up.Title
</h3>
</div>
<a href="yyy" class="checked-statement__argument">
$Up.Up.Statement…
$Top.Icon('chevron-right')
</a>
</article>
<% end_with %>
<% end_loop %>
I would think I would need only one "Up" to get to the scope of $Checked. But I need two, which is strange, as the first Up should break out of the "with", and the second one should break out of the loop, giving me the top level scope, in which the specific item is not available...
Can anyone point me at the fault in my reasoning, or code?

At some point, I believe it was with 3.0 or 3.1, the logic behind $Up changed.
Previously <% with $Items.First %> or back in 2.x <% control $Items.First %> was just one scope level that you could break out of with $Up.
These days, each object/method call/property gets it's own scope. This means:
<% with $Foo.Bar %>Foo's ID: $Up.ID<% end_with %>
<% with $Foo.Bar %>Outside of "with" ID: $Up.Up.ID (== $Top.ID in this case)<% end_with %>
And for deeper nesting you need even more ups:
<% with $Foo.Bar.X %><% loop $Y %>$Up.Up.Up.Up.ID == $Top.ID<% end_loop %><% end_with %>

Related

SilverStripe arithmetic in template

I want to do a simple arithmetic operation in a .ss template.
<% loop $Images %>
<img src="$Link" alt=""/>
<% $Pos == 4 %>
and {$TotalItems - 4} more foto's
$Break
<% end_if %>
<% end_loop %>
For example I would want to output
and 10 more foto's
But the best I can get is
and 14 - 4 more foto's
I know I can make a function, which works for now, but can I do arithmetic operations in the template?
You could do it like this.
At first you limit the images to the amount you would like to display. After that you loop over the same set with an offset of x (4) and check if there's more. If so, output the amount of remaining images.
<% loop $Images.Limit(4,0) %>
<img src="$Link" alt=""/>
<% end_loop %>
<% if $Images.Limit(9999,4) %>
and $Images.Limit(9999,4).Count more foto's
<% end_if %>
the code is untested but should work.
Edit
I think "real" arithmetic is not possible by default. You'll need to write a custom function to do this.

Using article frontmatter when iterating in Middleman blog

Not the best title, but I'm honestly not sure on how to properly explain what I'm looking for help for.
So I'm using Middleman blog to well create my blog. Anyways, I'm using frontmatter to pass css that change the look of each page individually. I'm using 4 variables, link_color, text_color, bg_link. So what I want to do is reuse that same frontmatter information in the layout.html.erb file.
So the layout.html.erb is the standard
<% if paginate && num_pages > 1 %>
<p>Page <%= page_number %> of <%= num_pages %></p>
<% if prev_page %>
<p><%= link_to 'Previous page', prev_page %></p>
<% end %>
<% end %>
<% page_articles.each_with_index do |article, i| %>
<li class="article_summary">
<h1><%= link_to article.title, article, id: "#{i}" %></h1>
</li>
<% end %>
<% if paginate %>
<% if next_page %>
<p><%= link_to 'Next page', next_page %></p>
<% end %>
<% end %>
What I'm trying to do is for each article within that iterator is if the article has bg_color frontmatter then use that and change the color of the article.title if not, then do nothing. Currently if I try with something like:
<style>
<% if article.data.bg_color? %>
.article_summary a#<%= i %>{
color: rgb(<%=article.data.bg_color %>);
}
<% end %>
</style>
I'm doing it this way because my blog lives on Github.
Currently it works, but since it's just a simple iteration it gives every article that same color and not on a per article basis. So I'm trying to figure out the best way to utilize the index as some sort of id so that they're targeted individually.
Perhaps changing the li from a class to an id consisting of the index, but then I won't be able to apply a global style from the scss in the stylesheet folder no?
I've found a dirty method that works.
<% page_articles.each_with_index do |article, i| %>
<li class="article_summary" id="test_<%=i %>">
<h1><%= link_to article.title, article %></h1>
<style>
<% if article.data.bg_color? %>
#test_<%=i%> a{
color: <%=article.data.bg_color %>;
}
<% end %>
</style>
</li>
<% end %>
Pretty much added "test_" to the id (before I was just doing the index itself) and viola!

Partial Cache Members

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.

Silverstripe: Excluding current page from list of the parent's children

Using Silverstripe's "ChildrenOf" syntax, I've been successfully able to list all children of a page's parent. It's being used in a "see also" style list on a page.
I'd like to exclude the current page from the list but unsure how to determine which is the same as the current page, as within the control loop I'm in the parent's scope. Any ideas? Here's a pseudocode of what I'm doing:
<% control ChildrenOf(page-url) %>
<!-- Output some stuff, like the page's $Link and $Title -->
<% end_control %>
there's a built-in page control for this, so to exclude the current page from your list:
<% control ChildrenOf(page-url) %>
<% if LinkOrCurrent = current %>
<!-- exclude me -->
<% else %>
<!-- Output some stuff, like the page's $Link and $Title -->
<% end_if %>
<% end_control %>
see http://doc.silverstripe.org/sapphire/en/reference/built-in-page-controls#linkingmode-linkorcurrent-and-linkorsection
UPDATE
as you mentioned in your comment below that you'd like to use the $Pos control, you need to filter the dataobjectset before iterating over it.
add the following to your Page_Controller class:
function FilteredChildrenOf($pageUrl) {
$children = $this->ChildrenOf($pageUrl);
if($children) {
$filteredChildren = new DataObjectSet();
foreach($children as $child) {
if(!$child->isCurrent()) $filteredChildren->push($child);
}
return $filteredChildren;
}
}
then replace 'ChildrenOf' in your template by 'FilteredChildrenOf':
<% control FilteredChildrenOf(page-url) %>
//use $Pos here
<% end_control
In Silverstripe 3.1 you can use a method like this -
<% loop $Parent.Children %>
<% if $LinkingMode != current %>
<!-- Output some stuff, like the page's $Link and $Title , $Pos etc -->
<% end_if %>
<% end_loop %>
This way you can list all parent's children pages.
See https://docs.silverstripe.org/en/3.1/developer_guides/templates/common_variables/

silverstripe function to return menu level

I'm trying to write a function that returns what menu levels are visible on the page...at the moment I'm using <% if %> statements in the template, ie:
<div class="<% if Menu(1) %>navA<% end_if %> <% if Menu(2) %>navB<% end_if %> <% if Menu(3) %>navC<% end_if %>">...</div>
Which, if there are 3 menu levels on a page, returns <div class="navA navB navC">
What I want is a function that returns just the lowest level menu on the current page, ie <div class="navC">
Thanks
that's perfectly possible.
just add the following to your Page_Controller class:
function LowestLevel() {
$i = 1;
while($this->getMenu($i)->count() > 0) $i++;
return 'level'.($i-1);
}
now you can call it in your template like so:
<div>lowest level: $LowestLevel</div>
$LowestLevel will print 'level1', 'level2' etc.
in case your class names have to be like 'navA', 'navB'... you need to do some matching like 'level1'->'navA', which shouldn't be too hard - come back to me if you need any help on this.
What about the following (untested):
<div class="<% if Menu(3) %>navC<% else_if Menu(2) %>navB<% else %>navA<% end_if %>">...</div>
You might want to consider using some custom code in the Controller for logic-heavy stuff, but this should get you going...

Resources