Data objects in templates

Silverstripe Version: 4

Question:
How do I use data from a ModelAdmin::get() in a template?

In the site I’m migrating, I use to do this in a controller:

$content_blocks = ContentBlock::get();

$data = [];

foreach ($content_blocks as $block)
{
    $data[$block->Name] = $block;
}

Which gives me an indexed array of objects.

Then in the view: echo $content['intro']->content;

This content is distributed throughout the page so a loop won’t work. The key of “intro” etc are known so I can simply output them manually where I need them.

Full controller

use SilverStripe\CMS\Controllers\ContentController;

class PageController extends ContentController
{
    private static $allowed_actions = [];

    protected function init()
    {
        parent::init();

        $content_blocks = ContentBlock::get();

        $data = [];

        foreach ($content_blocks as $block)
        {
            $data[$block->Name] = $block;
        }

        return $this->customise(['data'=> $data])->renderWith("Page");
    }
}

I realise I can’t do the output like that in SS as it doesn’t allow PHP in templates.
So I tried to use ArrayList and ArrayData but I can’t seem to set the keys - making it useless in the view. That attempt is:

use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;

class PageController extends ContentController
{
    private static $allowed_actions = [];

    protected function init()
    {
        parent::init();

        $content_blocks = ContentBlock::get();

        $data = new ArrayList();

        foreach ($content_blocks as $block)
        {
            $data->push(
                new ArrayData([
                    'Name' => $block->Name,
                    'Title' => $block->Title,
                    'Content' => $block->Content
                ])
            );
        }

       return $this->customise(['data'=> $data])->renderWith("Page");
    }
}

I also read that it might be better to set this data as a class property, and write a seperate accessor function in the controller to get the data out of it. I tried that and it couldn’t access the function even when it was in $allowed_actions. Perhaps because it’s in PageController. Is this controller special or something?

Help greatly appreciated. Problems I’m having seem like the most basic I can think of but I’m struggling to find much info anywhere.

Does your object “ContentBlock” have a forTemplate() function?

No it doesn’t. It’s got just about nothing in there:

use SilverStripe\ORM\DataObject;

class ContentBlock extends DataObject
{
	private static $table_name = 'Blocks';

	private static $db = [
		'Name' => 'Varchar',
		'Title' => 'Varchar',
		'Content' => 'HTMLText'
	];

	private static $summary_fields = [
		'Name' => 'Name',
		'Title' => 'Title'
	];
}

Frustratingly, it seems like this whole line:

return $this->customise(['data'=> $data, 'SomeThing' => 'Bingo'])->renderWith("Page");

Does nothing in my PageController file. I use $Something in the Page.ss file expecting to see “Bingo” but there’s nothing.

I’ve read that controllers will autoload a file that matches the naming convention so I assume that is what is happening.

If you really want to loop over all ContentBlocks without a filter, you probably don’t need anything in your Controller. I’m working from memory but I think you could just put this in your template:

<% loop $List('ContentBlock') %>
    <p><b>Name</b>$Name</p>
    <p><b>Title</b>$Title</p>
    $Content
<% end_loop %>

It’s more realistic that you’ll want to return only some of them so then you could use an accessor function. You don’t need to mention that in allowed_actions as it’s not a url action. Try deleting your custom init method on your PageController then put this in your controller:

public function ContentBlocks()
{
    return ContentBlock::get();
}

and put this in your Page.ss template:

<% loop $ContentBlocks %>
    <p><b>Name</b>$Name</p>
    <p><b>Title</b>$Title</p>
    $Content
<% end_loop %>

p.s. Have you checked out the SilverStripe Lessons? They go over concepts like this and I think they’re pretty great.

Thanks Jono,

No I don’t want to loop these at all. As mentioned in the first post: “This content is distributed throughout the page so a loop won’t work. The key of “intro” etc are known so I can simply output them manually where I need them.”

I want to get them in an associative structure and access them as I need them. I’m surprised this is such a mission… it’s the most basic function of any framework isn’t? Get some data from a database and expose it to a view.

I’ve tried an even more simple version of your function:

public function ContentBlocks() { return 'Hello world'; }

in the template

<h1>$ContentBlocks - test</h1>

I do a dev/build just 'cos and I see nothing.

Yes I have done all the lessons. They don’t cover this sort of thing at all.

It sounds like the template you’re editing isn’t the one that’s being rendered. Try adding some config to see which templates are being used: Template debugging – SilverStripe Documentation

Once you get your template rendering from your controller okay, try this in your init method (if I understand correctly now):

$customPageProperties = [];
foreach ($content_blocks as $block)
{
    $customPageProperties[$block->SomeUniqueKey] = $block;
}
return $this->customise($customPageProperties);

Note that you’re passing a simple keyed array to the customise method, and you don’t need to call renderWith. That should let you access each individual block by the key you set.

I can see the template it’s loading is Page.ss in /app/src/templates.

I changed my /app/_config/app.yml to:

SilverStripe\Core\Manifest\ModuleManifest:
  project: app
SilverStripe\View\SSViewer:
  source_file_comments: true

and did a ?flush and I don’t see anything on the page or in the console?

I did a quick test without all the data stuff with just:

return $this->customise(['data'=> $data, 'please' => 'WORK']);

and using $please in the view, I don’t see WORK… gah.

So it turns out all my problems of loading a template, renderWith() and customise() being ignored is soley due to me doing all this in the PageController::init(). :man_facepalming:

I’ve moved this code into PageController::index() and it works as expected.

Also, sending the associative array data to the view is simple and doesn’t need all the ArrayList and ArrayData guff as @JonoM showed.

For future users, this is all I did:

Controller:

use SilverStripe\CMS\Controllers\ContentController;

class PageController extends ContentController
{
	private static $allowed_actions = [];

    protected function init()
    {
        parent::init();
    }

	public function index()
	{
		$content_blocks = ContentBlock::get();

		foreach ($content_blocks as $block)
		{
			$tmp[$block->Name] = $block;
		}

		return $this->customise(['Title' => 'Some page title','data' => $tmp])->renderWith('Painful');
	}
}

Then in the view to use it: $data.intro.Title, $data.intro.Content, $data.key.value

Thanks for looking.

1 Like

Glad you sorted it out Lance.