SilverStripe GroupedList with has_one data - silverstripe

I'm banging my head trying to figure this out and I feel like I'm over thinking it.
So I've built 2 DataObjects (Brand, Product) and 1 Controller/Page (ProductType).
ProductType has many Products
Product has one Brand
Product has one ProductType
Brand has many Products
What I'm looking to do is:
ProductType pages should make available a list of Brands that are used ONLY by the Products of that Type.
Now I have one somewhat working but it feels hacky and not very portable. Example below:
ProductType Controller:
public function getBrands() {
if($products = $this->Products()) {
$group = GroupedList::create($products->sort('BrandID'))->GroupedBy('BrandID');
}
}
Product Type Template:
<% if Brands %>
<ul>
<li>All</li>
<% loop Brands %>
<% loop Children.Limit(1) %>
<li>$Brand.Title</li>
<% end_loop %>
<% end_loop %>
</ul>
<% end_if %>
Is there a way I can build a method in the ProductType controller that would return a DataList of just the Brands used by Products of that type?
Using Silverstripe 3.1.3
Let me know if I need to be more clear about something and thanks!

Ah, i guess you had a misunderstanding of a GroupedList
You already have the Datalist of all products of this product type.
It's the has_many relation
$this->Products()
now you want to group the current products by BrandID. Your method naming might be confusing, so let's rename it a bit:
public function getProductsGroupedByBrands() {
if($products = $this->Products()) {
$group = GroupedList::create($products->sort('BrandID'))
->GroupedBy('BrandID');
}
}
So in your template you have a GroupedList you can loop over.
<% if Products %>
<ul>
<li>All</li>
<% loop ProductsGroupedByBrands %>
<li>
<%-- here you should be able to see the grouping relation --%>
$Brand.Title
<%-- in doubt use the First element to get the current Brand, it's a has_one -->
<% with $Children.First %>
$Brand.Title
<% end_with %>
<ul>
<% loop Children %>
<%-- here are the products grouped by brand --%>
<li>$Title</li>
<% end_loop %>
</ul>
</li>
<% end_loop %>
</ul>
<% end_if %>
See also Silverstripe Docs or Grouping Lists

Related

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!

Creating article pagination

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.

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.

Using a page variable as an if statements conditon within a control

I have the following problem:
I have the variable $GiftID on my page.
I want to cycle through all of my gift objects using my function getGifts().
When the $ID of the gift is equal to the $GiftID of the page then I want something to happen.
Here is an example of my code:
$GiftID
<% control getGifts %>
<% if CurrentPage.GiftID = ID %>This is it!<% end_if %>
<% end_control %>
Using $CurrentPage.GiftID works when printing inside the control, but how on earth do I access it from within the if statement?
I am using SS 2.9
I have not used ss2.9 yet, but as far as I know you can not do <% if Top.GiftID = ID %> in any 2.x version, you can not compare 2 variables, you can only compare with static vaules. (but it is possible in 3.0)
So you have to do it on php side, if you want to only display the slected gift object, then:
if GiftID is actually the DB field for the has_one relation of Gift then you can just do <% control Gift %> and it will scope the Gift object with the GiftID
If you really have GiftID saved as DB field or otherwise, then can do
public function getGift() { return DataObject::get_by_id('Gift', $this->GiftID); }
both ways you can do <% control Gift %> and it will scope it
If you want to list all gifts and mark the current gift then you need to do it on php side (foreach the set of objects and set a flag on the current object)
You should be able to access the current page with Top:
<% control getGifts %>
<% if Top.GiftID = ID %>This is it!<% end_if %>
<% end_control %>

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/

Resources