Marionette.Behaviors

où comment réaliser une
architecture découplée

C'est quoi déjà Marionette ?

Non ce n'est pas un pantin

s/(.*)/\1.js/

Marionette, c'est ...

  • ... un framework ?

  • ... une librairie ?

Marionette, c'est ...

une surcouche sur Backbone

Backbone, vous connaissez?

Backbone, c'est

  • Router
  • Model / Collection
  • View

Donc Marionette c'est:

  • Router
  • Model
  • View

?

http://marionettejs.com

marionettejs/backbone.marionette

Et que dit le README.md ?

Backbone.Marionette

 

The Backbone framework

 

Marionette is a composite application library for Backbone.js that aims to simplify the construction of large scale JavaScript applications. It is a collection of common design and implementation patterns found in applications.

Marionette is a composite application library for Backbone.js that aims to simplify the construction of large scale JavaScript applications. It is a collection of common design and implementation patterns found in applications.

Marionette brings an application architecture to Backbone, along with built in view management and memory management. It's designed to be a lightweight and flexible library of tools that sits on top of Backbone, providing the framework for building a scalable application.

Like Backbone itself, you're not required to use all of Marionette just because you want to use some of it. You can pick and choose which features you want to use. This allows you to work with other Backbone frameworks and plugins easily. It also means that you are not required to engage in an all-or-nothing migration to begin using Marionette.

Marionette

TL;DR

une collection de patterns pour architecturer une application

arionette

Behaviors

Share complex UI interactions across views.

 

Behaviors are like mixins, without all of the pain associated with property collision.

Que dis la doc ?

A Behavior is an isolated set of DOM / user interactions that can be mixed into any View or another Behavior.

 

Behaviors allow you to blackbox View specific interactions into portable logical chunks, keeping your Views simple and your code DRY.

Donc un behavior, c'est ...

c'est du code ...

 

... isolé,

... qu'on peut partager,

... appliqué à des vues 

 

Donc un behavior,

ça sert pour ...

 

... des manipulations du DOM
... des interactions utilisateurs 

 

Donc un behavior,

Utiliser les behaviors, ça permet ...

 

... d'avoir des vues simples (KISS),

... de ne pas se répéter (DRY)
...en gros c'est (FUN)

Donc un behavior,

En langage technique, c'est:

 

  • un building block
  • du code réutilisable
  • testable
  • de la composition
  • de l'inversion de contrôle

Donc un behavior,

Behavior API

Behavior API

Les propriétés

Behavior API

Behavior API

Marionette.Behavior.extend({
 
  events: {
    click: 'onClick'
  },

  onClick: function (event) {
    // ...
  }

});

Propriétés `ui` et `events`

Marionette.Behavior.extend({
 
  ui: {
    label: '.label'
  },

  events: {
    'click @ui.label': 'onClick'
  }

});

events

ui, events

Behavior API

Marionette.ItemView.extend({
 
  ui: {
    foo: '.foo'
  },

  behaviors: {
    Foo: {
      behaviorClass: behaviors.foo
    }
  }

});

Propriété `ui`

Marionette.Behavior.extend({
 
  ui: {
    label: '.label'
  },

  events: {
    'click @ui.label': 'onClick'
  },

  onClick: function (event) {
    // this.ui = behavior.ui + view.ui
    // {
    //    label: '.label'
    //    foo:   '.foo'
    // }
    this.ui.foo.addClass('bar');
  }

});

view

behavior

Behavior API

Marionette.Behavior.extend({
 
  modelEvents: {
    'change:name': 'onNameChange'
  },

  onNameChange: function (model) {
    // ...
  }
});

Propriétés `modelEvents` et `collectionEvents`

Marionette.Behavior.extend({

  collectionEvents: {
    add: 'onCollectionAdd'
  },

  onCollectionAdd: function (model) {
    // ...
  }
});  

modelEvents

collectionEvents

Behavior API

Propriété `triggers`

Marionette.Behavior.extend({

  triggers: {
    'click .foo': 'click:foo',
    'click @ui.foo': 'click:ui:foo'
  }

});
Marionette.ItemView.extend({

  behaviors: {
    Foo: {
      behaviorClasS: behaviors.foo
    }
  },

  onClickFoo: function (event) {
    // called by triggers 'click:foo'
  },

  onClickUiFoo: function (event) {
    // called by triggers 'click:ui:foo'
  }

});

triggers

view react to trigger

Behavior API

Marionette.Behavior.extend({

  foo: function () {
    // ...
    this.el.classList.add('foo');
  }
});

Propriétés `el` et `$el`

Marionette.Behavior.extend({

  foo: function () {
    // ...
    this.$el.addClass('foo');
  }
});

this.el

this.$el

Behavior API

Marionette.Behavior.extend({

  foo: function () {
    // ...
    this.$('.foo'); // = this.view.$('.foo');
  }
});

Propriété `$`

Behavior API

Marionette.Behavior.extend({

  foo: function () {
    // ...
    this.view.foo('bar');
  }
});

Propriété `view`

Behavior

par l'exemple

Dessine moi un behavior

Dessine moi un behavior


var MyView = Marionette.ItemView.extend({













});

<div class="view">

  <header>

    <button class="button--close">
      <span><!-- --></span>
    </button>

    <h1>It's awesome</h1>

  </header>

  <div class="content">
    
    <form><!-- --></form>

  </div>

</div>

vue

template


var MyView = Marionette.ItemView.extend({
  ui: {
    "closeButton": ".button--close"
  },

  events: {
    "click @ui.closeButton": "close"
  },

  // ...

  close: function() {
    // ...

    this.destroy()
  }
});

var MyView = Marionette.ItemView.extend({
  ui: {
    "closeButton": ".button--close"
  },

  events: {
    "click @ui.closeButton": "close"
  },

  // ...






});

Dessine moi un behavior


var MyView = Mn.ItemView.extend({
  ui: {
    "closeButton": ".button--close"
  },

  events: {
    "click @ui.closeButton": "close"
  },

  close: function() {
    // ...

    this.destroy()
  },

  // ...

});

var CloseButtonBehavior = Mn.Behavior.extend({
  ui: {
    "closeButton": ".button--close"
  },

  events: {
    "click @ui.closeButton": "close"
  },

  close: function() {
    // ...

    this.destroy()
  }
});
var MyView = Mn.ItemView.extend({
  behaviors: {
    CloseButton: {
      behaviorClass: CloseButtonBehavior
    } 
  },

  // ...

});

view

view

behavior

Avant

Après

Behavior

la configuration

Behaviors: la configuration

Où se cache les behaviors ?

Deux méthodes:

fournir la classe lors de l'instanciation du behavior

spécifier la propriété behaviorsLookup()

Behaviors: la configuration


Mn.Behaviors.behaviorsLookup = function () {
  return window.behaviors;
};

Marionette.Behaviors.behaviorsLookup

si vous aimez les variables globales :)

Behaviors: la configuration

// http://marionettejs.com/docs/v2.4.1/marionette.behaviors.html#behaviorclass
define(['marionette', 'lib/tooltip'], function(Marionette, Tooltip) {

  return Marionette.ItemView.extend({
    behaviors: {
      Tooltip: {
        behaviorClass: Tooltip,
        message: "hello world"
      }
    }
  });

});

Utilisation de la propriété `behaviorClass`

Behavior API

Les détails

Behavior API: les détails

  • Life Cycle Methods

  • Paramétrisation

  • Nested behavior

Behavior API

Life Cycle Methods

Behavior API: Life Cycle Methods

Marionette.Behavior.extend({

  onRender: function () {
    // ...
  },

  onShow: function () {
    // ...
  },

  onDestroy: function () {
    // ...
  }
});

Life Cycle Methods d'une vue (1/3)

Behavior API: Life Cycle Methods

Life Cycle Methods d'une vue (2/3)

tout appel à context.triggerMethod('foo:bar') fonctionne:

<Behavior>.onFooBar()

 

avec context = la vue

au-delà de :

onBeforeRender, onRenderonShow, onDomRefresh, ...

 

Marionette.Behavior.extend({

  onFooBar: function () {
    // yeah!
  }
});

Appel à triggerMethod depuis la vue

behavior

Marionette.ItemView.extend({

  foo: function () {
    this.triggerMethod('foo:bar');
  }
});

view

Behavior API: Life Cycle Methods

Life Cycle Methods d'une vue (3/3)

behaviors.foo = Marionette.Behavior.extend({

  onFoo: function () {
    this.view.triggerMethod('bar');
  }
});

Appel à triggerMethod depuis un behavior

behavior

Marionette.ItemView.extend({

  behaviors: {
    Foo: {
      behaviorClass: behaviors.foo
    },
    Bar: {
      behaviorClass: behaviors.bar
    }
  }, 
});

view

Behavior API: Life Cycle Methods

behaviors.foo = Marionette.Behavior.extend({

  onFoo: function () {
    this.view.triggerMethod('bar');
  }
});


behaviors.bar = Marionette.Behavior.extend({

  onBar: function () {
    // yeah!
  }
});

Behavior API

paramétrisation

Behavior API: paramétrisation

Marionette.Behavior.extend({

  defaults: {
    label: 'foo'
  },

  onRender: function () {
    this.options.label;
  }
});

passage d'options ou paramétrage d'un behavior

behavior

Marionette.ItemView.extend({

  behaviors: {
    Foo: {
      behaviorClass: behaviors.foo
    }
  }
});

view

Paramétrage par défaut

Behavior API: paramétrisation

Marionette.Behavior.extend({

  defaults: {
    label: 'foo'
  },

  onRender: function () {
    this.options.label; // foo
  }
});
Marionette.Behavior.extend({

  defaults: {
    label: 'foo'
  },

  onRender: function () {
    this.options.label;
  }
});

passage d'options ou paramétrage d'un behavior

behavior

Marionette.ItemView.extend({
  behaviors: {
    Foo: {
      behaviorClass: behaviors.foo
      label: 'bar'
    }
  }
});

view

Surcharge des paramètres par défaut par la vue

Behavior API: paramétrisation

Marionette.Behavior.extend({

  defaults: {
    label: 'foo'
  },

  onRender: function () {
    this.options.label; // bar
  }
});

Behavior API

nested

// CloseButtonBehavior
Mn.Behavior.extend({

  ui: {
    button: '.button--close'
  },

  events: {
    'click @ui.button': 'close'
  },

  close: function (event) {
    // do something
  }
});

behavior

Mn.ItemView.extend({
  behaviors: {
    CloseButton: CloseButtonBehavior
  },

  // ...

});

view

Behavior API: nested

Demande: ajout d'un effet "touch" sur les boutons

// CloseButtonBehavior
Mn.Behavior.extend({





  ui: {
    button: '.button--close'
  },

  events: {
    'click @ui.button': 'close'
  },

  close: function (event) {
    // do something
  }
});

behavior

Mn.ItemView.extend({
  behaviors: {
    CloseButton: CloseButtonBehavior
  },

  // ...

});

view

Behavior API: nested

// TouchButtonBehavior
Mn.Behavior.extend({
  events: {
    click: 'onClick'
  },

  onClick: function (event) {
    // do something
  }
});

touch behavior

Demande: ajout d'un effet "touch" sur les boutons

// CloseButtonBehavior
Mn.Behavior.extend({

  behaviors: {
    TouchButton: TouchButtonBehavior
  },

  ui: {
    button: '.button--close'
  },

  events: {
    'click @ui.button': 'close'
  },

  close: function (event) {
    // do something
  }
});

Communication
view / behavior

view

behavior

  • this.trigger('foo:bar')

view

behavior

Mn.ItemView.extend({

  behaviors: {
    // ...
  },

  foo: function () {
    this.trigger('foo');
  }
});
Mn.Behavior.extend({

  events: {
    'foo'
  },

  foo: function () {
    this.trigger('foo');
  }
});
  • this.triggerMethod('foo:bar')

view

behavior

Mn.ItemView.extend({

  behaviors: {
    // ...
  },

  foo: function () {
    this.triggerMethod('foo');
  }
});
Mn.Behavior.extend({

  onFoo: function () {
    // do something
  }
});

view

behavior

Exécution d'une methode directement sur la vue: this.view

view

behavior

Mn.ItemView.extend({

  bar: function () {
    alert("I've been called by someone");
  }
});
Mn.Behavior.extend({

  foo: function () {
    this.view.bar()
  }
});

Trigger d'un event:

this.view.trigger

view

behavior

Mn.ItemView.extend({
  
  events: {
    'bar': 'onBar'
  },

  onBar: function () {
    alert("I've been trigger by bar event");
  }
});
Mn.Behavior.extend({

  foo: function () {
    this.view.trigger('bar');
  }
});

Trigger d'une méthode:

this.view.triggerMethod

view

behavior

Mn.ItemView.extend({

  onBar: function () {
    alert("I've been trigger by bar event");
  }
});
Mn.Behavior.extend({

  foo: function () {
    this.view.triggerMethod('bar');
  }
});

Behavior

Exemples

Behavior: exemples

  • boutons

  • formulaire

  • loader

  • toolbar

  • confirmation

  • déclenchement d'animations de vue

  • mise en place d'un slider

  • scroll infini

Behavior: exemples

Exemple d'une toolbar

Behavior: exemples

Exemple d'un bouton de recherche

Behavior: exemples

Exemple d'un bouton de recherche

Behavior: exemples

Différenciation OS

iOS

Android

Behavior: exemples

Différenciation OS : iOS (1/2)

Behavior: exemples

Différenciation OS : Android (2/2)

Behavior: exemples

Différenciation OS : iOS (1/2)

Behavior: exemples

Différenciation OS : Android (2/2)

Behavior: exemples

Mise en place d'un bouton de confirmation

Behavior: exemples

Gestion d'un formulaire complexe

Behavior: exemples

Formulaire

Marionette.LayoutView.extend({

  // ...

  regions: {
    geolocation: '[data-widget="geolocation"]',
    image: '[data-widget="images"]'
  },
   
  ui: {
    iconClose: '.icon--close',
  }

  // ...
});

Behavior: exemples

Gestion d'un formulaire et de l'affichage d'erreurs

Marionette.LayoutView.extend({

  // ...

  behaviors: {
    Form: {
      behaviorClass: behaviors.form
    },
    FormValidationDisable: {
      behaviorClass: behaviors.formValidation
    }
  },
   
  // ...
});

Behavior: exemples

confirmation avant fermeture de la vue

Marionette.LayoutView.extend({

  // ...

  triggers: {
    'click @ui.iconClose': 'cancel'
  },

  behaviors: {
    Confirm: {
      behaviorClass: behaviors.confirm,
      dialog: 'cancel:confirm'
    }
  },
   
  onCancelConfirmed: function () {
    this.trigger('close');  
  },

  // ...
});

Behavior: exemples

Gestion des widgets de geolocalisation et d'ajout d'images

Marionette.LayoutView.extend({

  // ...

  regions: {
    geolocation: '[data-widget="geolocation"]',
    image: '[data-widget="images"]'
  },
   
  ui: {
    iconClose: '.icon--close',
  },

  behaviors: {

    Geolocation: {
      behaviorClass: behaviors.geolocation,
      region: 'geolocation'
    },
    AddImage: {
      behaviorClass: behaviors.addImage,
      region: 'image'
    }
  },
   
  // ...
});

Behavior: exemples

Surchage du formulaires

function (PublishView, rules) {
  'use strict';

  return PublishView.extend({
    formEvents: {
      trigger: 'publish:action',

      listeners: {
        success: 'publish:action:success',
        error: 'publish:action:error'
      }
    },

    rules: rules
  });
});
function (PublishView, rules) {
  'use strict';

  return PublishView.extend({
    formEvents: {
      trigger: 'publish:news',

      listeners: {
        success: 'publish:news:success',
        error: 'publish:news:error'
      }
    },

    rules: rules
  });
});

Formulaire d'action

Formulaire de news

Behavior

Retour d'XP

Behavior: retour d'XP

Faire attention aux collisions de méthodes 

Light

Comment

Share

Behavior: retour d'XP

Faire attention aux collisions de méthodes 

var LightBehavior = Mn.Behavior.extend({

  onSuccess: function () {
    // do something
  },

  onError: function () {
    // do something
  }
});
var ShareBehavior = Mn.Behavior.extend({

  onSuccess: function () {
    // do something
  },

  onError: function () {
    // do something
  }
});

Behavior: retour d'XP

Faire attention aux collisions de méthodes 

var LightBehavior = Mn.Behavior.extend({

  onLightSuccess: function () {
    // do something
  },

  onLightError: function () {
    // do something
  }
});
var ShareBehavior = Mn.Behavior.extend({

  onShareSuccess: function () {
    // do something
  },

  onShareError: function () {
    // do something
  }
});

=> Préférable d'utiliser un namespace

onLight<Action>

onShare<Action>

Behavior: retour d'XP

les instances des behaviors sont disponibles dans une variable _behaviors

À éviter pour un découplage propre

var LightBehavior = Mn.Behavior.extend({
 
  // ...

});

var CommentBehavior = Mn.Behavior.extend({
 
  // ...

});


var ShareBehavior = Mn.Behavior.extend({
 
  // ...

});
var View = Mn.ItemView.extend({
 
  Behaviors: {
    Light: {},
    Comment: {}
    Share: {}
  }

});

var view = new View();

// -> `_behaviors` is an array of 
// behavior instances

view._behaviors

Behavior

Le futur

Behavior: le futur

Uniquement Marionette #major

var Foo = Marionette.Behavior.extend({
  ui: {
    foo: '.foo'
  },

  events: {
    'click @ui.foo': 'onFooClick'
  },

  onFooClick: function () {
    console.log('foo');
  }
});

Behavior: le futur

Uniquement Marionette #major

var SecondView = Marionette.View.extend({
  ui: {
    "foo": ".foo-btn"
  },

  behaviors: {
    Foo: {
      // no options
    }
  }
});
var FirstView = Marionette.View.extend({
  behaviors: {
    Foo: {
      // no options
    }
  }
});

Behavior: le futur

Uniquement Marionette #major

L'icone de fermeture d'une vue peut ainsi être représentée par un même behavior

Stay tuned!

https://slides.com/stephanebachelier/marionettebehaviors

sbachelier

stephanebachelier

Made with Slides.com