2

I'm creating a custom module to execute some JS AFTER the cache is cleared. Here is the structure of the module:

Module -> flush.info.yml

name: Flush
type: module
description: A very important module.
package: Custom
version: 1.0
core_version_requirement: ^8 || ^9

Module -> flush.libraries.yml

flush:
  version: 1.x
  js:
    js/flush.js: {}
  dependencies:
    - core/jquery
    - core/drupalSettings

Module -> flush.module

<?php

use Drupal\Core\Form\FormStateInterface;

/**
 * Implementation of hook_cache_flush()
 */

  function flush_cache_flush() {
    \Drupal::state()->set('flush_cache_cleared', TRUE);
  }

  function flush_page_attachments(array &$attachments) {
    if (\Drupal::state()->get('flush_cache_cleared')) {
      $attachments['#attached']['library'][] = 'flush/flush';
     \Drupal::state()->set('flush_cache_cleared', FALSE);
    }
  }

Module -> JS -> flush.js

(function ($, Drupal, drupalSettings) {
    'use strict';

    Drupal.behaviors.flush = {
      /**
       * Drupal attach behavior.
       */
      attach: function (context, settings) {
        this.settings = this.getSettings(settings);
        alert("flush!");
        console.log ("Hello World");
      },
    };
  })(jQuery, Drupal, drupalSettings);

Both functions in flush.module work properly, however the JS that is #attached which is simply supposed to use an alert() never runs. I believe the issue is related to this line specifically : $attachments['#attached']['library'][] = 'flush/flush';

Is there something obvious I'm doing wrong? I used page_attachments as the method to attach the JS because it does not necessarily have to be associated with a specific part of the page, or a render array, etc. Please let me know if you have any potential ideas as to what the issue could be, thanks!

13
  • What happens if you run it without the if statement in hook_page_attachments?
    – sonfd
    Commented Mar 27, 2022 at 15:23
  • @sonfd The JS still doesn't work. I know the if statement triggers as intended because if I echo the JS alert() command inside of it, it triggers. It's only when I try to attach the JS via the library that nothing happens.
    – Joseph
    Commented Mar 27, 2022 at 15:38
  • Did you clear the cache after adding that code?
    – Jaypan
    Commented Mar 27, 2022 at 17:12
  • @Jaypan Yeah, clearing the cache works and if I add JS into the function flush_page_attachments() it will work as intended (i.e. an alert when the cache is cleared) however using a library which is the correct way of doing it doesn't work.
    – Joseph
    Commented Mar 27, 2022 at 17:46
  • 1
    It is perfectly valid to attach a library inside hook_page_attachments() like you are trying to do - I have working code that does that. I don't see anything wrong with your hook_page_attachments(). BUT if you have added or changed flush.libraries.yml after you enabled your flush module, then the new libraries file might not be picked up. Try uninstalling and re-installing your module.
    – anonymous
    Commented Mar 27, 2022 at 18:09

2 Answers 2

2

I wouldn't use Drupal::state() or hook_page_attachments(), those are too static. Better use a session value and a block, see my comment in the previous question.

Set the session value in the hook when the cache is cleared and only if not running from the command line:

function flush_cache_flush() {
  if (PHP_SAPI !== 'cli') {
    \Drupal::request()->getSession()->set('run_flush_js', TRUE);
  }
}

In the block check the session value and remove it so that it doesn't persist after the redirect.

src/Plugin/Block/FlushBlock.php

<?php

namespace Drupal\flush\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a flush block.
 *
 * @Block(
 *   id = "flush_flush",
 *   admin_label = @Translation("Flush"),
 *   category = @Translation("Custom")
 * )
 */
class FlushBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $session = \Drupal::request()->getSession();  
    $build = [];
    if ($session->get('run_flush_js')) {
      $session->remove('run_flush_js');
      $build['content'] = [
        '#markup' => '<div class="run-flush-js"></div>',
        '#attached' => ['library' => ['flush/flush']],
      ];
    }
    $build['#cache']['max-age'] = 0;
    return $build;
  }
}

js/flush.js

(function ($, Drupal, once) {
  'use strict';

  Drupal.behaviors.flush = {
    /**
     * Drupal attach behavior.
     */
    attach: function (context, settings) {
      if (once('flush', '.run-flush-js', context).length) {
        alert("flush!");
      }
    }
  };
})(jQuery, Drupal, once);

flush.libraries.yml

flush:
  js:
    js/flush.js: {}
  dependencies:
    - core/drupal
    - core/jquery
    - core/once

Edit: Update js code, see How can I make this JavaScript code get executed when the BigPipe module is enabled?

5
  • hook_page_attachments() is used to conditionally add attachments to a page before it's rendered, and the state API is used to store site state values. The OP isn't doing anything wrong in using them. The suggested alternative has its cons: A session value is per user and a block needs to be added to a theme region (which also means that changing theme could cause the library not to be added).
    – avpaderno
    Commented Mar 28, 2022 at 9:40
  • Then, using a block and a session value won't resolve the issue the OP is noticing. It could be the OP edited the .libraries.yml file after installing the module, or the OP is editing the wrong .libraries.yml file (which could also happen because there are two copies of the same module); in both the cases, changing the way the library is added to the page doesn't fix the real issue.
    – avpaderno
    Commented Mar 28, 2022 at 9:49
  • 1
    I think you need a session, without you run the javascript on a random client, whoever is requesting the first page after the cache is cleared. There are other ways than a block, but it's probably the quickest way because you can use the system how Drupal renders pages, This works even with BipPipe. I've tested it with and without and you can see how this effects the page loading.
    – 4uk4
    Commented Mar 28, 2022 at 10:02
  • 1
    The OP didn't say he wanted to run the JavaScript code for the same user who caused the cache to be cleared; if that were the case, though, using a session value isn't necessary, as the user who caused the cache to be cleared would be visiting at least two different pages or the same page twice. Even supposing the OP wanted to be sure to add the JavaScript code to the page requested by the user who caused the cache to be cleared (which doesn't make sense, since the cache is cleared for every user), it would be sufficient to store the user ID using the state API.
    – avpaderno
    Commented Mar 28, 2022 at 11:30
  • That's make sense because the user who was logged in when the cache was cleared is a state value for the site, not a session value.
    – avpaderno
    Commented Mar 28, 2022 at 11:32
1

I think you should approach this differently. Here's what I would do:

First, implement hook_cache_flush() and use it to store the last cache flush time to Drupal state.

function flush_cache_flush() {
  \Drupal::state()->set('flush_cache_cleared', time());
}

Next, use hook_page_attachments() to always attach your javascript on every page, but pass the the cache cleared timestamp to your javascript.

function flush_page_attachments(array &$attachments) {
  $attachments['#attached']['library'][] = 'flush/flush';
  $attachments['#attached']['drupalSettings']['flush_cache_cleared'] = \Drupal::state()->get('flush_cache_cleared');
}

Last, in your javascript, check if the new cache cleared time is more recent than the previous (you'll need to store this on the client side via local storage or something).

If the times are different, you know the cache was cleared.

(function ($, Drupal, drupalSettings) {
  'use strict';

  Drupal.behaviors.flush = {
    attach: function (context, settings) {

      let cacheFlushedTime = drupalSettings['flush_cache_cleared'];
      // Do stuff with the cache_flushed_time here, like
      // compare it to a previously known cache flushed time...

    },
  };
})(jQuery, Drupal, drupalSettings);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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