Taking Flight
How to Front-end at RetailMeNot
Goals
- Introduce You to Flight.js
- Guidance for Proper Usage
- Try to Live Code Something
- Overview of Front-end Stack
- Answer Any Questions You Have
Assumptions
- Can open Terminal.app
- Written some JavaScript / jQuery
-
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;
}
};
});
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
};
}
} );
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();
}
},
// ...
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
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
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