Further to http://drupal.org/node/120360, we should introduce a means to register js behaviors.

The issue is that we need to be able to attach behavior to content that's loaded via AJAX. Without the behaviors, content can be broken. E.g., as things stand, collapsible fieldsets on newly loaded and rendered are collapsed (due to the css with the html.js selector) but not expandable (because the collapse behavior has not been reattached).

We can't use the array of attach functions created through $(document).ready() calls because these are reset after being run (when the DOM is first ready).

The attached patch adds a small jQuery extension that allows us to register behaviors and then attach all registered behaviors as needed.

To complete the conversion, we would change our current behavior killswitch code, e.g., from

// Global killswitch
if (Drupal.jsEnabled) {
  $(document).ready(Drupal.uploadAutoAttach);
}

to

// Global killswitch
if (Drupal.jsEnabled) {
  $.registerBehavior(Drupal.uploadAutoAttach);
}

Then, after loading content, we would attach behaviors to the rendered content as follows:

$(elt).attachBehaviors();
CommentFileSizeAuthor
drupal-js-behaviors.patch1.08 KBnedjo

Comments

nedjo’s picture

The patch addresses one part of the problem of how to attach behaviours to newly-loaded content: how to know what behaviours to attach.

But there is a second, trickier part: how to ensure that the needed js is available.

Consider a case where we're loading an inline node editing form on a regular node page. The regular page won't necessarily contain the js files needed for the form's behaviours (let's assume, collapse and upload).

To complicate the issue, it's not just javascript files that are needed. Behaviours may depend on specific css files.

I have a hunch, though, that we can leave this work to modules implementing AJAX behaviours and leave it out of core.

From the side of a module implementing AJAX loading, we have access to the files that need to be loaded. We could do something like:


$scripts = array();
foreach (array('header', 'footer') as $scope) {
  $scripts[$scope] = drupal_get_js($scope);

}

print drupal_to_js(array('scripts' => $scripts));

and something parallel for css.

Here's a rough idea for how we could load scripts in a contrib module:

Drupal.dynamicload.loadScript = function (url) {
  if (!$('#' + url).length) {
  var script = document.createElement('script');
  $(script)
    .attr('type', 'text/javascript')
    .attr('id', url)
    .attr('src', url);
  script.onload = script.onreadystatechange = function() {
    if (script.readyState && script.readyState != 'loaded' && script.readyState != 'complete') {
      return;
    }
    script.onreadystatechange = script.onload = null;
    Drupal.dynamicload.loadCallback(script.id);
  };
  $('head').append(script);
  Drupal.dynamicload.scriptsLoading.push(script.id);
}

Drupal.dynamicload.loadCallback = function (id) {
  if (Drupal.dynamicload.scriptsLoading.length) {
    for (i in Drupal.dynamicload.scriptsLoading) {
      if (Drupal.dynamicload.scriptsLoading[i] == id) {
        delete(Drupal.dynamicload.scriptsLoading[i]);
      }
    }
  }
  if (Drupal.dynamicload.scriptsLoading.length == 0) {
    $(document).attachBehaviors();
  }  
}

We would iterate through the array of scripts to load as passed in JSON from the AJAX handler and dynamically add them to the head element if they're not already there, registering a callback that attaches all registered behaviours when all scripts have loaded.

So I'm thinking that this patch (when completed with the missing pieces, i.e., converting all behaviours to register behaviours rather than directly registering the ready event), together with http://drupal.org/node/120360, should be enough to enable contrib modules to do the rest. Does this seem to make sense?

nedjo’s picture

Status: Needs work » Closed (duplicate)

I've rolled this into http://drupal.org/node/120360.