1. 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.

Discussion in 'XenForo Development Discussions' started by Chris D, Nov 12, 2012.

  1. Chris D

    Chris D XenForo Developer Staff Member

    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?
     
  2. James

    James Well-Known Member

    Looking at it, what you basically want is to dump each $thread array to get a breakdown of the threads?
     
    Jake Bunce and Chris D like this.
  3. ManOnDaMoon

    ManOnDaMoon Well-Known Member

    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 $hookParamsXenForo_Template_Abstract $template)
        {
            if (
    $hookName == "thread_list_item_icon_key")
            {        
                
    $contents $contents'<pre>' print_r($hookParams['thread'], true) . '</pre>';
            }
        }
     
    Jake Bunce and Chris D like this.
  4. Jake Bunce

    Jake Bunce XenForo Moderator Staff Member

    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 likes this.
  5. Chris D

    Chris D XenForo Developer Staff Member

    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.
     
  6. Jake Bunce

    Jake Bunce XenForo Moderator Staff Member

    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).
     
  7. ManOnDaMoon

    ManOnDaMoon Well-Known Member

    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 $hookParamsXenForo_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);    
                }
            }
        }
     
    Jake Bunce and Chris D like this.
  8. ManOnDaMoon

    ManOnDaMoon Well-Known Member

    NB: I have read somewhere in the XF forums that this method requires to preload the templates at template creation (for better performance ?).
     
    Jake Bunce and Chris D like this.
  9. Chris D

    Chris D XenForo Developer Staff Member

    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}
     
  10. Chris D

    Chris D XenForo Developer Staff Member

    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.
     
  11. ManOnDaMoon

    ManOnDaMoon Well-Known Member

    If you want it selected with jQuery, give it an id="thread-##" using $thread['thread_id'], and use the jQuery selector
    Code:
    $('#thread-42').aJqueryFunction()
    More info : http://api.jquery.com/id-selector/

    EDIT: Oops, misunderstood the question. I am not a jQuery power user, so I don't know exactly how to achieve what you are asking.
     
    Jake Bunce and Chris D like this.
  12. ManOnDaMoon

    ManOnDaMoon Well-Known Member

    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.
     
    Jake Bunce and Chris D like this.
  13. cclaerhout

    cclaerhout Well-Known Member

    ... 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>
    
     
    Jake Bunce and Chris D like this.
  14. Chris D

    Chris D XenForo Developer Staff Member

    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.
     
    Walter likes this.
  15. cclaerhout

    cclaerhout Well-Known Member

    Then why don't you create a post template listener to modify the template "thread_list_item" ? This template has the $thread variable.
     
    Jake Bunce likes this.
  16. Chris D

    Chris D XenForo Developer Staff Member

    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 &$containerDataXenForo_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.
     
    cclaerhout likes this.
  17. ManOnDaMoon

    ManOnDaMoon Well-Known Member

     
    Jake Bunce, cclaerhout and Chris D like this.
  18. cclaerhout

    cclaerhout Well-Known Member

    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.
     
    Jake Bunce likes this.
  19. Chris D

    Chris D XenForo Developer Staff Member

    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:
    [​IMG]

    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.
     
  20. cclaerhout

    cclaerhout Well-Known Member

    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 ?
     
    Jake Bunce likes this.

Share This Page