jQuery

Drupal 8, behaviors, and jQuery Once

When porting some front-end code from Drupal 7 to Drupal 8, I ran into an unexpected change in the use of jQuery.once(). In Drupal 7, you’d do this in a behavior:

Code language: JavaScript

Drupal.behaviors.exampleBehavior = {
  attach: function(context, settings) {
    $('.example', context).once('example-behavior', function() {
      // Do stuff.
    });
  },
  detach: function(context, settings, trigger) {
    $('.example', context).removeOnce('example-behavior', function() {
      // Undo stuff.
    });
  }
};

In Drupal 8, however, you can’t pass in a function to jQuery.once(), as the API for that jQuery plugin has changed. It now acts like jQuery.filter(), in that it filters out any elements that have already been processed so they aren’t processed more than once, and returns a jQuery collection. So, in Drupal 8, the example would be:

Code language: JavaScript

Drupal.behaviors.exampleBehavior = {
  attach: function(context, settings) {
    $('.example', context).once('example-behavior').each(function() {
      // Do stuff.
    });
  },
  detach: function(context, settings, trigger) {
    $('.example', context).removeOnce('example-behavior').each(function() {
      // Undo stuff.
    });
  }
};

Using jQuery's queue functions

I needed a simple queue system for a project I’m working on and realized that jQuery already exposed its own:

Queues in jQuery are used for animations. You can use them for any purpose you like. They are an array of functions stored on a per element basis, using jQuery.data(). They are First-In-First-Out (FIFO). You can add a function to the queue by calling .queue(), and you remove (by calling) the functions using .dequeue().

To understand the internal jQuery queue functions, reading the source and looking at examples helps me out tremendously. One of the best examples of a queue function I’ve seen is .delay():

Code language: JavaScript

$.fn.delay = function( time, type ) {
  time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
  type = type || "fx";
 
  return this.queue( type, function() {
    var elem = this;
    setTimeout(function() {
      jQuery.dequeue( elem, type );
    }, time );
  });
};

The default queue - fx

The default queue in jQuery is fx. The default queue has some special properties that are not shared with other queues.

  1. Auto Start: When calling $(elem).queue(function(){}); the fx queue will automatically dequeue the next function and run it if the queue hasn’t started.
  2. ‘inprogress’ sentinel: Whenever you dequeue() a function from the fx queue, it will unshift() (push into the first location of the array) the string "inprogress" - which flags that the queue is currently being run.
  3. It’s the default! The fx queue is used by .animate() and all functions that call it by default.

NOTE: If you are using a custom queue, you must manually .dequeue() the functions, they will not auto start!

Retrieving/Setting the queue

You can retrieve a reference to a jQuery queue by calling .queue() without a function argument. You can use the method if you want to see how many items are in the queue. You can use push, pop, unshift, shift to manipulate the queue in place. You can replace the entire queue by passing an array to the .queue() function.

Quick Examples:

Code language: JavaScript

// lets assume $elem is a jQuery object that points to some element we are animating.
var queue = $elem.queue();
// remove the last function from the animation queue.
var lastFunc = queue.pop();
// insert it at the beginning:
queue.unshift(lastFunc);
// replace queue with the first three items in the queue
$elem.queue(queue.slice(0,3));

[…]

[C]ustom queue example

Run example on jsFiddle

Code language: JavaScript

var theQueue = $({}); // jQuery on an empty object - a perfect queue holder
 
$.each([1,2,3],function(i, num) {
  // lets add some really simple functions to a queue:
  theQueue.queue('alerts', function(next) {
    // show something, and if they hit "yes", run the next function.
    if (confirm('index:'+i+' = '+num+'\nRun the next function?')) {
      next();
    }
  }); 
});
 
// create a button to run the queue:
$("<button>", {
  text: 'Run Queue',
  click: function() {
    theQueue.dequeue('alerts');
  }
}).appendTo('body');
 
// create a button to show the length:
$("<button>", {
  text: 'Show Length',
  click: function() {
    alert(theQueue.queue('alerts').length);
  }
}).appendTo('body');
Tags

Amazon's menu prediction cone

A screenshot of the Amazon menu, with a triangle/cone overlaid demonstrating the region wherein the menu will remain locked to the current item if the pointer doesn't stray outside of it.
A visualization of the mathematical cone that Amazon uses to predict the menu item you're heading for. Cone is not actually visible.

Standard drop-down menus that contain sub-menus very often have no concept of user intent, and this can lead to a repeating frustration that most of us have likely run into: straying off course by even a single pixel can cause the sub-menu to close instantly. Ways around this include adding a delay to try and account for user error, but that doesn’t feel as snappy. Amazon has a really clever solution that accounts for user error yet responds instantly:

At every position of the [pointer] you can picture a triangle between the current mouse position and the upper and lower right corners of the dropdown menu. If the next mouse position is within that triangle, the user is probably moving their [pointer] into the currently displayed submenu. Amazon uses this for a nice effect. As long as the [pointer] stays within that blue triangle the current submenu will stay open. It doesn’t matter if the [pointer] hovers over “Appstore for Android” momentarily – the user is probably heading toward “Learn more about Cloud Drive.”

And if the [pointer] goes outside of the blue triangle, they instantly switch the submenu, giving it a really responsive feel.

So if you’re as geeky as me and think something this trivial is cool, I made a jQuery plugin that fires events when detecting this sort of directional menu aiming: jQuery-menu-aim.

See the source link for more.

Abstraction libraries - Robust Client-Side JavaScript

Every year or so, someone writes an article titled “You do not need jQuery” or “You do not need Lodash”. These articles point out that the native APIs have been improved since or old browsers that prevented the usage of native APIs have died out. That is right, but they often miss the other main goal of libraries.

Libraries provide a concise and consistent API that is an abstraction of several inconsistent browser APIs. For example, using jQuery for traversing and manipulating the DOM, handling events and animation is still more pleasant than using the respective native APIs. This is because jQuery provides an unbeaten abstraction: A list type containing DOM nodes with powerful map, reduce and filter operations. Also, jQuery still deals with browser inconsistencies and tries to level them.

For the sake of robustness, use well-tested, rock-solid libraries. The time, resources and brain power that went into the creation and maintenance of such libraries do not compare to your own solutions.

Quoted content by Mathias Schäfer is licensed under CC BY-SA. See the other snippets from Robust Client-Side JavaScript.

A love letter to jQuery

It hurts me when I hear them say things like “you don’t need jQuery”. They don’t remember how dark it was before your light. We needed you then and we still need you now. I like the way you do things and although the years have passed, for certain tasks, you still do what you do better than anyone else could. I trust you. I know you and you know me. There will always be other ways we could do things, but I know I can rely on you and you’re always there when I need you to be.

More Proof We Don't Control Our Web Pages

I’ve talked about this before: As web designers, we can’t trust the network. Sure, we have to contend with mobile data “dead zones” and dropped connections as our users move about throughout the day, but there’s a lot more to the network that’s beyond our control.

Here’s a roundup of some of my “favorite” network issue related headlines from the last few years:

Some of these issues can be avoided by serving content over HTTPS, but that still won’t enable you to bypass things like firewall blacklists (which led to the jQuery outage on Sky). Your best bet is to design defensively and make sure your users can still accomplish their goals on your site when some resources are missing or markup is altered.

We can’t control what happens to us in this world, we can only control our reaction to it.