Wego Engineering

Docs, guidelines and compromising photos

To make changes to these pages, checkout the wego.github.io project and the GitHub Pages docs.

JavaScript style guide for Wego

Everyone has an opinion on how to write JavaScript. Here's ours.

Building block of a prototypical structure

A sample class.

// The following example is an example and not to be taken as actual
// working code.

// Wrap your stuff inside a closure to be `call(this)` later (which usually pass
// in the `window` context.
(function() {

  // Read https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode
  // This is mandatory - future pull requests without this will fail.
  "use strict";

  // You need to bind it a global `Wego` object to be used later.
  var Map = Wego.Map = function(el, options) {
    // Only optional arguments should be passed in as an `options` hash.
    // What is defined as required or not is purely up to the creator of the code.

    // Throw an error if required arguments are not available (should we?)
    if (!el) {
      throw new Error('Cannot create new instance of Map without an element');
    }

    // Prepares the `options` in case it's `undefined`
    options = options || {};

    // `this.$el` should usually be the parent container.
    this.$el = $(el);

    // Prefer `.data` over `.attr` in these cases because `.data` allows you
    // to pass in a JSON object.

    // Try to keep the `data` attributes namespace to this code class so
    // multiple objects can be bound to one element.

    // Optional arguments should have at least one `||` fallback
    this._params = options.params || this.$el.data('map-params');

    // There should always be 2 ways to define options in order:
    // - The `data-*` attribute on the element itself (priority)
    // - Passed in as a hash when calling the constructor
    this._mapId = options.mapId || this.$el.data('map-id');

    // Or something more than 1 `||` fallbacks if needed
    this._instant = options.instant || this.$el.data('map-instant') || false;

    // It's good to specify variables here if it's gonna be used more than once
    // or be overridden by the caller
    this._stateClass = options.stateClass || 'is-closed';

    // Optionally your code should accept callbacks for others to use
    // Prefix callbacks with `on`.
    // `noop` is a function that does nothing when called, perfect fallback
    // for callbacks.
    this._onToggle = options.onToggle || $.noop;

    // Call the initializer
    this._initialize();
  };

  // We chose to put all (public/private) methods as prototypes instead of
  // defining in constructors because of memory issues versus no real
  // gains otherwise.
  _.extend(Map.prototype, {
    // Public methods should appear before private methods so it's easier
    // to find when people want to use this class.
    toggle: function() {
      this._create();
      this.$el.toggleClass(this._stateClass);
      this.refill();

      // This specific callback will be called here. Where you put the callbacks
      // is up to you.
      this._onToggle();

      // If you want your methods to be chainable, add this. Could be useful.
      return this;
    },

    // Since we use fake private methods, all private methods should be
    // prefixed with a `_`. No one outside this class should be calling any
    // methods from here on.
    _initialize: function() {
      // If a variable is used more than once, it's generally good to set a
      // `var` for it to reduce scope lookup.
      var mapId = this._mapId;

      // Do stuffs here that must be called on init.
      this._render();
      this._bind();
    },

    // Since most scripts will require binding events to DOM or otherwise,
    // generally we put them all here, which in turn calls all the other
    // binding methods.
    _bind: function() {
      this._bindToggle();
      this._bindClickExample();
    },

    _bindClickExample: function() {
      var self = this;

      this.$el
        .off('click.map')
        .on('click.map', '[data-map-toggle]', function(e) {
          // Stop browser default behaviors
          e.preventDefault();
          // Can be used to stop pjax (if you need to)
          e.stopPropagation();
          // This can be used in jQuery context to act as both above
          // but avoid using this because it can be confusing.
          return false; // DO NOT DO THIS.
        });
    },

    _bindToggle: function() {
      var self = this;
      this.$el
        // Remember to give your event namespaces so they can be removed if
        // needed without affecting other events.
        .off('toggle.map')
        // Also good to remove prior events before binding new one to avoid
        // event stacking for no reasons.
        .on('toggle.map', function(e) {
          // Then calls the public method for toggling, up to you.
          self.toggle();
        });
    },
  });

  // After a class has been initialized, usually any trigger of events is bound
  // to the DOM the class is associated with, so we send the DOM an event it cannot
  // refuse.
  $(window).on('resize scroll load DOMContentLoaded', _.throttle(function() {
    $('.map').trigger('render.map');
  }, 1000));

// Remember to add this after using `use strict` because strict mode has no
// implicit global/`window`.
}).call(this);

Finally to use the above class:

<div class="elem" data-map-id="123"></div>

// should init with `_mapId = 123`
new Wego.Map($('.elem'));

// should init with `_mapId = 1234`
new Wego.Map($('.elem'), { mapId: 1234 });

// add a callback to be called when toggle is triggered
new Wego.Map($('.elem'), {
  onToggle: function() { console.log('pew'); }
})

Source Code Layout

Naming

Syntax

jQuery

Attribution

Some of the guidelines here are directly taken from Airbnb's JavaScript Style Guide.