Taking Flight

How to Front-end at RetailMeNot

Goals



  1. Introduce You to Flight.js
  2. Guidance for Proper Usage
  3. Try to Live Code Something
  4. Overview of Front-end Stack
  5. Answer Any Questions You Have

Assumptions


  1. Can open Terminal.app
  2. Written some JavaScript / jQuery
  3. Good with Computers



History

RMN 12.07

 define([ 'cookie', 'jquery' ],function( cookie, $ ){

//
// Usage:
// 
// var user = require( 'user' );
// user().done(function( userData ){
//   if( userData ){
//     alert( userData.first_name );
//   }
// });
//

var defer,
    userEndPoint = 'something/something.php';

return function(){

    if( defer ){

        return defer;

    } else if( !cookie( 'user[preferredUsername]' ) ){

        //Provide a consistent API from the module
        // This will result in undefined being passed to callback
        return $.Deferred().resolve();

    } else {

        // Ajax call protected by existance of the cookie variable
        defer = $.ajax( userEndPoint, {
            dataType: 'json',
            xhrFields:{
                withCredentials: true
            }
        });

        return defer;
    }

};

});
user.js

RMN 12.07

define( function() {

// usage: 'singluar'.pluralize(2, 'plural');
if (!String.prototype.pluralize) {
    String.prototype.pluralize = function (/*Int*/ count, /*String?*/ plural) {
        var text = this, lastIdx;

        if (count !== 1) {
            if (plural) return plural; // String

            lastIdx = text.length - 1;
            switch (text[lastIdx]) {
            case 'x':
            case 'h':
            case 's':
                text += 'es';
                break;
            case 'y':
                text = text.substring(0, lastIdx) + 'ies';
                break;
            default:
                text += 's';
                break;
            }
        }

        return text.toString(); //String
    };
}

} );
pluralize.js

12.07

// ...
domReady = function() {
  var $doc = $(document);
      $doc.on('click.selectOffer', '.offer', select);
      $doc.find('.popup_logo').on('click', close);
  
  var $chosen = $doc.find('.offer_list').eq(0).find('.offer').addClass('selected_coupon');
  $doc.on('click', '.title a, .discount a, .merchant-out-link', send2Back);

  // Filter on the selected coupon type
  if ($chosen.hasClass('coupon')) {
    if($chosen.data('freeShipping')) {
      $('#filter_freeship').click();
    } else {
      $('#filter_codes').click();
    }
  } else if ($chosen.hasClass('sale')) {
    if($chosen.data('freeShipping')) {
      $('#filter_freeship').click();
    } else {
      $('#filter_sales').click();
    }
  } else if ($chosen.hasClass('printable')) {
    $('#filter_printables').click();
  }
},
// ...
excerpt from popup.js
commit d14561ee78c0fcf7b4f90b1ff5cb0cf810113485
Author: Evan Dragic <edragic@whalesharkmedia.com>
Date:   Mon Jul 8 14:02:36 2013 -0500

    [RMN-9551] New store page template, v4



Flight


Developed by Twitter's web team

Very light relative to other
frameworks (~29k gzipped)

Requires AMD and jQuery (which we have)

Everything centers around components



COMPONENTS



Basic component

 define(['flight/lib/component'], function (component){

  // The trailing 'UI' is a convention
  // when using flight to signify that
  // this component will be dealing with
  // the DOM in some way.
  function myComponentUI () {

    // Each component has attributes that may be specified when it
    // is initialized. The attributes here will be set by default.
    this.attributes({
      greeting: 'Hello'
    });

    // This will be called when the component is initialized
    this.after('initialize', function () {

      // Bind a handler to the 'greet' event at the document level
      this.on( document, 'greet', this.sendGreeting );
    });

    // This method acts as an event handler for the greeting
    this.sendGreeting = function sendGreeting (ev, who) {
      alert( this.attr.greeting + ', ' + who );
    });
  };

  // Tell flight to build a component from our prototype method
  // This prevents other components from directly referencing this one
  return component( myComponentUI );
});

Using the component

define([
  'jquery',
  'components/my-component-ui.js'
], function (
  $,
  myComponentUI
) {

  // The component will be initialized when it is attached to a root context
  //
  // Keep in mind that a separate component will be attached to each DOM node
  // that matches our selector.
  myComponentUI.attachTo('.greeting-root-context');

  // Now we can send an event to greet err'body
  $(document).trigger('greet', ['everyone']);

});


DEMO
BREAK



mixins

"The Other Inheritance"

Basic Mixin


define(function () {

  // Its just a function!
  function withDivDetection () {

    // Grants +1 DIV-finding 
    this.isADiv = function isADiv (element) {
      return element.tagName === 'DIV';
    };

  }

  return withDivDetection;

});

Using the mixin

define([
  'flight/lib/component',
  'mixins/with-div-detection'
], function (
  component,
  withDivDetection
){

  function selfDestructiveComponentUI () {
    
    this.after('initialize', function () {

      // Use the mixin to check the component root node
      if (this.isADiv( this.node )) {
        console.log('Ka-boom!!');

        // Unbinds the component
        // and removes it.
        this.teardown();
      }

    });
    
  }

  return component(
    withDivDetection,
    selfDestructiveComponentUI
  );

});



Advice

before




// somewhere within a flight component ...
  
  this.meFirst = function meFirst () {
    alert('I\'m first!');
  };

  this.meSecond = function meSecond () {
    alert('I\'m second');
  };


  this.before('meSecond', this.meFirst);

// ... (carry on)

After




// somewhere within a flight component ...
  
  this.meFirst = function meFirst () {
    alert('I\'m first!');
  };

  this.meSecond = function meSecond () {
    alert('I\'m second');
  };


  this.after('meFirst', this.meSecond);

// ... (carry on)


around


// somewhere within a flight mixin ... 
  this.isCrazy = function isCrazy () {
    return true;
  };

  this.callMe = function callMe () {
    alert('Here\'s my number: ' + this.attr.number);
  };


  this.around('callMe', function (callMeMaybe) {    if (this.attr.iJustMetYou && this.isCrazy()) {      callMeMaybe();    }  });

// ... (carry on)

advice
Re: advice




  • Great for drying up nested method calls
  • Avoid for things that should be tightly coupled
  • In general, use sparingly



More info







The Flight Docs


The RMN Docs

projects "in flight"










grunt

Basic gruntfile

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    // Task configuration.
    jshint: {
      options: {
        jshintrc: '.jshintrc',
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib_test: {
        src: ['lib/**/*.js']
      }
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      }
    }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Default task.
  grunt.registerTask('default', ['jshint']);

};

Running grunt





Based on node, so don't forget your `npm install`


Run tasks from the terminal
grunt                 - runs the default task
grunt <task>          - runs the specified task for all targets (e.g. `grunt jshint`)
grunt <task>:<target> - runs the specified task for a given target
                        (e.g. `grunt compass:www`)







custom
tasks


DEMO
BREAK



¿preguntas?


please forward all comments, questions, and stern criticism to jstilwell@rmn.com

Taking Flight

By Jared Stilwell

Taking Flight

A basic introduction to Flight.js and the RetailMeNot front-end stack.

  • 1,180