There is no off-season: NFL.com’s move to YUI

by Ryan Cannon

YUIConf 2011

Or, how the NFL's front-end engineers
got serious about performance,
learned to write JavaScript applications,
and grew a little in the process.

NFL.com Flash Video Players

NFL Fantasy Mobile

Yay! JavaScript

Photo by ElvertBarnes

NFL.com:

Photo by mike 23

2007

…But:

var MyClass = Class.create({
	initialize: function () {
		// the rest is up to you
	}
});
			

We were writing scripts, not building applications.

NFL Web Properties: 2010

NFL.com Fantasy Club Sites
Prototype YUI jQuery

Why not jQuery?

jQuery appeared to cause the same problems we had with Prototype.

Why YUI?

Why YUI?

Home Page 2010

Updates and new features were easier than ever before.

…maybe too easy

Launched: 1 week after home page launch

Super Bowl promo with slide implementation

…maybe too easy

Four states, live data, animation, tabs

Every story needs conflict.

Prototype tricks YUI.

// Prototype 1.6.0.3
// Non-standard array sugar 
Array.prototype.reduce = function() {
  return this.length > 1 ? this : this[0];
};

// YUI
// Standard method defined in JavaScript 1.8
Y.Array.reduce = Array.prototype.reduce ?
   NATIVE_REDUCE :
   CUSTOM_REDUCE;
			

Replace the YUI method with the Prototype version

// Just make sure you `use()` this when Prototype is on the page.
YUI.add("prototype-compat", function (Y) {

  var arrayProto = Array.prototype;

  arrayProto._protoReduce = arrayProto.reduce;
  arrayProto.reduce = function () {
    if (arguments.length) {
	  return CUSTOM_REDUCE.apply(this, arguments);
    } else {
	  return this._protoReduce();
    }
  };

});
			

Clever, huh?

Y.Node.prototype.one = function (sel) {
	var newNode = Element.down(this._node, sel);
	return Y.one(newNode);
};
			

Not quite!

<div id="my-element">
  <div class="child">
    <div class="descendent"></div>
  </div>
</div>
			
// YUI
node.one(">div"); // div.child

// Prototype
Element.down(node._node, ">div"); // div.descendent! 
			

Our current solution

Y.Node.prototype.one = function (sel) {
	var parent  = Element.up(this._node),
	    newNode = parent.down("#" + this.get("id") + " " + sel));
	return Y.one(newNode);
};
			

…but there’s probably a better one!

We found conflicts with:

It’s not all doom and gloom.

YUI().use('event-custom', function (Y) {
	var publisher = new Y.EventTarget();
	Object.extend(document, {
		_fire: Element.Methods.fire.methodize(),
		fire: function (eventName, memo) {
			var evt = document._fire(eventName, memo);
			// publish and fire a YUI custom event
			return evt;
		}
	});
});

Broadcast prototype custom events to YUI.

It’s not all doom and gloom.

var flashVars:Object = this.loaderInfo.parameters,

// these flashVars available to any swf
// embedded with Y.SWF
var yId:String = flashVars.yId;
var swfId:String = flashVars.YUISwfId;
var callback:String = flashVars.YUIBridgeCallback;

ExternalInterface.call("YUI.applyTo", yId, callback, [swfId, {
	type: "flash:frob", // make sure it has a ":"
	memo: { frobs: 7 } // any valid JSON data types
}]);

Fire YUI events from a SWF.

HTML Score Strip

Score Strip: A part of NFL.com since its launch

...but always in Flash

HTML Score Strip (thanks, iPad!)

Avoiding cross-domain security with Loader

YUI({ modules: { "scorestrip-iframe": {
  condition: {
    trigger: "scorestrip",
    test: function (Y) {
      var loc    = Y.config.win.location,
          domain = [loc.protocol, loc.host].join("//");

      return "http://www.nfl.com/" !== domain;
    },
    when: "instead"
  }
}}).use(...);
			

HTML Score Strip (thanks, iPad!)

HTML Score Strip: ATTRS & mutation events

var SSTile = Y.Base.create("scorestrip-tile", Y.Widget, [Y.WidgetChild], {
	bindUI: function () {
		this.after("isRedZoneChange", this._afterRedZoneChange);
	}
}, {
	ATTRS: {
		isRedZone: {
			value: false
		}
	}
});

HTML Score Strip (thanks, iPad!)

initializer: function(config) {
	this.publish("update", {
		broadcast: 2
	});
}

Pro Bowl Ballot

What YUI Gave Us

How to talk about architecture

var MyClass = Class.create({
	initialize: function () {
		// the rest is up to you
	}
});
			

How to really talk about architecture

var MyClass = Y.Base.generate("my-class", Y.Widget, EXTENSIONS, {
	// prepare non-DOM elements
	initializer: function (config) {},
	// create DOM
	renderUI: function() {},
	// set event listeners
	bindUI: function() {},
	// set up UI based on current state
	syncUI: function() {},
	// clean up after yourself
	destructor: function () {}
}, {
	// properties that require mutation events
	ATTRS: {},
	// properties to be parsed from HTML
	HTML_PARSER: {}
});

How to architect CSS

/* set up positioning, metrics, etc. */
.yui3-my-class {}
/* set background, padding, borders */
.yui3-my-class-content {}
/* handle the hidden state */
.yui3-my-class-hidden {} 
/* handle the focused static */
.yui3-my-class-focused {}
/* namespace your internal DOM */
.yui3-my-class-heading {}
.yui3-my-class li {}
			

Applications, Not Scripts

Applications, Not Scripts

bindUI: function () {
  var header = this.get("boundingBox").one(".header");
  header.on("click", this._onHeaderClick, this);
  this.after("openedChange", this._afterOpenedChange);
},
_onHeaderClick: function (event) {
  event.halt();
  this.set("opened", !this.get("opened"));
},
_afterOpenedChange: function (event) {
  if (event.newVal) {
    // change the view to opened state
  } else {
    // change the view to closed state
  }
}

How to combine assets

Minify: http://code.google.com/p/minify/

var YUI_config = {
	comboBase: "http://www.nfl.com/yui/combo/index.php?b=yui3&f=",
    comboSep: ",",
    combine: true,
    maxURLLength: 1000,
	groups: {
		nfl: {
			base: "http://j.nflcdn.com/scripts/",
			combine: false,
			modules: { /* NFL module definitions */ }
		}
	}
};

To date, NFL developers have defined 88 custom modules.

What we still have to figure out

Conditions with module dependencies

YUI({ modules: {
  "video-swf" : {
    condition: {
      test: function (Y) {
	    return Y.SWFDetect.hasFlashVersion(10, 2, 0);
	  }
    }
  }
}});
			

CSS Combination & Image Paths

<!-- direct reference -->
<link href="/audio-player/assets/skins/sam/audio-player.css" />

<!-- combined -->
<link href="/combo/?f=audio-player/assets/skins/sam/audio-player.css" />	
			

Relative paths get confused.

What we still have to figure out

Thank you to the
YUI team.

Thanks for listening!

©2011 Ryan Cannon