I'm trying to pull out enum values from a dataobject to act as a menu/filter. I have not been able to find any documentation on how to do this and my attempts have all failed.
For example I have:
class Specification extends DataObject {
private static $db = array (
'Standard' => 'Enum("BS 1400,AS 1565")'
);
}
I'm trying to do something like:
public function Standards() {
$stnds = Specification::get()->dbObject('Standard')->enumValues();
$list = ArrayList::create();
foreach ($stnds as $stnd) {
$list->push($stnd);
}
return $list;
}
I want to be able to loop the resulting values in the template, but can't access the labels - if I do:
<% loop Standards %>
$Pos
<% end_loop %>
This gives me 1 2, which suggests it is working, but cannot access the enum value labels in the template.
How do I correctly return these values so they can be looped through in the template?
When you push items into an ArrayList object, they are just stored inside a php array. The SS template parser does not deal with php arrays, therefore one solution to your issue is to wrap your item inside an ArrayData before pushing it, like below:
public function Standards(){
$stnds = Specification::get()->dbObject('Standard')->enumValues();
$list = ArrayList::create();
foreach ($stnds as $stnd) {
$list->push(new ArrayData(array('Standard' => $stnd)));
}
return $list;
}
Then, in your template:
<% loop Standards %>
<h1>$Pos $Standard</h1>
<% end_loop %>
Related
Is posible to save SS template variable in database from CMS and after execute it in template?
Okay lets see example:
In CMS i have settings where i put social media links and contact informatios.
Also in CMS i have module where i create HTML block-s which after that i loop in website.
In that html block i want to put existing $SiteConfig.Email variable.
I Try that but that is rendered in template like $SiteConfig.Email not show real email?
Is this posible to do or i need some extra modification?
Check photo
The question you have written makes no sense to me, but I understand the screenshot.
So, SilverStripe renders .ss files with a class called SSViewer. Basically it reads the file as string and then runs it through SSViewer to generate the HTML output.
But, as you saw, the output of variables is not processed.
I can think of 3 ways to get what you want:
Run the variables through SSViewer aswell (in this example, use $RenderedHTMLContent in the template)
class MyDataObject extends DataObject {
private static array $db = [
'Title' => DBVarchar::class,
'HTMLContent' => DBText::class,
];
public function Foobar() { return "hello from foobar"; }
public function RenderedHTMLContent() {
$template = \SilverStripe\View\SSViewer::fromString($this->HTMLContent);
// using $this->renderWith() will allow you access to all things of $this in the template. so eg $ID, $Title or $Foobar. Probably also $SiteConfig because it's global
return $this->renderWith($template);
// if you want to add extra variables that are not part of $this, you can also do:
return $this->renderWith($template, ["ExtraVariable" => "Hello from extra variable"]);
// if you do not want $this, you can do:
return (new ArrayData(["MyVariable" => "my value"]))->renderWith($template);
}
}
Please be aware of the security implications this thing brings. SilverStripe is purposely built to not allow content authors to write template files. A content author can not only call the currently scoped object but also all global template variables. This includes $SiteConfig, $List, .... Therefore a "bad" content author can write a template like <% loop $List('SilverStripe\Security\Member') %>$ID $FirstName $LastName $Email $Salt $Password<% end_loop %> or perhaps might access methods that have file access. So only do this if you trust your content authors
Use shortcodes instead of variables. But I never liked shortcodes, so I don't remember how they work. You'll have to lookup the docs for that.
Build your own mini template system with str_replace.
class MyDataObject extends DataObject {
private static array $db = [
'Title' => DBVarchar::class,
'HTMLContent' => DBText::class,
];
public function Foobar() { return "hello from foobar"; }
public function RenderedHTMLContent() {
return str_replace(
[
'$SiteConfig.Title',
'$SiteConfig.Tagline',
'$Title',
'$Foobar',
],
[
SiteConfig::current_site_config()->Title,
SiteConfig::current_site_config()->Tagline,
$this->Title,
$this->Foobar(),
],
$this->HTMLContent
);
}
}
I'm using unclecheese/dashboard module i use it like it is described in the README. I use silverstripe 3.5.3
I get this error message:
[User Warning] None of the following templates could be found (no
theme in use): DashboardMostActiveUsersPanel.ss
this is the panel content:
class DashboardMostActiveUsersPanel extends DashboardPanel{
private static $db = array (
'Count' => 'Int',
);
public function getLabel() {
return 'Most Active Users';
}
public function getDescription() {
return 'Shows the most active Users.';
}
public function getConfiguration() {
$fields = parent::getConfiguration();
$fields->push(TextField::create("Count", "Number of users to show"));
return $fields;
}
public function getMostActiveMembers() {
$members = Member::get()->sort("Activity DESC")->limit($this->Count);
return $members;
}
public function PanelHolder() {
return parent::PanelHolder();
}
}
this is the template:
<div class="dashboard-recent-orders">
<ul>
<% loop $MostActiveMembers %>
<li>$Name, $Activity</li>
<% end_loop %>
</ul>
</div>
This is where the error comes from: theme_enabled is empty
Config::inst()->get('SSViewer', 'theme_enabled‘)
I set the theme in the CMS Backend and i set it in config.yml like
SSViewer:
theme: 'my-theme'
I also tried to put the templates in different folders in /themes directory. But still no luck. What am i missing any help would be highly appreciated.
Themes only affect the frontend. The backend does not use them. You'll need to put the template in your mysite/ directory, or whatever your $project is.
I didn't know how to write the question using just words so here's a nifty little ascii diagram of what I got going on for your reference before I begin explaining...
+-----+
Department Department |Shows |
+ + +--+--+
| | ^
+->ShowAssignment +>ShowAssignment |
+ |
+------------------------+ |
| | | |
v v v |
AssignShow AssignShow AssignShow |
+ |
| +------+ |
+-->Content| |
| | |
+-->Content+--------------------------------------+
| |
+-->Content|
+------+
BTW - moving forward lets pretend that we're trying to get the first AssignShow with it's 3 child articles.
Alrighty, so now you've got a visual of what's up let's get to it.
In every Department there is a ShowAssignment page and under it every child page is an AssignShow page that can have Content pages relating to the work a particular department is doing on a show.
In the Shows pages there is a section which finds all the departments that have been assigned to it and lists it out on the page. I have been able to get as far as retrieving all the assigned departments but cannot seem to be able to get down to getting the all the content of what each department is doing for that show...
More visuals :) I have the departments in big blue and where 'Static fo da mo' is I need the title of those articles and a link to its page
Here is the code I currently have :
Shows.php
# Get Departments assigned to this show
public function getAssignedDepartments(){
$result = new ArrayList();
# use this shows ID to find out what shows have been selected by departments
$assignedShowID = AssignShow::get()->filter('ShowsID', $this->ID);
if(count($assignedShowID) > 0){
foreach($assignedShowID as $dept){
$department = Department::get()->byID($dept->DepartmentID);
$result->add(new ArrayData(array(
'DepartmentTitle' => $department->Title
)
));
# This here is where i'm super stuck... I've managed to drill down
# this far but dont know how to get those darn kids!
$x = $department
->Children()
->find('ClassName', 'ShowAssignments')
->Children();
}
# Title (on the first echo) returns the
# show title so I know it's targeting correctly...
foreach ($x as $key) {
echo $key->Title . '<br>';
echo $key->Children()->Title . '<br>';
}
return $result;
}
else
return null;
}
So all in all my latest attempt of getting the children got me as far as getting the Title of the AssignShow page but using something like that $key->Children() doesn't let me go 1 step deeper... What do I need to do?
Edit, new info
Righty-o, so I've managed to get the children and able to access their info from the help given by bummzack
Update
If you just need to traverse the hierarchy in code, you should be aware that Children is a List, so something like $this->Children()->Title won't work. You'll need something like:
$children2LevelsBelow = array();
foreach ($this->Children() as $child) {
// Go one level deeper…
foreach ($child->Children() as $subChild) {
$children2LevelsBelow[] = $subChild;
}
}
I think this is the key part that was missing from the code posted with your question.
The code I have at the moment is like this (still in development so its a bit incomplete, but the answer above has assisted me in getting closer)
Show.php
# Get Departments assigned to this show
public function getAssignedDepartments(){
$result = new ArrayList();
# use this shows ID to find out what shows have been selected in departments
$assignedShowID = AssignShow::get()->filter('ShowsID', $this->ID);
if(count($assignedShowID) > 0){
foreach($assignedShowID as $dept){
$department = Department::get()->byID($dept->DepartmentID);
$result->add(new ArrayData(array(
'DepartmentTitle' => $department->Title,
'Link' => '/film/departments/' . $department->URLSegment
)
));
# this gets me what I need... ALMOST -_-
foreach($dept->Children() as $x) {
echo '<br>' . $x->Title . '<br>'; # get AssignShow child Title
echo $x->Content; # gets AssignShow child content (not that i need it)
}
}
return $result;
}
else
return null;
}
What I don't understand is how do I output a list of content under every department...
So for every grid-listing in the HTML below it'll need 1 or more <li>'s to go with it before the next block runs...
Here is a snippet of the HTML / template I'm using this for
Shows.ss
<% loop AssignedDepartments %>
<div class="grid-listing">
<h2>$DepartmentTitle</h2>
<ul>
<%-- How do I loop in a loop to get a list of ALL li --%>
<%-- before moving onto the next department in the main loop? --%>
<li>› $ContentTitle</li>
<%-- end_loop --%>
</ul>
</div><!-- . grid-listing -->
<% end_loop %>
I guess you have a relation from Shows to Department, maybe has_many or many_many?
So you should be able to do something like this in your Shows template:
<% loop $Departements %>
<div class="departement">
<h1>$Title</h1>
<% loop $Children %>
<div class="show-assignment">
<h2>$Title</h2>
<% loop $Children %>
<div class="assign-show">
<h3>$Title</h3>
<% loop $Children %>
<div class="content">
<h4>$Title</h4>
</div>
<% end_loop %>
</div>
<% end_loop %>
</div>
<% end_loop %>
</div>
<% end_loop %>
While it looks mesmerizing, a template like this is rather ugly and doesn't play well if you ever plan to change the hierarchy…
Instead, you could just have a special method in your Page class that renders it's children recursively.
Eg. create a method like this in your Page class:
public function RecursiveChildren(){
return $this->renderWith(array('RC' . $this->ClassName, 'RCPage'));
}
This simply renders the current page with an RC<ClassName>, or RCPage template, depending on what is available. The minimal required template would be RCPage.ss and could look like this.
<div class="$ClassName">
<h1>$Title</h1>
<% if $Children %><% loop $Children %>
$RecursiveChildren
<% end_loop %><% end_if %>
</div>
What you can then do, is replace the complex template above with something like this:
<% loop $Departements %>
$RecursiveChildren
<% end_loop %>
And it will create pretty much the same output as the complex template above.
To design different templates for each page type, you could go ahead and create: RCDepartment.ss, RCShowAssignment.ss etc. each responsible for rendering the fragment of said page-type.
Update
If you just need to traverse the hierarchy in code, you should be aware that Children is a List, so something like $this->Children()->Title won't work. You'll need something like:
$children2LevelsBelow = array();
foreach ($this->Children() as $child) {
// Go one level deeper…
foreach ($child->Children() as $subChild) {
$children2LevelsBelow[] = $subChild;
}
}
I think this is the key part that was missing from the code posted with your question.
I done did it! It was the querying that was a bit complicated and I was going to resort to using SQL but managed to stick with SilverStripes ORM. Additionally I was trying to get the data within the same function and using the same $result->add() which is probably why I found it so difficult...
This is the order of how it works.
Shows.php
We get the departments that are assigned to the show we're viewing. We put in a return value for the ID of the department, we'll be using that later
# Get Departments assigned to this show
public function getAssignedDepartments(){
$result = ArrayList::create();
# use this shows ID to find out what shows have been selected in departments
$assignedShowID = AssignShow::get()->filter('ShowsID', $this->ID);
if(count($assignedShowID) > 0){
foreach($assignedShowID as $dept){
$department = Department::get()->byID($dept->DepartmentID);
$result->add(ArrayData::create(array(
'Title' => $department->Title,
'Link' => '/film/departments/' . $department->URLSegment,
'DepartmentID' => $dept->DepartmentID
)
));
}
return $result;
}
else
return null;
}
Shows.ss
We use the above to get the departments assigned to the show and loop through them. While looping we need a second loop to get the children of that department, we need it to loop through all the children before moving onto the next department loop. To ensure we're getting the right content we pass in the DepartmentID to use in the getDepartmentContent function
<% loop AssignedDepartments %>
<div class="grid-listing">
<h2>$Title</h2>
<ul>
<% loop $Up.getDepartmentContent($DepartmentID) %>
<li>› $Title</li>
<% end_loop %>
</ul>
</div><!-- . grid-listing -->
<% end_loop %>
Shows.php
Now we get the children, this was the part I had quite a lot of trouble with. We use the DepartmentID we passed in to help us filter relevant content, otherwise it'll get all the content assigned to the show and output it for every department, which isn't accurate. We also build the URL using the URLSegments
public function getDepartmentContent($DepartmentID){
# filter result to get the AssignShow which matches this show
# and the department id supplied param
$assignedShowID = AssignShow::get()->filter(array(
'ShowsID' => $this->ID,
'DepartmentID' => $DepartmentID
)
);
$result = ArrayList::create();
foreach($assignedShowID as $key){
foreach($key->Children() as $children){
# AssignShow
$assignShow = AssignShow::get()->byID($children->ParentID);
# ShowAssignment
$showAssignment = ShowAssignments::get()->byID($assignShow->ParentID);
# Department
$department = Department::get()->byID($showAssignment->ParentID);
# full url path
$link = $department->URLSegment . '/'
. $showAssignment->URLSegment . '/'
. $assignShow->URLSegment . '/'
. $children->URLSegment;
$result->add(ArrayData::create(array(
'Title' => $children->Title,
'Link' => 'film/departments/' . $link
)
));
}
}
return $result;
}
This way here provides me with exactly what I needed to retrieve in the way I needed it. It also kept everything contained in the Controller to keep it neat.
I am trying to create a single page that will display multiple userforms in a tabbed view. For example basic contact form, request a quote form etc.
I thought I could make a new page type and loop through the children to display the forms, but the $Form variable isn't rendering the form.
<% loop $Children %>
<div>
<h2>$Title</h2>
$Form
</div>
<% end_loop %>
Am I missing something here, or is there a different way to render a form using a its ID in a template file?
You could try the following.
Create a function in your page holder controller to get the form from a specific child (must be a UserDefinedForm page). To do this you'll need to create the controller of this child page.
public function ChildForm($pageID) {
$page = UserDefinedForm::get()->byID($pageID);
$controller = UserDefinedForm_Controller::create($page);
return $controller->Form();
}
afterwards you'll call this function in your loop and pass the current child id to it
<% loop $Children %>
<div>
<h2>$Title</h2>
$Top.ChildForm($ID)
</div>
<% end_loop %>
This should (code is untested) return the forms you want.
The problem at play here is the difference between the DataObject/Page and the Controller. Looping over $Children returns you a DataObject whereas the Form function and template variable are part of UserDefinedForm's controller.
The other answer shows one working solution however it has some hair on it:
Jumping scope to your controller to pass an ID to get your form
Additional DB query
Requires all the child pages to be of type UserDefinedForm
We can implement a more generic solution that removes some of those elements and making your code a little more maintainable.
Take the following which would be added to the Page class (not the controller):
function getInLoopForm() {
if (in_array('UserDefinedForm', $this->ClassAncestry)) {
$controllerName = $this->ClassName . '_Controller';
$controller = $controllerName::create($this);
if ($controller->hasMethod('Form')) {
return $controller->Form();
}
}
return false;
}
The first part of that checks whether the current object has UserDefinedForm in its class ancestry. If it is, we then create the appropriate controller and return the form.
Your template code would look like this instead:
<% loop $Children %>
<div>
<h2>$Title</h2>
$InLoopForm
</div>
<% end_loop %>
This solution is generic for three reasons:
In our getInLoopForm function, the value "UserDefinedForm" can be replaced with any class that extends Page. It could even be brought out to a YML value if you were so inclined.
For SilverStripe, controller names for pages must match "{PageClassName}_Controller" so we can abuse that by working out the controller name dynamically. This allows for you to extend UserDefinedForm and its controller and we can still call the right function.
You only require your DataObject to access the form, you don't need your own controller.
For SS 4 there is a small code change needed:
public function getInLoopForm() {
if (in_array('SilverStripe\UserForms\Model\UserDefinedForm', $this->ClassAncestry)) {
$controller = UserDefinedFormController::create($this);
if ($controller->hasMethod('Form')) {
return $controller->Form();
}
}
return false;
}
I've written a simple custom function to take care of more than (>)
This works fine and returns 1 or 0.
It stops working when I pass $Pos to the function when it's called withing a loop.
SilverStripe Version 3
Controller
function MoreThen($pos, $value) {
if($pos > $value) {
return TRUE;
} else {
return FALSE;
}
}
Template.ss
<% loop GalleryObjects %>
<% if Top.MoreThen($Pos,2) %>
$Pos
<% end_if %>
AFAIK it is not possible to use variables as arguments of function calls, only concrete values. Depending on what you want to do, you might want to look at using GalleryObjects.limit() in your template, or write a specific getter that would return only GalleryObjects with an offset greater than 2. Hope this helps