
What we're going to cover today is not an exhaustive list. No doubt you'll find other changes scattered throughout. Today we're just going to focus on some of the more significant or noteworthy changes.
So let's go!
<xf:extends template="thread_view" />
<xf:extension name="above_messages">
<xf:if is="$poll">
<xf:macro template="poll_macros" name="poll_block" arg-poll="{$poll}" />
</xf:if>
</xf:extension>
thread_view_type_poll
template, which is what is used when viewing a poll thread. It's one of the simplest thread type templates.<xf:extends>
thread_view
(the standard template used when viewing a thread). When this line is present, it essentially means that we output the named template instead (though there's more to it <xf:extension>
above_messages
extension point.thread_view
template itself. The relevant section:<xf:macro template="lightbox_macros" name="setup" arg-canViewAttachments="{$thread.canViewAttachments()}" />
<xf:extension name="above_messages"></xf:extension>
<xf:ad position="thread_view_above_messages" arg-thread="{$thread}" />
<xf:extension>
tag.thread_view
template, we're effectively marking this as an overrideable extension point. By default, it won't display anything, but another template can choose to change what is displayed in this location. Since the poll template is overriding this position, we'll get the poll displayed above the messages.<xf:extension>
tags. In some cases, the default template might contain some content. A good example of this is the extended article forum example. In forum_view
we now have something like this:<div class="block-body">
<xf:extension name="thread_list">
<div class="structItemContainer">
<!-- all of the standard stuff to display sticky threads, normal threads, etc -->
</div>
</xf:extension>
</div>
forum_view_type_article
we can override that like this:<xf:extension name="thread_list">
<xf:if is="$forum.type_config.display_style == 'expanded' AND $forum.canViewThreadContent()">
<!-- display extended versions of articles instead of the standard thread list -->
<xf:else />
<xf:extensionparent />
</xf:if>
</xf:extension>
<xf:extensionparent>
tag. This allows you to output the parent/original version of the extended area. In this case, we're using it as a fallback to the original display when extended mode isn't enabled, though it can also be used to add things at the beginning or end of the area or to wrap the output.<xf:macro name="answer" extends="post_macros::post">...</xf:macro>
<xf:extension>
tags can also be defined with a value
attribute which can allow them to be used for more than just HTML blocks, similar to the two approaches to <xf:set>
.<xf:extensionvalue>
tag and extension_value()
template function. These allow you to reference an extension multiple times if needed. The template function also allows you to use an extension with specific HTML attributes. We use this in several places to changes the classes applied to an element.<xf:extensionparent>
can also take an extension name to render the parent version of that. This is unlikely to be used often but there are some particular cases that are difficult without it.<xf:macro name="template_name::macro_name">
over <xf:macro template="template_name" name="macro_name">
although both options are supported. The single attribute approach makes it more straightforward to dynamically switch to a different macro, something done quite commonly in the forum and thread types system.XF\Entity\ArrayValidator
class. This is designed to add simple entity-like validations to a basic array, including things like types, constraints, required fields, and validation callbacks. Both forum and thread types use this for their type config/data columns to centralize data validation.protected function getTypeConfigColumnDefinitions(): array
{
return [
'display_style' => ['type' => Entity::STR, 'allowedValues' => ['full', 'expanded']],
'expanded_snippet' => ['type' => Entity::UINT],
'expanded_per_page' => ['type' => Entity::UINT],
];
}
type_config
field to forums where you can store, well, forum-type configuration options. The defaults are setup in the getDefaultTypeConfig()
handler method. When accessing this via the entity, we merge the defaults with any overrides, so you can be sure that each config option is present, which helps avoid unexpected errors when new config options are added.redirect
threads are always allowed, so that is something you may need to account for.) This is done via:getDefaultThreadType
- defines the type that will be selected by default if there are multiple creatable types and the type that will be used if the thread is created in a manner that doesn't explicitly set the type.getExtraAllowedThreadTypes
- any additional thread types that may be allowed.getCreatableThreadTypes
- potentially a subset of the first two methods, this differentiates the types of threads that users can create manually vs those that can only be created internally. As an example, resource discussion threads would be allowed in discussion forums but end users can't manually create them.forum_view
) and manipulate the parameters sent to the template.type_config
column, thread types expose type_data
. This may expose configuration options, but it may also include additional data that's specific to that thread type. For example, with questions, we store the post ID and user ID for the solution in this data (though we do also mirror this to a separate table to aid certain tasks). However, some thread types may involve significantly more complex data that you might not want to store in the thread record. Our type-specific data management methods are designed to handle this.renderExtraDataEdit
method. Note that this is used both in thread creation and thread editing contexts. Information about the context is passed into the method, allowing you to react appropriately. For example, while polls can be created with the thread, there are different constraints on editing an existing poll, so we don't show anything unless we're in the create
context.thread_view
so simply toggling this on will make it work, though there are some additional overrides that allow you to easily change the display of just the pinned first post.ArrayValidator
is interesting, looking forward to playing around with it.One good solution!<xf:macro name="template_name::macro_name">
Worth mentioning that the templates do need to include the extension points, so template modifications will always be required at some point, though you can potentially make a modification to include an extension point instead.Template extensions are very interesting indeed, makes for much cleaner ways of adding custom content to templates and hopefully fewer template modification conflicts.
That's probably not entirely relevant. The usage of this would primarily be for an add-on to use an extension point in its own template.And how will the extension be displayed if 2 add-ons use one extension?
That raises a good point actually; how liberally are you going to apply template extension points? If I was to dig through some of my existing template mods, I'm sure I'd find some really ugly ones (regexp capturing a wholeWorth mentioning that the templates do need to include the extension points, so template modifications will always be required at some point, though you can potentially make a modification to include an extension point instead.![]()
<xf:radiorow>
f.ex. because I wanted to add the option after said row and it's the last option in a section). These bits could do with either a template modification capture comment, or an extension point.These bits could do with either a template modification capture comment, or an extension point.
Ahh I see, fair enough then. Still, being able to get more template modification comments in the system wouldn't go amissI don't think an extension point is meant to be used in this way, it seems a bit more like Laravel/Blade's layout system
Fair to say Twig's template inheritance was somewhat of an inspiration tooit seems a bit more like Laravel/Blade's layout system
<xf:extension name="xxxxx"></xf:extension>
is there per top level template?We need more developer documentation like IPBoard.
We use essential cookies to make this site work, and optional cookies to enhance your experience.