Hello,
I've been struggling for a whole day trying to get this to work:
I want to display a sidebar containing all blog posts on the left, and the currently selected blog post (via :slug) on the right. And if there's no slug parameter present, the right side should not be blank, it should display the latest blog post per default.
I've tried the following approaches:
...
[blogPost]
slug = "{{ :slug }}"
categoryPage = 404
==
<div class="row">
<div class="medium-4 columns" id="sidebar">
{% component 'blogPosts' %}
</div>
<div class="medium-7 medium-offset-1 columns">
{% if not this.slug %}
{% component 'blogPost' slug=blogPosts.posts[0].slug %}
{% else %}
{% component 'blogPost' %}
{% endif %}
</div>
</div>
...
[blogPost]
slug = "{{ :slug }}"
categoryPage = 404
[blogPost firstPost]
==
<?php
function onInit()
{
if(!$this['slug'])
{
$this->page['firstPost'] = ['slug' => RainLab\Blog\Models\Post::isPublished()->latest()->first()->slug ];
}
}
?>
==
<div class="row">
<div class="medium-4 columns" id="sidebar">
{% component 'blogPosts' %}
</div>
<div class="medium-7 medium-offset-1 columns">
{% if not this.slug %}
{% component 'firstPost' %}
{% else %}
{% component 'blogPost' %}
{% endif %}
</div>
</div>
When trying to access/overwrite the slug of the blogPost component I'm getting a "Indirect modification of overloaded property ... has no effect" exception, even if I'm accessing arrays only.
Another important note is that I overrode the component layout within my theme and I want to use this as a component. So please do not suggest me to put something like this:
<div class="row">
<div class="medium-4 columns" id="sidebar">
{% component 'blogPosts' %}
</div>
<div class="medium-7 medium-offset-1 columns">
{% if not this.slug %}
<h1>{{ blogPosts.posts[0].title }}</h1>
<span>{{ blogPosts.posts[0].date }}</span>
...
{% else %}
{% component 'blogPost' %}
{% endif %}
</div>
</div>
Since that would make maintaining the code a chore.
Thanks in advance,
Harti
This is a possible, albeit hacky, solution:
[blogPost]
slug = "{{ :slug }}"
categoryPage = 404
==
<?php
function onInit()
{
$this['firstPost'] = $this->renderPartial('blogPost/default.htm', [
'post' => RainLab\Blog\Models\Post::isPublished()->latest()->first()
]);
}
?>
==
<div class="row">
<div class="small-12 small-order-2 medium-order-1 medium-4 columns" id="sidebar">
<h2><i class="fa fa-newspaper-o"></i> Neuigkeiten</h2>
{% component 'blogPosts' %}
</div>
<div class="small-12 small-order-1 medium-order-2 medium-8 large-7 large-offset-1 columns">
{% if not this.param.slug %}
{{ firstPost|raw }}
{% else %}
{% component 'blogPost' %}
{% endif %}
</div>
</div>
@LukeTowers is there a non-hacky solution while you're at it?
@Harti What was wrong with your first approach?
[blogPost]
slug = "{{ :slug }}"
categoryPage = 404
==
<div class="row">
<div class="medium-4 columns" id="sidebar">
{% component 'blogPosts' %}
</div>
<div class="medium-7 medium-offset-1 columns">
{% if not this.slug %}
{% component 'blogPost' slug=blogPosts.posts[0].slug %}
{% else %}
{% component 'blogPost' %}
{% endif %}
</div>
</div>
Did you try debugging to see what part of slug=blogPosts.posts[0].slug didn't return the correct value?
@LukeTowers it did return the correct slug. It didn't load the blog post, however - instead, the template was empty and displayed a "posted at" time showing "now".
I figured it doesn't work that way due to the plugin's life cycle so I had to go a different route.
Thanks for confirming though that this should be the way it's supposed to be working.
It might be because the slug property is already set in the configuration section of the page here:
[blogPost]
slug = "{{ :slug }}"
categoryPage = 404
Try setting up another instance of the component without specifying a slug and see if that works:
[blogPost singlePost]
==
{% component 'singlePost' slug=blogPosts.posts[0].slug %}
Play around with it a bit and try throwing some debugging statements into the Components/BlogPost.php file to figure out the lifecycle and where the error is occurring.
@LukeTowers
The issue seems to lie in the way components are rendered and how attributes are being gathered in RainLab\Blog\Components\Post.
During onRun() the component has a slug attribute value of false, only during onRender() the given attribute will be applied.
Interestingly, when I call $this->post = $this->loadPost(); during onRender() instead of onRun(), the correct post does get pulled. It will have the original RainLab template though, but only when the slug is not present in the URL.
It will pull the correct post (including the correct template override!) if I set the post's slug to this.param.slug explicitly and call the URL with a slug like so:
[blogPost]
[blogPost singlePost]
==
...
{% if not this.param.slug %}
{% component 'singlePost' slug=blogPosts.posts[0].slug %}
{% else %}
{% component 'blogPost' slug=this.param.slug %}
{% endif %}
... but when I call the page without a slug, even if I hard-code the slug parameter to some existing slug, it will render the wrong (original) template.
I'm very confused as to why that happens. Changing the default value in the defineProperties() array didn't help. It is very contradictory and doesn't make sense to me, maybe some caught exception (due to a missing URL parameter) silently causing the Controller to pull the wrong template. I hope the person who programmed components and controller/routing interaction can shed some light on this.
@Harti The explanation for why the incorrect template is being pulled is quite simple: Component template overrides are pulled based off of the component alias, not the component name.
So if you have a blogPost component with the alias singlePost then it is going to look for the override in partials/singlePost instead of partials/blogPost. Does that make sense?
@LukeTowers that does indeed make sense, thank you so much.
Now that we figured out the solution I'm proposing a bugfix PR, see #324.
@Harti Thanks for that, I've merged your PR.