(function() { var s = msnbc.namespace('msnbc.document.settings'); s.selectors = { TITLE: 'h2:first', CONTENT: '.module-content:last', STACKED_MODULE: 'stacked-module', EXPAND_TOGGLE: 'h2:first' }; s.classes = { EXPANDED: 'expanded' }; s.DEFAULT_TEXT_ID = "default-story-text"; s.DEFAULT_TEXT_SHIM_ID = "default-text-shim"; s.DEFAULT_FOOTER_ID = "default-footer"; s.DEFAULT_COOKIE_NAME = 'sv2'; //msnbc.disable({ // "VideoPlayer" //}); })(); ///////////////////////////////////////////////////////////// // QUILT CONTROLLER (function() { var $_Core = msnbc.namespace('msnbc.core'); var $_Selectors = msnbc.document.settings.selectors; $_Core.QuiltController = $_Core.EventDispatcher.extend({ init: function() { this._super(); this.modules = []; this.containers = []; this.mainModules = []; this.moduleInterfaces = {}; this.modulesByName = {}; this.modulesByID = {}; this.adManager; this.urlManager; this.cookieMgr; this.selector; this.openOnScroll = []; }, render: function(selectr, data, moduleNS) { moduleNS = moduleNS || 'msnbc.modules'; var self = this; this.selector = selectr; this.openOnScroll = []; // special case: if present, replace default text shim with default text slice $('#'+msnbc.document.settings.DEFAULT_TEXT_SHIM_ID).replaceWith($('#'+msnbc.document.settings.DEFAULT_TEXT_ID)); var count=1; $(selectr).each(function(i) { var ele = this; // find matching data var mdata; if (ele.id == msnbc.document.settings.DEFAULT_TEXT_ID) { mdata = { type: "DefaultText", open: "select", autoplay: "false", content: [] }; } else if (ele.id == msnbc.document.settings.DEFAULT_FOOTER_ID) { mdata = { type: "MoreNews", open: "select", autoplay: "false", content: [] }; } else if (data[count][ele.id]) { mdata = data[count][ele.id]; count++; var newid = $(this).attr('id') +'-'+ i; $(this).attr('id', newid ); this.id = newid; } else { mdata = data[++count][ele.id]; count++; var newid = $(this).attr('id') +'-'+ i; $(this).attr('id', newid ); this.id = newid; } if (!mdata) { msnbc.log('[QuiltController::render] no data assigned to matched element #' + this.id, true); return; } mdata.name = this.id; var container = msnbc.containers.Factory(this); var module = msnbc.modules.Factory(mdata, container, moduleNS); // MODULE LISTENERS module.addListener("contextChange", function(params) { self.onContextChange(params); }); // low-level interface self.registerInterface(module, module.__getInterface()); // module-level interface self.registerInterface(module, module.getInterface()); self.containers.push(container); self.modules.push(module); self.modulesByID[this.id] = module; if ($(container.slot).hasClass('.' + $_Selectors.STACKED_MODULE)) { container.setIndex(i); self.mainModules.push(module); if (!container.isOpen) { // and more properties ... self.openOnScroll.push(container); } } }); // end data/shim .each() $(window).bind("scroll resize", self.onScroll); // force scroll event this.onScroll(); this.dispatchEvent("onRender"); }, makeNav: function(selectr) { var slot = $(selectr + ':first'); var cont = new msnbc.containers.CollapsibleContainer(slot); var nav = new msnbc.modules.StoryNav(this.mainModules, cont); this.registerInterface(nav, nav.getInterface()); return nav; }, makeFloatingNav: function(selectr, instance) { var slot = $(selectr + ':first'); var cont = new msnbc.containers.CollapsibleContainer(slot); var nav = new msnbc.modules.FloatingNav(this.mainModules, cont); this.registerInterface(nav, nav.getInterface()); return nav; }, reorderModules: function(firstModules) { var self = this; for (var i = firstModules.length - 1; i >= 0; i--) { var m = firstModules[i]; var c = m.__container; // shouldn't have non-collapsible containers in here if (!c.isCollapsible) return; // insert this one before the first stacked-module var $first = $('div.' + $_Selectors.STACKED_MODULE + ':first'); if ($first.attr("id") != c.slot.id) { $first.before(c.slot); } if (!c.isOpen) { c.open(); }; /*clean up classes - tf*/ $(c.slot).find('.module-header').removeClass('i2'); $first.find('.module-header').removeClass('i1'); }; // reset indexes this.mainModules = []; $(this.selector).each(function(j) { if ($(this).hasClass('.' + $_Selectors.STACKED_MODULE)) { var m = self.modulesByID[this.id]; self.mainModules.push(m); m.__container.setIndex(j); } }); }, applyURLCommands: function(commands) { // array of URLCommands if (!commands) return false; var moduleOrder = []; for (var i = 0; i < commands.length; i++) { var c = commands[i]; var fn; var module = this.modulesByName[c.module]; if (module) { moduleOrder.push(module); fns = this.moduleInterfaces[c.module][c.command]; if (!fns) { // console.log('no function'); return; } var args = {}; jQuery.each(c.args, function() { for (var k in this) { args[k] = this[k]; } }); try { jQuery.each(fns, function() { this.call(module, args); }); } catch(e) { msnbc.warn('[quilt::applyURLCommands] error calling command'); msnbc.warn(e); } } }; if (moduleOrder.length) this.reorderModules(moduleOrder); return true; }, applyCookieCommands: function(commands) { var self = this; if (!commands) return false; var globalCommands = msnbc.namespace('msnbc.document.settings.globalCookieCommands'); for (var n in commands) { var c = commands[n]; if (n === 'gx') { // capture global commands jQuery.extend(globalCommands, c.commandsByName); } else { // module commands var module = this.modulesByName[c.module]; if (module) { jQuery.each(c.commandsByName, function(command, arg) { var fns = self.moduleInterfaces[c.module][command]; if (!fns) return true; // continue; try { jQuery.each(fns, function() { this.call(module, arg); }); } catch(e) { msnbc.warn('[quilt::applyCookieCommands] error calling command'); msnbc.warn(e); } }); }; } }; }, registerInterface: function(module, ntrfc) { var self = this; if (!ntrfc.names || !ntrfc.commands) { // interface object not defined correctly return; } var names = ntrfc.names; var commands = ntrfc.commands; // take care of option to pass string value for "names" key if (typeof(names) == "string") names = [names]; jQuery.each(names, function() { var ntr = msnbc.namespace(this, self.moduleInterfaces); self.modulesByName[this] = module; for (var c in commands) { if (!ntr[c]) ntr[c] = []; ntr[c].push(commands[c]); } }); }, onScroll: function() { var self = msnbc.document.quilt; // hack out of jquery callback scope jQuery.each(self.openOnScroll, function() { if (!this.isOpen && !this.forceClosed && msnbc.utils.isInViewport(this.slot, -50)) { this.open(); var curr = this; self.openOnScroll = jQuery.grep(self.openOnScroll, function(n, i) { return curr.id != n.id && !n.isOpen; }); } if (!self.openOnScroll.length) { $(window).unbind("scroll", self.onScroll); }; }); }, setAdManager: function(mgr) { this.adManager = mgr; }, setURLManager: function(mgr) { this.urlManager = mgr; }, setCookieManager: function(mgr) { this.cookieMgr = mgr; }, onContextChange: function(command) { // make sure it's a urlcommand if (!command.toURLString) return; this.urlManager.updateLocation(command); }, onSettingsChange: function(moduleName, commands) { if (!command.toPartString) return; this.cookieMgr.addCookieCommand(msnbc.settings.DEFAULT_COOKIE_NAME, moduleName, commands); }, toString: function() { return "[QuiltController object]"; } }); })(); ///////////////////////////////////////////////////////////// // CONTAINERS (function() { var $_Containers = msnbc.namespace('msnbc.containers'); var $_Selectors = msnbc.document.settings.selectors; $_Containers.Factory = function(slot) { // if we define more types of containers, we'll need to handle them here var c; if ($(slot).hasClass(msnbc.document.settings.selectors.STACKED_MODULE)) { c = new $_Containers.CollapsibleContainer(slot); } else { c = new $_Containers.Container(slot); } return c; }; $_Containers.Container = msnbc.core.EventDispatcher.extend({ init: function(slot) { this._super(); this.id = slot.id; this.slot = slot; this.colSpan = this.getSpan(slot); this.title = $(slot).find($_Selectors.TITLE); this.markup = $(slot).find($_Selectors.CONTENT); }, setIndex: function(index) { var cn = $(this.title).parent().attr('class'); if (cn) { cn = cn.replace(/index-\d+/, ''); $(this.title).parent().attr('class', cn); } $(this.title).parent().addClass('i' + (index + 1)); }, applyLayout: function(selectr, type, floatnav) { var isGridDef = /\d(?:\.\d)??-/; var spanClasses = []; var columns = []; var outerSpan = this.getSpan(selectr); if (outerSpan < 1) { msnbc.warn('element isn\'t layout-ready'); return false; } type = type.toString(); if (type.match(isGridDef)) { // we're dealing with grid syntax, e.g. "2-1-1" spanClasses = type.split('-'); // todo: make sure the layout is actually valid (i.e. can fit in) for the selectr'ed element } else { // just a number of cols to distribute over var numSpans = parseInt(type); for (var i = 0; i < numSpans; i++) { var colspan = (function() { var remainder = outerSpan % numSpans; if (remainder == 1) { // 3 teases, return 2 cols if first element if (i == 0) return 2; // otherwise 1 col return 1; } // 4 teases if (remainder == 2) return '1p5'; // no remainder return even split return outerSpan / numSpans; })(); spanClasses.push(colspan); }; // end for } //end else //msnbc.log(spanClasses); // now make the structure based on array of col width classes jQuery.each(spanClasses, function(j) { // first item may need explicit class var first = (j == 0) ? 'first': ''; // TODO: find a more generic way to get the last-created element... var colwdth = (floatnav) ? colwidth = 'x0p5' : colwdith = 'x' + this; var col = $(selectr).append('
').children('div:last'); columns.push(col[0]); }); return columns; }, getSpan: function(selectr) { var span = -1; // get column span for this container // assumes parent markup defines col span with class 'q-width-define x[n]', returns n var cn = $(selectr).closest('div.q-width-define').attr('class'); if (cn) { var cspan = cn.match(/x(\d)/); if (cspan) span = parseInt(cspan[1]); } if (span == -1) msnbc.warn('[container::getSpan] couldn\'t find width definition with ' + selectr); return span; } }); $_Containers.CollapsibleContainer = $_Containers.Container.extend({ init: function(slot) { this._super(slot); this.isCollapsible = true; this.isOpen = false; this.forceClosed = false; var self = this; $(slot).find('h2:first .icon').click(function() { self.click(this); }); this.makeIcon(); }, click: function(e) { var self = this; if ($(this.slot).hasClass(msnbc.document.settings.classes.EXPANDED)) { this.close(); } else { this.open(); } }, open: function() { $(this.slot).addClass(msnbc.document.settings.classes.EXPANDED); self.isOpen = true; this.dispatchEvent("open", this, {}); }, close: function() { $(this.slot).removeClass(msnbc.document.settings.classes.EXPANDED); self.isOpen = false; this.dispatchEvent("close", this, {}); }, checkOpen: function() { if (this.forceClosed) this.close(); if ($(this.slot).hasClass(msnbc.document.settings.classes.EXPANDED) && !this.forceClosed) { this.open(); return true; } return false; }, makeIcon: function() {} }); })(); ///////////////////////////////////////////////////////////// // MODULES (function() { var $$ = msnbc.namespace('msnbc.modules'); $$.Factory = function(data, container, ns) { var m; var clazz = msnbc.namespace(ns + '.' + data.type); if (clazz.prototype) { m = new clazz(data, container); } else { // default class msnbc.warn('used abstract module class for type: ' + data.type); m = new $$.Module(data, container); } return m; }; $$.Module = msnbc.core.EventDispatcher.extend({ init: function(data, container) { this._super(); this.__rendered = false; this.__components = []; this.__container = container; this.__type = data.type; this.__defaultSetup(container.markup); this.setup(data, container); this.__defaultBehaviorSetup(data, container); }, __defaultBehaviorSetup: function(data, container) { var self = this; if (container.isCollapsible) { $(container.slot).addClass('t-' + data.type); // ALWAYS DEFINE CALLBACK AS ANON FN, WITH SELF REFERENCE // OTHERWISE BIG SCOPING PROBLEM IN CALLBACK CONTEXT container.addListener("open", function() { self.__onOpen(); self.render(); }); container.addListener("close", function() { self.__onClose(); }); this.addListener("rendered", function() { self.__onRender(); }); container.checkOpen(); } else { this.render(); } }, __defaultSetup: function(markup) { // find ads this.ads = msnbc.document.adManager.getAdsIn(markup); }, setup: function() { msnbc.warn("[msnbc.modules.Module::setup] WARNING: abstract method not overridden in subclass"); }, render: function() { msnbc.warn("[msnbc.modules.Module::render] WARNING: abstract method not overridden in subclass"); this.dispatchEvent("rendered", this, {}); }, getInterface: function() { msnbc.warn("[msnbc.modules.Module::getInterface] WARNING: abstract method not overridden in subclass"); return {}; }, getNavTease: function() { msnbc.warn("[msnbc.modules.Module::getNavTease] WARNING: abstract method not overridden in subclass"); return undefined; }, __handleCookieCode: function(code) { var self = this; var c = this.__container; if (code == 1) { c.forceClosed = false; return; } if (code == 0) { c.forceClosed = true; c.checkOpen(); var msg = (this.getNavTease() && this.getNavTease().icon) ? msg = this.getNavTease().icon : msg = "this area"; $(this.__container.slot).append(' Collapse ' + msg + ' for each story in this special section?'); $(this.__container.slot).find('span.rusure input') .addClass('t-' + this.__type + ' slice-hidden') .click(function(e) { //e.preventDefault(); msnbc.document.cookieMgr.addCookieCommand(msnbc.document.settings.DEFAULT_COOKIE_NAME, self.SHORT_NAME, 1); self.__container.forceClosed = false; $(this).parent().remove(); c.open(); try { $A.Track(this); } catch(e) { msnbc.log(e); msnbc.log("omniture tracking error"); } }); } }, __getInterface: function() { // low-level interface var ntrfc = {}; if (this.SHORT_NAME) { ntrfc = { names: this.SHORT_NAME, commands: { cookie: function(code) { this.__handleCookieCode(code); } } }; } return ntrfc; }, addComponent: function(name, params) { var c = msnbc.components.Factory(name, params); c.__module = this; this.__components.push(c); return c; }, __onRender: function(context, params) { this.__rendered = true; }, __onOpen: function() { $(this.__container.slot).find('.rusure').remove(); }, __onClose: function() { var self = this; // trigger handler for scroll into view stuff //$(window).trigger("scroll"); msnbc.document.quilt.onScroll(); // set up auto-hide option if (msnbc.document.cookieMgr && this.SHORT_NAME && !this.__container.forceClosed) { var msg = (this.getNavTease() && this.getNavTease().icon) ? msg = this.getNavTease().icon : msg = "this area"; $(this.__container.slot).append(' Collapse ' + msg + ' for each story in this special section?'); $(this.__container.slot).find('span.rusure input') .addClass('t-' + this.__type) .click(function(e) { //e.preventDefault(); //if ($(this):checked) {msnbc.log("checked");} msnbc.document.cookieMgr.addCookieCommand(msnbc.document.settings.DEFAULT_COOKIE_NAME, self.SHORT_NAME, 0); self.__container.openOnScroll = false; $(this).parent().remove(); /*track via Omniture*/ try { $A.Track(this); } catch(e) { msnbc.log(e); msnbc.log("omniture tracking error"); } }); }; }, onError: function(msg) {}, toString: function() { return '[Module]'; } }); })(); ///////////////////////////////////////////////////////////// // COMPONENTS (function() { var $$ = msnbc.namespace('msnbc.components'); $$.Factory = function(cname, params) { var cmpt; var clazz = msnbc.namespace(cname); if (clazz.prototype) { // component is available, return the class from the string name cmpt = new clazz(params); } else { msnbc.warn('[msnbc.components.Factory] WARNING: component ' + cname + 'not available'); cmpt = new msnbc.components.Component(params); clazz = null; } return cmpt; }; $$.Component = msnbc.core.EventDispatcher.extend({ init: function() { this._super(); }, insert: function(selectr) { $(selectr).append('[msnbc.components.Component]'); } }); })();