• This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn more.

Problems with injecting a new template into a template hook that contains a <xen:foreach loop.

Chris D

XenForo developer
Staff member
#1
Title is a bit crappy... basically consider the thread_list_threads hook.

That sits in the thread_list template:

Code:
		<xen:hook name="thread_list_threads">
		<xen:foreach loop="$threads" value="$thread">
			<xen:include template="thread_list_item" />
		</xen:foreach>
		</xen:hook>
And within that hook is a foreach loop where the $threads param and therefore individual $thread params are available.

If I inject a template somewhere within the foreach loop that contains this code:

Code:
{xen:helper dump, $thread}
I would expect each thread_list_item to now contain the $thread record for each list item.... Nope.... Unfortunately each thread_list_item now contains "NULL".

However, if I inject a template that contains this code:

Code:
{xen:helper dump, $threads}
Each thread item now has a dump of all threads.

Aside from the TMS - is there anything else that can be done to achieve what I want?
 

ManOnDaMoon

Well-known member
#3
Hmmm, it seems that the $contents var is already processed when passed to the hook...

There is another hook inside the thread_list_item template that may be useful : "thread_list_item_icon_key".
Just append the print_r dump of the 'thread' hook param to the $content var :
PHP:
public static function templateHookTest ($hookName, &$contents, array $hookParams, XenForo_Template_Abstract $template)
	{
		if ($hookName == "thread_list_item_icon_key")
		{		
			$contents = $contents. '<pre>' . print_r($hookParams['thread'], true) . '</pre>';
		}
	}
 

Jake Bunce

XenForo moderator
Staff member
#4
That hook has no params. $threads is not passed. But you should be able to use this to access $threads within your hook function:

Code:
$threads = $template->getParam('threads');
 

Chris D

XenForo developer
Staff member
#5
That hook has no params. $threads is not passed. But you should be able to use this to access $threads within your hook function:

Code:
$threads = $template->getParam('threads');
Threads is there if I do that. That's no problem. Except I want to insert a new template using str_replace somewhere in the hook within the for each loop. My aim is that as $threads is already looping, I want to access $thread.
 

Jake Bunce

XenForo moderator
Staff member
#6
The hook system renders the contents first, then calls the hook. So you can only easily append to the beginning and end of the contents. You can't hook inside of the execution of the foreach. The closest thing would be to hack away at the rendered HTML (the $content of the hook).
 

ManOnDaMoon

Well-known member
#7
There is one hack possible IMO.

The render inside the foreach loop is a list with each <li> element being identified following this pattern : "thread-$threadId" (e.g.: thread with ID 42 is listed in a <li id="thread-42"> element).

With a couple of lines of code, you could grab all the thread ids from the $threads hook param, and use it to str_replace within the rendered $contents var. This should somehow look a bit like the following (on the fly, untested):
PHP:
	public static function templateHookTest ($hookName, &$contents, array $hookParams, XenForo_Template_Abstract $template)
	{
		if ($hookName == "thread_list_threads")
		{
			$threads = $hookParams['thread']; // or $template->getParams('threads'); ?

			foreach ($threads as $thread)
			{
				$yourTemplate = $template->create('yourCustomTemplate', array('thread' => $thread));
				
				$oldStr = '<li id="thread-'.$thread['thread_id'] . '" etc...>';
				$replaceStr = '<li id="thread-'.$thread['thread_id'] . '" etc...>' . $yourTemplate->render();
				$contents = str_replace($oldStr, $replaceStr, $contents);	
			}
		}
	}
 

Chris D

XenForo developer
Staff member
#9
Hmmm, it seems that the $contents var is already processed when passed to the hook...

There is another hook inside the thread_list_item template that may be useful : "thread_list_item_icon_key".
Just append the print_r dump of the 'thread' hook param to the $content var :
PHP:
public static function templateHookTest ($hookName, &$contents, array $hookParams, XenForo_Template_Abstract $template)
{
if ($hookName == "thread_list_item_icon_key")
{
$contents = $contents. '<pre>' . print_r($hookParams['thread'], true) . '</pre>';
}
}
This works but the content I'm inserting looks like crap there.

Considering moving the content into the desired location with jQuery but that's tricky as because it's within a foreach loop, targeting the correct element isn't pretty.

Anyone good with jQuery?

Let's say I have, as my content:

<a href="{xen:link mylink, $thread}" class="myUrl"><span class="myLink">My Link</span></a>

And that's being inserted into the thread_list_item_icon_key hook (the $thread parameter is available here because the hook exists within the foreach loop).

If I want to append that link to the nearest <div class="controls faint"> div using jQuery then how would I do that? I know how to append data to jQuery selectors - but as there's one of these divs per thread I need to ensure the data is appended to simply the closest or at least the one that sits in the list element with an ID of #thread-{$thread.thread_id}
 

Chris D

XenForo developer
Staff member
#10
There is one hack possible IMO.

The render inside the foreach loop is a list with each <li> element being identified following this pattern : "thread-$threadId" (e.g.: thread with ID 42 is listed in a <li id="thread-42"> element).

With a couple of lines of code, you could grab all the thread ids from the $threads hook param, and use it to str_replace within the rendered $contents var. This should somehow look a bit like the following (on the fly, untested):
PHP:
 public static function templateHookTest ($hookName, &$contents, array $hookParams, XenForo_Template_Abstract $template)
{
if ($hookName == "thread_list_threads")
{
$threads = $hookParams['thread']; // or $template->getParams('threads'); ?
 
foreach ($threads as $thread)
{
$yourTemplate = $template->create('yourCustomTemplate', array('thread' => $thread));
 
$oldStr = '<li id="thread-'.$thread['thread_id'] . '" etc...>';
$replaceStr = '<li id="thread-'.$thread['thread_id'] . '" etc...>' . $yourTemplate->render();
$contents = str_replace($oldStr, $replaceStr, $contents);
}
}
}
Our posts crossed... I feel my last post would be a more elegant solution if you know any jQuery that might help me out? But failing that, yours might well be a good solution.
 

ManOnDaMoon

Well-known member
#12
Our posts crossed... I feel my last post would be a more elegant solution if you know any jQuery that might help me out? But failing that, yours might well be a good solution.
I agree the "threads_list_item_icon_key" hook is a better place. My proposal is a (quite horrible) hack in case you want to append a template in another place within the thread list item.

Best of all remains template modifications, IMO.
 

Chris D

XenForo developer
Staff member
#14
... you'd better use TMS instead of jquery for this, it would be cleaner:
Code:
                    <xen:include template="your_template">
                        <xen:map from="$thread" to="$your_variable" />
                    </xen:include>
I don't like my add-ons to have dependencies, no matter how good they are :(

If there was a jQuery solution it would look just as good as it would with TMS. Granted, anyone who disables javascript will see the link in its default position which isn't perfect, but I'd actually rather that than to say to people my add-on requires another add-on to work.
 

Chris D

XenForo developer
Staff member
#16
Then why don't you create a post template listener to modify the template "thread_list_item" ? This template has the $thread variable.
I've tried it. Doesn't work.

You can't target the thread_list_item or thread_list template. You can only target the forum_view template and I had problems passing the $thread parameter to that for the same reasons as the first post :(

So annoying for something so simple!

PHP:
	public static function templatePostRender($templateName, &$content, array &$containerData, XenForo_Template_Abstract $template)
	{
		if ($templateName == 'thread_list_item')
		{						
			$content = '';
		}
	}
That should effectively blank the thread_list_item template, right? It doesn't. All present and correct. Same with thread_list.

Yet, if I change the templateName to "forum_view" then the forum view disappears so my listener is set up right. I think template_post_render is only any good on the container templates.
 

ManOnDaMoon

Well-known member
#17
Then why don't you create a post template listener to modify the template "thread_list_item" ? This template has the $thread variable.
admin.php?code-event-listeners/add said:
Called after a template is rendered. Please note that this is only called for templates that are created via the template object directly. Templates that are included via <xen:include> will not trigger this event.
 

cclaerhout

Well-known member
#18
Sorry I need time to understand ^^ What do you want to modify inside the "thread_list_item" ? Do you have a concrete example? For example, what would you do as replacement with TMS?
The solution gave by ManOnDaMoon on post 7 should work.
 

Chris D

XenForo developer
Staff member
#19
Sorry I need time to understand ^^ What do you want to modify inside the "thread_list_item" ? Do you have a concrete example? For example, what would you do as replacement with TMS?
The solution gave by ManOnDaMoon on post 7 should work.
Thanks for taking the time to think about it :)

Here's the desired end result. It really is the most simple thing in the world. Add "MyLink" to the ".controls" div in thread_list_item:


Before any changes, the template code looks like:
Code:
				<div class="controls faint">
					<xen:if is="{$thread.canEditThread}"><a href="{xen:link 'threads/list-item-edit', $thread}" class="EditControl">{xen:phrase edit}</a></xen:if>
					<xen:if is="{$showSubscribeOptions} AND {$thread.email_subscribe}">{xen:phrase email}</xen:if>
				</div>
Desired end result reproduceable either with a direct template edit or TMS:
Code:
				<div class="controls faint">
					<a href="{xen:link mylink, $thread}" class="myUrl"><span class="myLink">MyLink</span></a>
					<xen:if is="{$thread.canEditThread}"><a href="{xen:link 'threads/list-item-edit', $thread}" class="EditControl">{xen:phrase edit}</a></xen:if>
					<xen:if is="{$showSubscribeOptions} AND {$thread.email_subscribe}">{xen:phrase email}</xen:if>
				</div>
To summarise so far:

Can't use thread_list_threads as hook contents are compiled before the foreach loop meaning the $thread variable isn't available.
Can't use template_post_render as can only use that on templates that aren't called via <xen:include, only available one is forum_view and the $thread variable isn't available.
Can use thread_list_item_icon_key but its position sucks and is inconsistent
Can use TMS but would prefer not to.

I haven't tried the suggestion from post #7 yet.
 

cclaerhout

Well-known member
#20
The good new is that you've got a pretty <div class="controls faint"> to target in order to inject code after with a string replacement or a regex.
Do you have a parsed ouput of what {xen:link mylink, $thread} will be ?