drupal-javascript-behaviors



drupal-javascript-behaviors

0 0


drupal-javascript-behaviors


On Github danielbeeke / drupal-javascript-behaviors

Drupal ES6 Javascript Behaviors

Daniel Beeke

Developer @ Studio Fonkel

Chapters

  • The page has loaded, what now?
  • Behaving with order in Drupal land
  • The tower of Babel (Javascript v6)
  • Drupal Behaviors + es6 = Superpowers!

Chapter 1

The page has loaded, what now?

Hello World

function helloWorld () {
    alert('Hello World!')
}

Let's say we want to greet the visitor.

body onload

1: <body onload="helloWorld()">
2: body.onload = helloWorld
3: Document.body.addEventListener('load', helloWorld);

  • 1 mixes HTML and Javascript
  • 2 can have only one script
  • 2 & 3 need to be inside the <head>

jQuery ready

$(document).ready(helloWorld)
$(helloWorld)

  • Detached from the HTML
  • Can be in <body>, it will always execute
  • Uses DOMContentLoaded and onload, fires when the DOM is loaded, or when everything is loaded
  • Dependency on the 'big' old jQuery

domready

domready(helloWorld)

  • Detached from the HTML
  • Can be in <body>, it will always execute
  • Uses DOMContentLoaded or IEContentLoaded shim, fires when the DOM is loaded
  • Small dependency
  • AMD plugin structure

Drupal startup

Somewhere in drupal.js

domready(function () { 
    Drupal.attachBehaviors(document, drupalSettings)
})

This starts all the javascript of core, modules and themes.

Chapter 2

Behaving with order in Drupal land

Drupal behavior

(function ($) {

  'use strict';

  Drupal.behaviors.myExample = {
    attach: function (context, drupalSettings) {

    },

    detach: function (context, drupalSettings, trigger) {

    }
  }

})(jQuery)

Let's break that apart!

Namespace

(function ($) {

})(jQuery)
(function ($, $$, _) {

})(jqv1_7, jq3_1, underscore)

  • Makes it possible to using different version of a library.
  • Makes injecting libraries a possible, see jqmulti for an example

use strict

{
    'use strict';
}

  • Better errors and warnings
  • Put this inside the closure or else other scripts may be also set in strict mode

Drupal.behaviors

Somewhere in drupal.js

window.Drupal = { 
    behaviors: {}
}

After downloading all javascript files

Drupal.behaviors.contextualFilters
Drupal.behaviors.wysiwyg
Drupal.behaviors.myExample

Drupal.behaviors is an object, a set of plugins that need to be executed

drupalSettings

A way to ship PHP variables to the javascript frontend. Inside a render array

$build['#attached']['drupalSettings']['my_module']['foo'] = 'bar';

Inside the behavior

attach: function (context, drupalSettings) {}

Attach, Detach

These functions will be called in a couple of situations:

  • On page load
  • On AJAX
  • Quick edit
  • Form API AJAX
  • Your script?

Drupal.attachBehaviors

Drupal.attachBehaviors(document, drupalSettings)

Drupal loads the page, Drupal.behaviors gets filled with attach functions (and some detach functions) On domready all these functions are executed with the drupalSettings variable and the context set to the document

Chapter 3

The tower of Babel (Javascript v6)

ES6 / Javascript 2016

  • The new version of javascript
  • Easier and better
  • Not yet fully supported in all browsers
  • Needs transpiling to work in older browsers

So what is new and what is better?

Arrow functions

() => {}

Example:

this.foo = 'a'

var that = this;

items.forEach(function (item) {
    // this = item
    // that.foo = 'a'
})

No more "var that = this";

this.foo = 'a'
items.forEach((item) => {
    // this == 'a'
})

Arrow functions shorthand

var materials = [
  'Hydrogen',
  'Helium',
  'Lithium',
  'Beryllium'
];

var materialsLength = materials.map(function(material) { 
  return material.length;
});

var materialsLength = materials.map((material) => {
  return material.length;
});

var materialsLength = materials.map(material => material.length);

Block scoped variables

let is properly scoped to the block, like you would expect var to work.

for (let i = 0; i < a.length; i++) { 
    let x = a[i] … 
} 

for (let i = 0; i < b.length; i++) {
    let y = b[i] … 
}

You can swap var with let.

Constants

const PI = 3.141593

Things that can not change in the runtime.

Function parameter defaults

function f (x, y = 7, z = 42) { 
    return x + y + z 
}

No more if (!paramX) { paramX = 'lorem' }

Rest parameter

function f (x, ...y) { 
    console.log(y) // ['a', 'b', 'c']
}


f(1, 'a', 'b', 'c')

Spread operator

let animals = ['dog', 'cat', 'donkey'];
let things = ['computer', ...animals];

console.log(things); // computer, dog, cat, donkey

Template literals

let data = {
    name: 'Henk',
    surname: 'Jansen'
}

let markup = `Hello ${data.name},
is ${data.surname} your last name?`

Classes

class Widget {
    name = ''

    constructor (name) {
        this.name = name;
    }

    myMethod (parameterX) {
        // Method
    }
}

let myWidget = new Widget('my-widget');

Classes inheritance

class GraphWidget extends Widget {
    constructor (name) {
        super(name)
    }

    myMethod () {
        // Improved version
    }
}

Finally classes support,no more hacky frameworks to support class inheritance.

Modules and imports

lib/math.js

export var pi = 3.141593
export function sum (x, y) { 
    return x + y 
}

someApp.js

import * as math from "lib/math" 

console.log("2π = " + math.sum(math.pi, math.pi))

And a lot more

Check it out on http://es6-features.org

Transpiling

The process of compiling the es6 code to the old javascript code.

This way we can use ES6 today in crappy browsers!

Babel

A transpiler that support ES6 and more.

ES6

let animals = ['dog', 'cat', 'donkey'];
let things = ['computer', ...animals];

console.log(things); // computer, dog, cat, donkey

transpiled

var animals = ['dog', 'cat', 'donkey'];
var things = ['computer'].concat(animals);

console.log(things); // computer, dog, cat, donkey

JSPM

A terminal tool to install packages, run babel in the browser and load modules.

  • jspm install jquery
  • jspm install npm:immstruct
  • jspm bundle app/main build.js

It is build on top of systemjs

Chapter 4

Drupal Behaviors + es6 = Superpowers!

How to combine ES6 & Drupal

  • Install Drupal via composer Drupal-project
  • init jspm and configure it to work with Drupal 8
  • Load systemjs and jspm config via module.libraries.yml
  • Monkey patch domready so systemjs is started before Drupal.attachBehaviors

Init jspm & configure it to work with Drupal 8

  • npm init
  • npm install -g jspm@beta
  • npm install --save-dev jspm@beta

jspm config

jspm init

Init mode: Custom
Prefix package.json properties under jspm: Yes
Local package name: Project name
package.json directories.baseURL: web
package.json configFiles.jspm: web/jspm/jspm.config.js
Use package.json configFiles.jspm:dev: No
Use package.json configFiles.jspm:browser: No
Use package.json configFiles.jspm:node: No
SystemJS.config browser baseURL: /
SystemJS.config Node local project path: web/
SystemJS.config browser local project URL to web/: .
package.json directories.packages [web/jspm_packages/]: web/jspm/packages/
SystemJS.config browser URL to web/jspm/packages [jspm/packages/]: jspm/packages/ 
SystemJS.config local package main [drupal-javascript.js]: drupal-javascript.js
SystemJS.config local package format (esm, cjs, amd): esm
SystemJS.config transpiler (Babel, Traceur, TypeScript, None): babel

jspm config edit

Edit jspm.config.js:

You can add your own namespaces, so you can import drupal/my_module/js/script

SystemJS.config({
  paths: {
    "npm:": "jspm/packages/npm/",
    "drupal-custom/": "modules/custom/"
  },

my_module.libraries.yml

Create a module and a libraries file like below:

jspm:
  version: VERSION
  js:
    /jspm/packages/system.js: { weight: -20 }
    /jspm/jspm.config.js: { weight: -20 }
    js/start-es6.js: { weight: -20 }
  dependencies:
    - core/drupal
    - core/drupalSettings

Make sure this library is always loaded:

function my_module_preprocess_page(&$variables) {
  $variables['#attached']['library'][] = 'my_module/jspm';
}

Monkey patch domready

start-es6.js

var realDomready = window.domready;

window.domready = function (callback) {
    SystemJS.import('drupal-custom/my_module/js/es6.js').then(function () {
        realDomready(callback);
    });
};

Now you can use ES6 and Drupal behaviors

Now you can add a javascript file like this one:

import $ from 'jquery';
import _ from 'underscore';

Drupal.behaviors.behaviorWithEs6 = {
    attach: (context, settings) => {

        if (context == document) {
            alert('woop woop')
        }

    }
};

Thanks

Questions?

Drupal ES6 Javascript Behaviors Daniel Beeke Developer @ Studio Fonkel