Skip to content

createbehavior

Mike Byrne edited this page Jan 25, 2022 · 2 revisions

createBehavior is a factory function for defining a new behavior.

Getting Started

Here's an example, where we define an "Accordion" behavior:

// Accordion.js

import { createBehavior } from '@area17/a17-helpers';

const Accordion = createBehavior('Accordion', {
  myCustomFn() {
    // ...
  },
}, {
  init() {
    console.log(this.node); // HTMLElement
    this.myCustomFn();
  }
});

export default Accordion;

Arguments

  1. Behavior name string (Needs to be explicitly defined for usage inside behavior, and also for debugging)
  2. Instance methods Object<string,Function>
  3. Lifecycle Object<string,any> See Lifecycle

Lifecycle

The following lifecycle properties are supported:

  • init Function - Called on accordionInstance.init()
  • destroy Function - Called on accordionInstance.destroy()
  • enabled Function - Called on accordionInstance.enable() (See Breakpoints)
  • disabled Function - Called on accordionInstance.disable() (See Breakpoints)
  • mixins Array<Object> - An array of mixin objects (See Mixins)

Features

Whilst you can still define a behavior as a standard function, using createBehavior enables the following features:

Parsed options

All data attributes on the root node with the following format: data-[BehaviorName]-[myoption] will be captured into the this.options object as this.options.myoption.

<div data-behavior="Accordion" data-Accordion-animate="true"></div>
// Accordion.js
init() {
  console.log(Boolean(this.options.animate)); // => true
}

Note: There are no types for these options, and they are not parsed like jQuery data attributes. (TODO!)

Child querying

Child elements can be queried concisely, like so:

<div data-behavior="Accordion">
  <div data-Accordion-title>My Title</div>
  <div data-Accordion-panel></div>
  <div data-Accordion-panel></div>
</div>
// Accordion.js
init() {
  this.panels = this.getChildren('panel'); // => NodeList
  this.title = this.getChild('title'); // => HTMLElement
}

Breakpoints

Behaviors can be scoped to a breakpoint. This creates opportunities for more generic behaviors, that can differ from instance-to-instance. For example, some instances of the Accordion behavior might only apply to mobile.

The breakpoint is defined by this.options.media, which must be a valid breakpoint alias. (See Configuring breakpoints)

<div data-behavior="Accordion Overlay" data-Accordion-media="small" data-Overlay-media="large">

</div>
// Accordion.js
{
  enabled() {
    // Called when behavior's breakpoint becomes valid (either after init() or on window resize).
    // Also fires after init() if there's no breakpoint defined.
  },
  disabled() {
    // Called when behavior's breakpoint becomes invalid (either after init() or on window resize).
    // Also fires when the behavior is destroyed
  },
}

It's up to each behavior implementation how its state is handled across these lifecycle methods. For example, an Accordion behavior might need to close all the panels when it becomes disabled (for which you'd make use of enabled/disabled; or alternatively, the panels might need to persist on resize (for which you'd keep it in init/destroy).

Configuring breakpoints

Before any breakpoints are instantiated, you must ensure have registered the breakpoint config with createBehavior.setBreakpointConfig:

// app.js
import { createBehavior } from '@area17/a17-helpers';

window.A17.onReady = function() {
  createBehavior.setBreakpointConfig({
    small: { start: null, end: 999 },
    large: { start: 1000, end: null },
  });
};

(The config format is similar to $breakpoints _variables.scss. TODO: automate this?)

Sub-behaviors

Whilst on most occasions, the behavior will be instantiated declaratively from HTML attributes, it's also possible to instantiate from JavaScript:

const OtherBehavior = createBehavior('OtherBehavior', {}, {
  init() {
    this.accordion = new Accordion(this.node, {
      options: {
        animate: true,
      },
      children: {
        title: this.getChild('othertitle'),
        panel: this.getChildren('otherpanel'),
      }
    });
    this.accordion.init();
  },
  destroy() {
    this.accordion.destroy();
  }
});

When constructing a behavior from JS, the arguments are:

  1. The node element to be used as this.node (HTMLElement)
  2. Configuration (Object):
  3. options (Object<string, any>)
  4. children (Object<string, HTMLElement|NodeList>) An object of pre-queried children

There is an more concise syntax if you don't need fine-grained control over the lifecycle:

init() {
  this.accordion = this.addSubBehavior(Accordion, this.node, /** config **/)
}

addSubBehavior will also run its init() immediately, and also run its destroy() whenever the parent behavior is destroyed.

Mixins

Shared functionality can be composed onto a behavior, without it having to be behaviors themselves.

const MyMixin = {
  init() {
    // ... called after the behavior's own init()
  },
  destroy() {
    // ... called after the behavior's own destroy()
  },
  otherFn() {
    // ... any other functions are augmented onto the behavior
  }
};

const Accordion = createBehavior('Accordion', {}, {
  mixins: [MyMixin] // Can contain multiple mixins
});

Use mixins sparingly. Using sub-behaviors instead makes it more reusable overall, as they can also be used by themselves.