How can I use Drupal.behaviors to notify H5P that it resizes itself?

109 views Asked by At

I don't have experience with Drupal. We had migrated from Drupal 7. There was a functionality which sets all elements to display: none; and after clicking on certain buttons parts of the site become visible.

With the switch to Drupal 10 everything has been integrated into Drupal.behaviors.

Now I have to add some H5P elements. Since the first load of the site starts with display: none; the height of the H5P elements becomes 0px.

I found this document describing my troubles.

But I can't make this work in our Drupal.behaviors.

Here is some code which works similar:

(function ($, Drupal) {
  Drupal.behaviors.myModule = {
    attach : function(context, settings) {
      once('these-tabs', 'these_buttons', context).forEach((these_buttons) => {
        $(".switcher_link").click(function () {
          $(".paragraph").hide();
          $(".paragraph").removeClass("activated");
          $(".paragraph-with-h5p-elem").fadeIn();
          $(".paragraph-with-h5p-elem").addClass("activated");
        });
        [...]
      });
    }
  }
})(jQuery, Drupal);

If I click on the button and start the Webdeveloper tools in my browser it works just fine. But if I do not activate the tools it shows no H5P content. So I guess the H5P needs to get a notification that it should resize itself. Which happens during the refresh triggered by the Webdeveloper tools.

I tried to add the following:

          [...]
          $(window).trigger("resize");
          [...]
          [...]
          $(this).trigger("resize");
          [...]

both seem not to notify the H5P handler.

If I try to activate the H5P function described here:

          [...]
          ui.newPanel.css('height', 'auto');
          H5P.jQuery(window).trigger('resize');
          [...]

it just tells me that there is no package "ui" or "H5P" known. They are not part of the namespace. But "importing" them by simply adding })(jQuery, Drupal, H5P); doesn't work either.

When I try:

          [...]
          $(window).reload();
          [...]

it tells me that "reload" is not a function.

The solution seems so simple: Notify H5P that it should resize itself again. But I can't get it to work in Drupal.behaviors yet.

EDIT Thank you, Oliver Tacke. But unfortunately you missed the point. To be more clear:

(function ($, Drupal) {
    Drupal.behaviors.myModule = {
        attach : function(context, settings) {
            once('these-tabs', 'these_buttons', context).forEach((these_buttons) => {
                $(".switcher_link").click(function () {
                $(".paragraph").hide();
                $(".paragraph").removeClass("activated");
                $(".paragraph-with-h5p-elem").fadeIn();
                $(".paragraph-with-h5p-elem").addClass("activated");
                $(document).ready(function () {
                    $(".switcher_link").on("click", function () {
                        console.log("Link for H5P activated!");
                        $(".paragraph-with-h5p-elem").height("1337px");
                        H5P.jQuery(window).trigger('resize');
                    });
                });
            });
        [...]

Doesn't work with Drupal.behaviors. It should resize by clicking the button with class "switcher_link" the second time. The output in the log reads "Link for H5P activated!". So the event listener works as intended. Even the height is set properly. But the video will not be shown.

So my message: "the workaround presented by H5P people doesn't work with Drupal.behaviors" holds true nevertheless.

Meanwhile I had some insights about this: H5P.instances stays empty! It should at least be filled with the object found in H5PIntegration.contents. IMHO that's why it doesn't do anything during H5P.jQuery(window).trigger('resize');.

The remaining questions:

  1. Did I get it right: H5P.instances should contain at least one object? -> I had proven that H5P.instances needs to contain at least one H5P object. Whenever I use H5P outside of the buttons modification it fills H5P.instances. Not in every case: There seem to be exceptions to it. 2. Why does H5P.instances stays empty by using Drupal.behaviors?

EDIT2: By reimplementing this in jQuery without Drupal.behaviors everything is shown except for H5P. Until I change anything in the browser (webdeveloper tools on/off, switching fullscreen in browser, and so on). I thought it doesn't work with Drupal.behaviors in Drupal 10. But I doesn't work with jQuery, too. After this I tested different browsers: Same problem. Afterwards I changed H5P content from interactive video to other content: Everything worked as intended! Switching back: H5P keeps missing. This smells like a bug.

EDIT3: After using console.log(document); console.log(window); in a javascript 'debug' modul I could track down some details.

In drupal/web/modules/h5p/vendor/h5p/h5p-core/js/h5p.js I inserted console.log("H5P.instance.push: ", instance); console.log("H5P.instances: ", H5P.instances); directly after H5P.instance.push(instance); (around line 369). I refer to this by [1]. And in the jQuery from us I inserted the line if (document.readyState === "complete") { setTimeout(() => {console.log("H5P.instances: ", H5P.instances);}, 2000); };. I refer to this by [2].

If I use the Hello World example Greeting card:

[1] "instance" shows:

Object { options: {…}, id: 17, "$": {…}, contentId: 17, libraryInfo: {…} }
​
"$": Object { 0: {…}, length: 1 }
​
contentId: 17
​
id: 17
​
libraryInfo: Object { versionedName: "H5P.GreetingCard 1.0", versionedNameNoSpaces: "H5P.GreetingCard-1.0", machineName: "H5P.GreetingCard", … }
​
options: Object { greeting: "Hello world!", image: {…} }
​
<prototype>: Object { on: on(type, listener, thisArg), once: once(type, listener, thisArg), off: off(type, listener), … }
h5p.js:370:13

and "H5P.instances" shows

H5P.instances:  
Array [ {…} ]
​
0: Object { options: {…}, id: 17, contentId: 17, … }
​
length: 1
​
<prototype>: Array []
h5p.js:371:13

[2] shows:

H5P.instances:  
Array [ {…} ]
​
0: Object { options: {…}, id: 17, contentId: 17, … }
​
length: 1
​
<prototype>: Array []
our_jquery.js:28:36

This resizes without any problems. I even do not need to insert extra code: It resizes automatically by setting the display: block; height: 400px; to parent node.

If I use Single Choice Set:

[1] "instance" shows:

H5P.instances.push:  
Object { contentId: 18, contentData: {…}, userResponses: [], on: on(type, listener, thisArg), once: once(type, listener, thisArg), off: off(type, listener), trigger: trigger(event, eventData, extras), order: (10) […], setBehaviour: setBehaviour(options), setVideo: setVideo(params), … }
h5p.js:370:13

and H5P.instances contains:

H5P.instances:  
Array [ {…} ]
​
0: Object { contentId: 18, contentData: {…}, userResponses: [], … }
​
length: 1
​
<prototype>: Array []
h5p.js:371:13

But [2] shows only:

H5P.instances:  
Array []
​
length: 0
​
<prototype>: Array []
our_jquery.js:28:36

Resize does not work in this case.

My impression is that:

  1. H5P.instances should contain elements because everytime it finds HTML tags with class .h5p-content it fills H5P.instances and H5PIntegration.contents (defined in h5p-integration.js) with different object formats (see lines 58 and 59 in h5p.js). Indeed it does in every tested case but didn't hold for the cases where the resize didn't work. This makes sense because H5P.trigger( in h5p.js is only defined for instance. Verbatim: H5P.trigger(instance, 'resize'); (lines 127, 129, 319, 330, 361, 364, 376, 568, 585, 1011, 1359, 1361, 1391 and 1445).
  2. H5P.instances seems to just keep objects formatted in a certain way. Everything else seems to get erased. The formatting problem of the object arises by getting var instance; ... (line 929 - 969).

I wonder why this happens.

1

There are 1 answers

0
Oliver Tacke On

You seem to be pasting code without actually trying to understand what it does. That's not what I think StackOverflow is supposed to foster. So let's try to help you without giving a free consulting job providing the complete solution ...

When your attach callback is run, the DOM's readyState can still be loading. That means that not all of the page has been built yet, in particular the H5P object may not have been put into the window context. You will need to check for the readyState value and only proceed if it is interactive (at least). Even then, you may still want to check for the H5P object being available if your attach function is not only run for Drupal content that can contain H5P contents.

Once H5P is loaded, the H5P core will listen for resize events on the window and forward them to the H5P content type instances. You have already found a way to trigger such a resize event ($(window).trigger('resize'); or window.dispatchEvent(new Event('resize')); in vanilla JavaScript without relying on a framework such as jQuery). You can learn more about H5P and its resizing mechanism by the way.

You will want to wait until an H5P content instance has been instantiated. Otherwise, triggering a resize event will not have an effect. You can achieve that by using H5P's event dispatcher that will fire an initialized event whenever an H5P content on a page was initialized. You can do this like so:

H5P.externalDispatcher.on('initialized', () => {
  // Do what needs to be done whenever H5P content was initialized ...
});

You mentioned that the container that holds the H5P iframe may not be able to gain height (as the CSS display property was set to none). You will need to make sure that has been changed before triggering a resize. Otherwise, the H5P iframe cannot gain height either even if the content inside resized.

Additionally, you may want to make sure that your whole callback is only run once due to how Drupal uses the attach function. Otherwise you may trigger a resize multiple times which would not break anything, but a resize can be expensive as it may trigger lots of computations.

Hope this will help you to figure out a solution.