4

I have a custom Twig Extension that needs to add a Javascript library ONLY when the Twig Extension is used.

Here is my modules/custom/MY_MODULE/MY_MODULE.libraries.yml:

my-twig-extension:
  version: VERSION
  js:
    js/myTwigExtension.js: {}
  dependencies:
    - core/jquery

Here is my modules/custom/MY_MODULE/js/myTwigExtension.js (just for testing):

console.log("Hello World!!! at " + Date.now());  

In my modules/custom/MY_MODULE/src/TwigExtension.php I define the extension here:

public function getFunctions() {
    return [
        new \Twig_SimpleFunction('MY_MODULE_widget', [$this, 'MY_MODULEWidget']),
    ];
}

and then it's function:

public function MY_MODULEWidget($MY_VARIABLE)
{
    return array(
            '#theme' => 'twig_extension_MY_MODULE_widget',
            '#MY_VARIABLE' => $MY_VARIABLE,
            );
}

The #theme above is correctly loading my file here modules/custom/MY_MODULE/templates/twig_extension_MY_MODULE_widget.html.twig:

<div class="MY_CLASS" data-MY_VARIABLE="{{ MY_VARIABLE | url_encode }}">Loading Widget...</div>

Attempt 0:

Added the following twig function to modules/custom/MY_MODULE/templates/twig_extension_MY_MODULE_widget.html.twig:

{{ attach_library('MY_MODULE/my-twig-extension') }}

but that did not work.

Attempt 1:

Tried adding the library in the render array:

public function MY_MODULEWidget($MY_VARIABLE)
{
    return array(
            '#theme' => 'twig_extension_MY_MODULE_widget',
            '#MY_VARIABLE' => $MY_VARIABLE,
            '#attached' => array(
                'library' => array(
                    'MY_MODULE/my-twig-extension',
                    ),
                ),
            );
}

but it doesn't look like BlockBase logic is supported here to load the library.

Attempt 2:

I looked at how the Twig function attach_library works and saw how in Drupal\Core\Template\TwigExtension::attachLibrary($library) worked and tried to implement it here like this:

public function MY_MODULEWidget($job_name)
{
    static $doOnce = true;
    if ($doOnce) {
        $doOnce = false;

        /* @var $renderer Drupal\Core\Render\Renderer */
        $renderer = \Drupal::service('renderer');
        $template_attached = ['#attached' => ['library' => ['MY_MODULE/my-twig-extension']]];
        $renderer->render($template_attached);
    }

    return array(
            '#theme' => 'twig_extension_adicio_rated_job_widget',
            '#job_name' => $job_name,
            );
}

... with the idea of including the library the first time the Twig Extension was called. But no go.

IMPORTANT: yeah I know I can add the following to my module/custom/MY_MODULE/MY_MODULE.info.yml:

libraries:
  - MY_MODULE/my-twig-extension

but I do not want to load this library (very expensive logic), to every page.

Any assistance would be greatly appreciated. Thank you.

  • Someone with 300+ reputation, please add the twig-extension Tag to this question. – Yzmir Ramirez Oct 27 '16 at 17:55
1

Partial Answer that works

... but I don't like it because the list of paths in the config never gets reset. I think I need to figure out how to do Cache Contexts for a proper answer.

In my modules/custom/MY_MODULE/src/TwigExtension.php file:

class TwigExtension extends \Twig_Extension 
{
   const CONFIG_KEY_NAME = 'MY_CONFIG_NAME';
...
    public function MY_MODULEWidget($MY_VARIABLE)
    {
        /* @var $config Drupal\Core\Config\Config */
        $config = $this->configFactory->getEditable($this->getName() . '.settings');

        $paths = (array)$config->get(self::CONFIG_KEY_NAME);

        /* @var $currentPath string */
        $currentPath = \Drupal::service('path.current')->getPath();        // returns '/node/47446' not the alias

        if (in_array($currentPath, $paths)) {
            // Good
        } else {
            // Add $currentPath to config
            $paths[] = $currentPath;
            $config->set(self::CONFIG_KEY_NAME, $paths)->save();
        }

        return array(
                '#type' => 'inline_template',
                '#template' => '<div class="MY_CLASS" data-MY_DATA="{{ MY_VARIABLE }}">Loading Widget ...</div>',
                '#context' => array(
                    'MY_VARIABLE' => $MY_VARIABLE,
                    ),
                );
    }
}

The above code will add the current path to a config. Unknown if there's an upper limit to the size of the data in the config; however, in my implementation the number of pages that this is on will be small (< 100).

Then once the path is added to the module's config setting then we read it to conditionally add a library like this using hook_page_bottom() like this in my modules/custom/MY_MODULE/MY_MODULE.module file:

/**
 * Implements hook_page_bottom
 */
function MY_MODULE_page_bottom(array &$page_bottom)
{
    /* @var $paths array of strings */
    $paths = (array)\Drupal::config('MY_MODULE.settings')->get(Drupal\MY_MODULE\TwigExtension::CONFIG_KEY_NAME);

    /* @var $currentPath string */
    $currentPath = \Drupal::service('path.current')->getPath();     // like '/node/47446', not the Alias

    if (in_array($currentPath, $paths)) {
        // Include the library that will modify jQuery(".MY_CLASS")
       $page_bottom['MY_MODULE']['#attached']['library'][] = 'MY_MODULE/my-twig-extension';
    }     

}

EDIT 0

Added a hook_build() to my module like this so I could clear the setting manually:

function MY_MODULE_rebuild()
{
    /* @var $config Drupal\Core\Config\Config */
    $config = \Drupal::service('config.factory')->getEditable('MY_MODULE.settings');

    $config->clear(Drupal\MY_MODULE\TwigExtension::CONFIG_KEY_NAME);

    $config->save();

}

Note: $config support fluid interface (e.g. $config->clear(...).

I will still not accept my answer because I still believe I should be using Cache Context for this.

  • I will not accept my answer because the above solution doesn't get cleared when the widget no longer exists on a page (in my case a View). Oh, also notice I used an inline_template instead of creating another .html.twig file. The template used is actually much more complex that what I showed. Technically don't need to add a '#context', I know. – Yzmir Ramirez Oct 30 '16 at 0:47
  • EDIT 0 - added hook_build() – Yzmir Ramirez Oct 30 '16 at 1:48
  • I feel bad accepting my own answer but this is what I went with to production. Its works. – Yzmir Ramirez Nov 15 '16 at 18:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.