XF 2.2 What's new for developers in XF 2.2?

Screenshot 2020-08-02 at 09.39.53.webpThe previous "have you seen" threads for 2.2 have generally been focused on what the changes mean to admins and end users. While we have made reference to some of the internal technical elements and what add-ons can do with these features, today we want to do a deeper dive into some of these changes from a development perspective.

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!
 
To understand extensions better: template modifications will not need a find/replace, if using an extension? Or, am I in left field? Can I replace a template modification with a template that uses an extension? (sorry for the questions, just want to understand this).

Also how many <xf:extension name="xxxxx"></xf:extension> is there per top level template?
A simple example might be useful here.

We have a template called five_liner
HTML:
<!-- template: five_liner -->
<div class="block">
	<div class="block-container">
		<h2 class="block-heading">This is my heading</h2>
		<div class="block-body">
			<div>This is my first line</div>
			<div>This is my second line</div>
			<xf:extension name="third_line">
				<div>This is my third line</div>
			</xf:extension>
			<xf:extension name="fourth_line">
				<div>This is my fourth line</div>
			</xf:extension>
		</div>
	</div>
</div>
And we want to extend it with a template called five_liner_alt
HTML:
<!-- template: five_liner_alt -->
<xf:extends template="five_liner" />

<xf:extension name="third_line">
	<div>This is something else entirely for my third line</div>
</xf:extension>

<xf:extension name="fourth_line">
	<div>This is my fourth line</div>
	<div>This is my four-point-five-th line</div>
	<div>This is my four-point-six-th line</div>
</xf:extension>
The result is that our extended template gets all of the data from the original, without having to create a customised version of it, and can replace or extend the bits it wants to, resulting in the following output:
HTML:
<!-- resulting output for five_liner_alt -->
<div class="block">
	<div class="block-container">
		<h2 class="block-heading">This is my heading</h2>
		<div class="block-body">
			<div>This is my first line</div>
			<div>This is my second line</div>
			<div>This is something else entirely for my third line</div>
			<div>This is my fourth line</div>
			<div>This is my four-point-five-th line</div>
			<div>This is my four-point-six-th line</div>
		</div>
	</div>
</div>
 
A simple example might be useful here.

We have a template called five_liner
HTML:
<!-- template: five_liner -->
<div class="block">
    <div class="block-container">
        <h2 class="block-heading">This is my heading</h2>
        <div class="block-body">
            <div>This is my first line</div>
            <div>This is my second line</div>
            <xf:extension name="third_line">
                <div>This is my third line</div>
            </xf:extension>
            <xf:extension name="fourth_line">
                <div>This is my fourth line</div>
            </xf:extension>
        </div>
    </div>
</div>
And we want to extend it with a template called five_liner_alt
HTML:
<!-- template: five_liner_alt -->
<xf:extends template="five_liner" />

<xf:extension name="third_line">
    <div>This is something else entirely for my third line</div>
</xf:extension>

<xf:extension name="fourth_line">
    <div>This is my fourth line</div>
    <div>This is my four-point-five-th line</div>
    <div>This is my four-point-six-th line</div>
</xf:extension>
The result is that our extended template gets all of the data from the original, without having to create a customised version of it, and can replace or extend the bits it wants to, resulting in the following output:
HTML:
<!-- resulting output for five_liner_alt -->
<div class="block">
    <div class="block-container">
        <h2 class="block-heading">This is my heading</h2>
        <div class="block-body">
            <div>This is my first line</div>
            <div>This is my second line</div>
            <div>This is something else entirely for my third line</div>
            <div>This is my fourth line</div>
            <div>This is my four-point-five-th line</div>
            <div>This is my four-point-six-th line</div>
        </div>
    </div>
</div>

Thank you for the example, @Kier. Yes, I was way in left field, :)
 
The result is that our extended template gets all of the data from the original, without having to create a customised version of it, and can replace or extend the bits it wants to, resulting in the following output:
Kier, the example helps visualize the new template extensions but in regards to "extend the bits", in your example the content is being replaced, is it possible to append to the bottom of the (for example) 3rd line's stock value of <div>This is my third line</div> instead of replacing it (sort of like doing a $0 {my new stuff} in the TMS)? In your 4th line example your replacement includes the original <div>This is my fourth line</div> value so I'm just want to make sure I understand what to play with when 2.2 is available to us, thanks. :)
 
@Kevin: that's where <xf:extensionparent /> comes in. It returns the "original" value for the extension you're overriding. It's pretty much equivalent to $0 in a template modification.

You can see a practical example with the expanded article example where we override the whole thread list section and if we detect that it's not an expanded article forum (it's been set to just the standard thread display), then we call <xf:extensionparent /> and it output the original thread list (which would've come from forum_list).
 
Have we reached the end of admin/end-user feature announcements? Just curious to know if I should temper my expectations from here on out. 🙃
 
I don't even know where to begin, haha. So many ideas to utilize this, but going to wait and see how this all goes with some of the initial adopters to flush out my ideas more.

Great work as always XenForo team!
 
@Kevin: that's where <xf:extensionparent /> comes in. It returns the "original" value for the extension you're overriding. It's pretty much equivalent to $0 in a template modification.

You can see a practical example with the expanded article example where we override the whole thread list section and if we detect that it's not an expanded article forum (it's been set to just the standard thread display), then we call <xf:extensionparent /> and it output the original thread list (which would've come from forum_list).
Thanks, Mike. :) I think the morning coffees are now kicking in and I'm seeing it. :D

In Kier's example then for line #4, instead of a replacement value of...
Code:
<xf:extension name="fourth_line">
    <div>This is my fourth line</div>
    <div>This is my four-point-five-th line</div>
    <div>This is my four-point-six-th line</div>
</xf:extension>
... the same result can be achieved by using:
Code:
<xf:extension name="fourth_line">
    <xf:extensionparent /> 
    <div>This is my four-point-five-th line</div>
    <div>This is my four-point-six-th line</div>
</xf:extension>
 
goodmorning xenforo team
i want to ask you about the xenforo api
is there any new?
we like to see the search api inadition to the option to close the thread
and we need option to super user to create thread using any user id or user name
this will give us the ability to create an desktop or mobile app to control our forums.
e.g
the user need to login and create thread using the app without going to the web site.
 
Closing threads and creating threads as arbitrary users are already possible in the API. It might be best to post in the development forum if you have particular questions.
 

Forum and thread types

From a development perspective, it's important to consider forum and thread types as separate systems with a bit of interaction and encompassing similar ideas. However, they are totally separate systems and there's nothing to say you can't define a forum type without defining any unique thread types and vice versa.

Though there are some exceptions to this rule, when viewing a forum, you're dealing with a forum type handler and when viewing a thread, you're dealing with a thread type handler.

We're not going to go into every method these type handlers cover in this thread; we're just going to go over some concepts that relate to each. The abstract handlers have full phpDoc blocks that try to explain how the methods are used and in some cases, things to keep in mind.

Array validators

Before we dive into forum and thread types, it's worth noting the new 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.

Basic setup mirrors entity column definitions:
PHP:
protected function getTypeConfigColumnDefinitions(): array
{
   return [
      'display_style' => ['type' => Entity::STR, 'allowedValues' => ['full', 'expanded']],
      'expanded_snippet' => ['type' => Entity::UINT],
      'expanded_per_page' => ['type' => Entity::UINT],
   ];
}

Forum types

Forum types add an additional 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.

Setting up custom type config options involves defining an admin template to contain those options, along with a method to handle reading the config from input and validating it. Best practice would also be to expose these options to the REST API, so there are methods to both expose the configuration when the forum data is returned and to update the config itself.

It's worth mentioning that a forum's type can be changed. The change process involves setting up the new type config options before the forum's type has actually changed. This is important because changing a forum type may change the type of the threads within the forum and that can be affected by the type config. (It is possible to block changing out of a specific forum type via the handler, if needed.)

Each forum type also defines the allowed thread types within that forum. This will affect the UI when creating a thread. If you want to add behaviors when viewing the forum that depend on the thread being a particular type, then you would likely want to make your forum only accept a single thread type. (Note that 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.
The majority of the remaining methods relate to controlling the display and behavior of the forum view pages. Some of the things you can do include:
  • Override the display entirely. This is the nuclear option, but if you don't want to use any of the controller or display code from a default forum, you have that option.
  • Override the name of the view or template (forum_view) and manipulate the parameters sent to the template.
  • Override some of the macros used, such as for displaying thread list items or quick thread, and optionally pass additional arguments to the macros.
  • Add additional ways to filter the threads shown or to customize the available sort orders.
  • Adjust the thread list finders and to fetch additional content with the threads.
  • Customize the number of threads shown per page. (This is primarily for situations like the expanded article view, where you're going from a very small amount of content per thread to a large amount.)
In conjunction with the template extension system, these options give you the ability to heavily customize how a forum looks and works, while being able to use the existing code and default output where you want it. It also means that unless you change a particular area, any add-ons that change the output or additional behavior will still applied.

Thread types

Many of the concepts we just explained under forum types apply to thread types as well. However, some times the functionality differs.

Similar to forum types' 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.

Thread type data is displayed via the 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.

When processing the extra data, there are two approaches: simple and service. Simple processing is essentially the same as what we saw in forum types. We use the array validator system to map inputs to the extra data we want to store. The service approach allows you to create a service object to setup the actions you want to take. This will be used in conjunction with the base service (thread create, edit, etc) to ensure that no action is taken unless both services validate and once the base service's action has run, the type data service will execute as well. This is wordy, but it's roughly equivalent to how we handled poll creation in 2.1 but in a more generic way.

In terms of thread viewing, thread types introduce two new concepts:
  • Pinned first post - if set for the thread type, the first post of the thread will be pinned to the top of each page. This is integrated directly into 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.
  • Highlighted posts - these are an arbitrary number of additional posts that will be fetched on each page. You're responsible for controlling how and where they will be displayed. This is how the solution is handled in questions, but there are virtually limitless ways this can be used to enhance threads.
Beyond that, thread types allow you to override a number of things in a similar way to forum types:
  • Override the thread display entirely.
  • Override the name of the view and template used and manipulate the parameters sent to the template.
  • Override some of the macros used, such as for displaying posts, deleted posts or the pinned first post, and optionally pass additional arguments to the macros.
  • Apply custom filters to thread viewing. While this isn't currently being used out of the box, it made sense to bring the concept of filters to threads so that we could apply the necessary behavior changes when a thread is filtered.
  • Allow custom sort orders and control the default order. Note that while it is possible to change the default order of a thread to be something other than chronological, chronological sorting will always be available and there are situations where we will automatically switch to it. There are also caveats that non-chronological orders will disable some standard behaviors, most notably thread read marking.
  • Adjust the post list finder and to fetch extra content for individual posts.
  • Control when thread or post voting is supported and the related permission checks.
Thread types also define extension points for various thread and post lifecycle events, including:
  • Thread pre-save, post-save and post-delete.
  • Thread is made visible or hidden.
  • Thread enters or leaves the type.
  • Thread's counters are rebuilt.
  • Threads are merged into the thread.
  • A visible post is added to or removed from the thread.
  • A post in the thread is saved or deleted.
While there is significant power in both of these systems already, we still have ideas for additional places where these systems can hook into XenForo to provide even more flexibility. We're very excited to see what can be done with these systems!
I currently use the thread prefix to do something similar to thread types. This is for my site only, but the addons, gets the thread prefix, if it matches the prefix that I am looking for then it will change the thread display tp include stats about a sporting event.

Would the thread type work or be a better solution for this, this is for usually one game per week and sits in a forum with all other threads too -> see https://silvertails.net/threads/raiders-v-manly-round-6-2020.58681/ for an example
 
So in order to extend a template, you simply create a new template with the required code and that's it?

I have this:
Code:
<xf:extends template="thread_view" />

<xf:extension name="above_messages">
    above_messages
</xf:extension>

<xf:extension name="below_messages">
    below_messages
</xf:extension>
And nothing happens in thread view.
 
You're probably best posting a thread in the dev forum with more details, but you have to call your template (via PHP). This doesn't do anything if you call thread_view. (The extension system is for a specific purpose -- things like the thread type system -- so it may not be appropriate for what you're trying to do.)
 
As someone that really cringed at depending on only template modifications in addons and after re-doing a bunch of template mods to take advantage of the extension system last night, I am floored by (forgive the oxymoron) the complexity and yet simplification of how we can make templates be overridden and extended.

Especially getThreadViewTemplateOverrides() ... which looks like you can piecemeal little or whole lots of pieces of thread_view (or in my case a custom_thread_view that extends thread_view).

Really cool stuff.
 
When calling a macro, we now generally prefer <xf:macro name="template_name::macro_name"> over <xf:macro template="template_name" name="macro_name"> although both options are supported.
@Mike @Kier
post_question_macros template has something like this <xf:macro name="answer" extends="post_macros::post">
What does it mean?
It has name=" " attribute and extends="post_macros::post" both parts at the same time, but no template=" " attribute get used.
This one is a bit confusing, could you please explain this a little bit further?
Thanks in advance.

A simple example might be useful here.

We have a template called five_liner
HTML:
<!-- template: five_liner -->
<div class="block">
    <div class="block-container">
        <h2 class="block-heading">This is my heading</h2>
        <div class="block-body">
            <div>This is my first line</div>
            <div>This is my second line</div>
            <xf:extension name="third_line">
                <div>This is my third line</div>
            </xf:extension>
            <xf:extension name="fourth_line">
                <div>This is my fourth line</div>
            </xf:extension>
        </div>
    </div>
</div>
And we want to extend it with a template called five_liner_alt
HTML:
<!-- template: five_liner_alt -->
<xf:extends template="five_liner" />

<xf:extension name="third_line">
    <div>This is something else entirely for my third line</div>
</xf:extension>

<xf:extension name="fourth_line">
    <div>This is my fourth line</div>
    <div>This is my four-point-five-th line</div>
    <div>This is my four-point-six-th line</div>
</xf:extension>
The result is that our extended template gets all of the data from the original, without having to create a customised version of it, and can replace or extend the bits it wants to, resulting in the following output:
HTML:
<!-- resulting output for five_liner_alt -->
<div class="block">
    <div class="block-container">
        <h2 class="block-heading">This is my heading</h2>
        <div class="block-body">
            <div>This is my first line</div>
            <div>This is my second line</div>
            <div>This is something else entirely for my third line</div>
            <div>This is my fourth line</div>
            <div>This is my four-point-five-th line</div>
            <div>This is my four-point-six-th line</div>
        </div>
    </div>
</div>
what would happen If we have more than one extension? another template called five_liner_alt2?
HTML:
<!-- template: five_liner_alt2 -->
<xf:extends template="five_liner" />

<xf:extension name="third_line">
    <div>This is something else entirely for my third line - version 2</div>
</xf:extension>

<xf:extension name="fourth_line">
    <div>This is my fourth line - version 2</div>
    <div>This is my four-point-five-th line - version 2</div>
    <div>This is my four-point-six-th line - version 2</div>
</xf:extension>
 
Last edited:
Top Bottom