Access a controller function when inside a loop (ChildrenOf) - silverstripe

I have a page type (ProducerReport) which acts like a summary page - it gets data from individual pages (Shows) and lists them all on one page with the main info from each article.
Using ChildrenOf() made it really easy and simple, I thought wow SilverStripe will do all the work for me all I have to do is structure and style it!
But then tragedy struck...
In Shows I have a DataObject linked by $has_many which allows users to add key people such as contacts etc for each individual article, this is done via GridField (ShowsContacts).
At first I kind of assumed that simply adding the necessary variables would get the data from ShowContacts - this didn't work.
Then in the view I took a shot in the dark and tried using $ID which actually worked and returned the ID of the post.
So I went ahead I added this into the ProducerReport controller which I hoped would get the job done, allowing me to perform a query to get the relevant contacts and loop it within in the ChildrenOf loop.
However, the controller doesn't do anything while in the loop. The only time it outputs anything is when I put the outside of the loop.
ProducerReport.php
class ProducerReport_Controller extends Page_Controller {
# Get the Show Contacts for the Show, based on ShowsID
public function something($SiteID){
# Needs to be cast to int as param comes in as string
$x = (int)$SiteID;
var_dump(ShowsContact::get()->find('ShowID', $x)->Role);
}
}
ProducerReport.ss
<div class="producer-report">
<% loop ChildrenOf(current-shows).sort('PercentageComplete', 'DESC') %>
<div class="show">
<div class="banner">
<% if $ReportImage %>
$ReportImage
<% else %>
<img src="/assets/_placeholders/producer_report_cover.png" />
<% end_if %>
<h2>$Title <span>($ProjectCode)</span></h2>
</div><!-- . banner -->
<a class="hub-link" target="_blank" href="http://website.com?job=$ProjectCode">Hub</a><!-- . hub-link -->
<div class="stats">
<h3>Show Statistics</h3>
<dl>
<dt>Client</dt>
<% if $Client %>
<dd>$Client</dd>
<% else %>
<dd>None set</dd>
<% end_if %>
</dl>
<dl>
<dt>Percentage Complete</dt>
<% if $PercentageComplete %>
<dd>$PercentageComplete%</dd>
<% else %>
<dd>-</dd>
<% end_if %>
</dl>
</div><!-- . stats -->
</div><!-- . show -->
<!-- Here I need to retrieve info of the contacts belonging to the page -->
<!-- Inside the ChildrenOf loop, this DOESNT output anything -->
$something($ID)
<% end_loop %>
<!-- This outside the loop DOES output a job role -->
$something(84)
</div><!-- . producer report -->
EDIT - Additional code
This here is what the ProducerReport gets data from, all direct data for this Model appears in that ChildrenOf loop; The ShowsContact data isn't accessible and if I try to query using the $something($ID) functionality it doesn't work when used inside the loop.
Shows.php
class Shows extends Page {
private static $db = array(
'ProjectCode' => 'Varchar(4)',
'Client' => 'Varchar(255)',
'PercentageComplete' => 'Int'
);
private static $has_one = array(
'ReportImage' => 'Image'
);
private static $has_many = array(
'ShowsContacts' => 'ShowsContact'
);
public function getCMSFields(){
# GridField / Show Contacts
$conf = GridFieldConfig_RelationEditor::create();
$gridField = new GridField('ShowsContact',
'Show Contact List',
$this->ShowsContacts(), $conf);
$fields->addFieldsToTab('Root.Content.ShowContact', array(
$gridField
));
return $fields;
}
}
class Shows_Controller extends Page_Controller {
# Get key people from ShowsContact class // input via ShowsContact GridField
# THIS HERE is the data that I need displayed on ProducerReport
public function getKeyPeople(){
if($this->ShowsContacts()->exists()){
$result = new ArrayList();
foreach($this->ShowsContacts()->column('MemberID') as $teamMemberID){
$member = Member::get()->byID($teamMemberID);
$result->add(new ArrayData(array(
'PictureURL' => $member->ImageURL,
'Role' => $this->ShowsContacts()->find('MemberID', $teamMemberID)->Role,
'Firstname' => $member->FirstName,
'Surname' => $member->Surname,
'Nickname' => $member->Nickname,
'Email' => $member->Email,
'Ext' => $member->Extension,
'Site' => Site::get()->byID($member->SiteID)->Title
)
));
}
return $result;
}
else
return null;
}
}
My question is - how would I get data from another page type as well as data that's linked to it by relationship if I am unable to do a loop within a loop - or am I doing something wrong?

AHHHH!! omfg!!!! Okay, so after taking many many shots at this issue I found out how to get this working.
The solution to this is to use $Top.[Method] inside the loop. I assume its because SilverStripe is doing it's own thing within the loop and for whatever reason becomes a bit blind. From what I understand - you have to tell the method to go outside the loop in order for it to see / use the Controller
Going from the code above in ProducerReport.ss and trimming it down for relevance to the question...
<% loop ChildrenOf(current-shows).sort('PercentageComplete', 'DESC') %>
...
$Top.something($ID) <!-- works perfectly, ID is passed. Output is as expected!
...
# Or when in a loop it'll be like so...
<% loop $Top.something($ID) %>
# ArrayList / ArrayData output...
<% end_loop %>
<% end_loop %>
I can't believe this took me so long to find a solution!
May anyone who gets stuck on something like this or anything similar find their way to this answer because I was honestly about to give up and rewrite everything for this page type and use queries to get all the data to put in the template...
Edit / Note: I've found that if you have a method called getUserName (for example) you cannot omit the 'get' when using it with $Top ... so in the template you need to put $Top.getUserName when using it

Not sure it's relevant if you think you've fixed your problem, but the something controller method (is that even used?) will never dump what you think it will as any DataObject::get() or DataList::create() ORM calls always return an instance of DataList, not an individual DataObject.
If you're trying to see what SS has from that query, try using ->first() instead, which as you might guess, returns the first DataObject (or DataObject subclass) in your list:
...
var_dump(ShowsContact::get()->find('ShowID', $x)->first()->Role);

For your template you could do this:
ProducerReport.ss
<% loop Shows %>
<% loop ShowsContacts %>
<%-- contact details here --%>
<% end_loop %>
<% end_loop %>
Where 'ShowsContacts ' is the name of the relation from shows to contacts

Related

Adding Recent post in silverstripe blog

I am working with silverstripe with first time and I almost create a blog in silverstripe but now I stuck at one place where I need help you guys. If anybody have any idea about it then please help me.
I am trying to add recent posts in my blog. I am using the below code for this
public function latestBlog($num=10) {
/* return BlogEntry::get()->sort('PublishDate','desc')->limit($num); */
echo $blogPosts;
return $blogPosts = BlogPost::get()->filter('ParentID', $this->ID)->limit($num);
}
And in my ss page I am using html like this
<% loop $latestBlog %>
<li>$Title</li>
<% end_loop %>
this gives me titles of the each post but in href I want url too
If my title is "TEST POST" then I want href like "www.mydomain.com/TEST-POST";
Can Anybody have Idea about it?
You can use $Link which will return the relative url. Reference https://docs.silverstripe.org/en/3.2/developer_guides/templates/common_variables/#links
<ul>
<% loop $latestBlog %>
<li>$Title</li>
<% end_loop %>
</ul>

setLeftTitle in Silverstripe 3.1

I have just jumped up the Silverstripe bandwagon.
I have been trying to get the following effect of static text in front of a text field on the getCMSfields form:
Telephone number: +36 [__________]
where "telephone number:" is obviously the field title (which can be altered via ->setTitle() and +36 is a static prefix prepended to the left of the input field.
I have been trying with ->setLeftTitle('+36') but it does not seem to have any effect.
BTW. ->setRightTitle('something') - which I expected to append a label to the right of input field, works identically to ->setDescription().
I'm confused. So is there any way to achieve what I want?
Use the FieldGroup class
public function getCMSFields($fields) {
$fields = parent::getCMSFields($fields);
$fields->addFieldToTab('Root.Main',
FieldGroup::create(
new HeaderField('+36'),
new TextField('PhoneNumber','')
)->setTitle('Telephone number')
);
return $fields;
}
Personally I'd use two TextFields for cosmetic reasons, and enforce some validation on the TelephonePrefix.
The default template does not render the left title. To do this, you will probably need to create a custom form field template (called FormFieldLeftTitle.ss) or something, and include $LeftTitle in the appropriate spot:
<div id="$Name" class="field<% if $extraClass %> $extraClass<% end_if %>">
<% if $Title %><label class="left" for="$ID">$Title</label><% end_if %>
<div class="middleColumn">
$LeftTitle
$Field
</div>
<% if $RightTitle %><label class="right" for="$ID">$RightTitle</label><% end_if %>
<% if $Message %><span class="message $MessageType">$Message</span><% end_if %>
<% if $Description %><span class="description">$Description</span><% end_if %>
</div>
You can then call $myFormField->setTemplate('FormFieldLeftTitle') to use this template.

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 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