timber / timber

Create WordPress themes with beautiful OOP code and the Twig Template Engine
https://timber.github.io/docs/
MIT License
5.53k stars 514 forks source link

Add Twig renderBlock #2944

Open JacobPrice opened 7 months ago

JacobPrice commented 7 months ago

Is your feature request related to a problem? Please describe.

Currently, Timber's render method is designed to render entire templates. While this works well for most use cases, there are scenarios, especially in dynamic and hypermedia-driven applications (e.g., those using HTMX), where rendering only a specific part of a template (a block or fragment) would be more efficient and effective. This is particularly relevant when only a small portion of the page needs to be updated in response to an action, avoiding the overhead of rendering and sending the entire template over the network.

Describe the solution you’d like

I propose adding a new method, render_block. This method would allow developers to render specific blocks within a Twig template, without needing to render the entire template.

A high-level overview of this would look something like this:

    /**
     * Renders a specific block within a Twig template.
     * 
     * @param string $file Path to the Twig template.
     * @param string $block_name Name of the block to render.
     * @param array $data Data to be passed to the block for rendering.
     * @return string|false Rendered content of the block or false on failure.
     */
    public function render_block(string $file, string $block_name, array $data)
    {
        try {
            $twig = $this->get_twig();
            $template = $twig->load($file);

            if (!$template->hasBlock($block_name)) {
                // Handle the error as needed (log, throw exception, etc.)
                return false;
            }

            $output = $template->renderBlock($block_name, $data);

            return $output;
        }
        catch (\Exception $e) {
            // Handle exceptions as needed
            return false;
        }
    }

An additional argument $via_block_render could be added to Timber::compile() :

public static function compile($filenames, $data = [], $expires = false, $cache_mode = Loader::CACHE_USE_DEFAULT, $via_render = false, $via_block_render = null)

Lastly, a conditional would be added to the output within the compile method:

            if($via_block_render){
                $output = $loader->render_block($file, $via_block_render, $data);
            } else {
                $output = $loader->render($file, $data, $expires, $cache_mode);
            }

To make this accessible via the Timber class an additional method could be added called render_block:

    /**
     * Renders a Twig block from a Twig file.
     *
     * Passes data to a Twig file and echoes the output of a specific block.
     *
     * @api
     * @example
     * ```php
     * $context = Timber::context();
     *
     * Timber::render_block( 'index.twig', 'content', $context );
     * ```
     * @param array|string   $filenames      Name or full path of the Twig file to render. If this is an array of file
     *                                       names or paths, Timber will render the first file that exists.
     * @param string         $block_name     The name of the block to render.
     * @param array          $data           Optional. An array of data to use in Twig template.
     * @param bool|int|array $expires        Optional. In seconds. Use false to disable cache altogether. When passed an
     *                                       array, the first value is used for non-logged in visitors, the second for users.
     *                                       Default false.
     * @param string         $cache_mode     Optional. Any of the cache mode constants defined in Timber\Loader.
     */
    public static function render_block(string $template, $block_name, array $data = [], $expires = false, $cache_mode = Loader::CACHE_USE_DEFAULT)
    {
        $output = self::compile($template, $data, $expires, $cache_mode, true, $block_name);
        echo $output;
    }

Describe alternatives you’ve considered

An alternative is to use twig partials and include them. This allows for those specific areas to be rendered as needed, but also leads to a slightly more fragmented development experience making it harder to understand the template as a cohesive unit.

Additional context

The above is a working prototype, but I'm sure it could be improved. I'd be more than happy to take some feedback and submit a PR if this is something Timber would benefit from.

Potentially missing things: Implement Timbers standard practice for handling errors/exceptions Maybe some filters are needed? Maybe this should be in a separate compilation?

https://twig.symfony.com/doc/3.x/api.html#rendering-templates https://htmx.org/essays/template-fragments/

JacobPrice commented 5 months ago

@Levdbas Is this worth making a PR for? It could be a straightforward implementation that could allow for more code organization.

Levdbas commented 5 months ago

Hey @JacobPrice ,

I think this would be a welcome addition but it does needs it's own documentation and tests. Are you willing to provide those as well? We can help out with the tests if that holds you back. Documentation wise I would like to add a guide on how to use this feature in the wild with a platform/framework of choice so it can benefit the whole Timber community. Let me know if you have any questions!

jasalt commented 4 months ago

I would be interested in trying this out and helping to document this feature. With HTMX, I've experimented with https://github.com/svandragt/htmxpress earlier and have used it on Django side with https://github.com/carltongibson/django-template-partials.

JacobPrice commented 4 months ago

@Levdbas @jasalt I will work on this when I get a chance

cbirdsong commented 1 month ago

Coming from the Wordpress side of things, I think this would be better named render_partial or render_fragment since I would expect render_block to have something to do with the block editor.

jasalt commented 1 month ago

With Twig templating it's hard to avoid some confusion with it's "block" concept which is used by plenthora of other templating engines also. Maybe docs could make the difference with it and CMS side block concept more clear.

Seems that django-template-partials separates block and partial concepts however, not sure if that could be applicable to follow here.

jasalt commented 4 days ago

While this is in progress, I did some testing with Timber and HTMX using API for HTMX plugin https://github.com/timber/timber/discussions/3044.