/*!
  v 0.5.7

  trblib core
    Required for all trblib plugins. Contains sub-libraries require, run, util, fn, and plugins.
    
    trblib require
      Loads JavaScript / CSS files dynamically.
    
    trblib run
      Event runner consolidating multiple events with a common handler. Allows custom events.
    
    trlib util
      Various utility methods.
    
    trblib fn
      Public API for code common to a distributed sub-library (e.g. plugins).
      
    trblib plugins
      Distributed, dynamically loaded plugin system using evented roles in a publish / subscribe 
      pattern with implictly invoked lazy instantiation. Actual plugins defined outside trblib
      core. All instances of a plugin use same runner and router.
      
      To register (load & enable) a plugin, call trblib.fn.plugins.register(pluginName).
  
  Author: Dan Krecichwost
*/

(function(window, $) {
  var document = window.document,
      trblib = window.trblib = window.trblib || {},
      data = trblib.data = {},
      old,
      root = $.root = $(document),
      find = root.find,
      urlMap = {},
      rAttributeCleanup = /[\n\t\r]/g,
      rSpace = /\s+/,
      rSpaceGlobal = /\s+/g,
      rQueryString = /([^\[=&]+)(\[\])?(?:=([^&]*))?/g,
      // used to decide whether to call trblib.element.reflow
      forceReflow = jQuery.browser.msie && jQuery.browser.version < 9,
      windowParams;
  
  // temporary - need correct version of jQuery
  trblib.jQuery = $;
  
  // iepp v1.6.2 MIT @jon_neal - http://www.iecss.com/print-protector/
  /*@cc_on(function(k,o){var a="abbr|article|aside|audio|canvas|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",f=a.split("|"),d=f.length,b=new RegExp("(^|\\s)("+a+")","gi"),h=new RegExp("<(/*)("+a+")","gi"),m=new RegExp("(^|[^\\n]*?\\s)("+a+")([^\\n]*)({[\\n\\w\\W]*?})","gi"),p=o.createDocumentFragment(),i=o.documentElement,n=i.firstChild,c=o.createElement("body"),g=o.createElement("style"),j;function e(r){var q=-1;while(++q<d){r.createElement(f[q])}}function l(u,s){var r=-1,q=u.length,v,t=[];while(++r<q){v=u[r];if((s=v.media||s)!="screen"){t.push(l(v.imports,s),v.cssText)}}return t.join("")}e(o);e(p);n.insertBefore(g,n.firstChild);g.media="print";k.attachEvent("onbeforeprint",function(){var r=-1,u=l(o.styleSheets,"all"),t=[],w;j=j||o.body;while((w=m.exec(u))!=null){t.push((w[1]+w[2]+w[3]).replace(b,"$1.iepp_$2")+w[4])}g.styleSheet.cssText=t.join("\n");while(++r<d){var s=o.getElementsByTagName(f[r]),v=s.length,q=-1;while(++q<v){if(s[q].className.indexOf("iepp_")<0){s[q].className+=" iepp_"+f[r]}}}p.appendChild(j);i.appendChild(c);c.className=j.className;c.innerHTML=j.innerHTML.replace(h,"<$1font")});k.attachEvent("onafterprint",function(){c.innerHTML="";i.removeChild(c);i.appendChild(j);g.styleSheet.cssText=""})})(this,document);@*/
  
  trblib.util = $.extend({
    // TODO add adler32, sharding, and base URL concepts to trblib.require
    adler32: function adler32(s) {
      var s1 = 1, s2 = 0, b, i = -1;
      while ((b = s.charCodeAt(++i))) {
        s1 = (s1 + b) % 65521;
       	s2 = (s2 + s1) % 65521;
      }
      return s2 << 16 | s1;
    },
    inject: function inject(t, o) {
      for (var x in o) if (!t[x] && o.hasOwnProperty(x)) t[x] = o[x];
    },
    absolutizeUrl: function absolutizeUrl(url) {
      var absoluteUrl;
      if ((absoluteUrl = urlMap[url])) return absoluteUrl;
      
      absoluteUrl = document.createElement('div');
      absoluteUrl.innerHTML = '<a href="' + url + '">d</a>';
      return (urlMap[url] = absoluteUrl.firstChild.href);
    },
    // object can be an element
    expandQueryString: function expandQueryString(queryString, object) {
      var params = typeof object == 'object' ? object : {},
          value;
      if (queryString && typeof queryString == 'string') queryString.replace(rQueryString, function(a, b, c, d) {
        value = d === undefined || d === '' ? true : d;
        if (c) {
          (params[b] || (params[b] = [])).push(value);
        } else {
          params[b] = value;
        }
      });
      return params;
    }
  }, trblib.util);
  
  trblib.ns = function(namespaceString, base) {
    if (!base) base = window;
    for (var i = -1, spaces = (namespaceString += '').split('.'), sl = spaces.length; ++i < sl;) {
       base = base[spaces[i]] || (base[spaces[i]] = {});
    }
    return base;
  };
  
  windowParams = data.windowParams = (function() {
    var queryStringIndex = location.href.indexOf('?');
    return queryStringIndex > -1 ? trblib.util.expandQueryString(location.href.substring(queryStringIndex + 1, location.href.length)) : {};
  })();
  
  
  old = trblib.require;
  trblib.require = (function require() {
    
    var a = arguments,
        head,
        _anchor = function() {
          head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
        },
        loadAsync = document.createElement('script').async === true || 'MozAppearance' in document.documentElement.style || window.opera,
        require = $.extend(function require() {

          var a = arguments,
              i;
          
          if ((i = a[0]) && (i = typeof i === 'boolean' ? require.delay : parseInt(i))) {
            setTimeout(function() {
              require.apply(require, Array.prototype.slice.call(a, 1));
            }, i);
            return;
          }
          if (!head) _anchor();
          
          i = a.length-1;
          var hasO = true,
              o = a[i],
              t = typeof o,
              su, b, x;
          
          if (t !== 'object' || o.url) {
            o = {};
          } else {
            --i;
          }
          if (!su && typeof a[i] === 'function') su = a[i--];
          if (su) o.success = su;
          
          b = new ResourceBundle(o);
          
          for (x = 0; x <= i; x++) {
            resourceBundleMethods_.addResource.call(b, a[x]);
          }
          b.ready = true;
          resourceBundleMethods_.update.call(b);

          return b;
        }, {
          resources: {},
          length: 0,
          resourceBundles: [],
          delay: 100
        }, old),
        resourceMethods_ = {
          notifyLoaded: function() {
            this.loaded = true;
            var bs = this.bundles;
            for (var i = 0; i < bs.length; i++) {
              resourceBundleMethods_.notifyResourceLoaded.call(bs[i]);
            }
          },
          notifyFailed: function() {
            this.failed = true;
            var bs = this.bundles;
            for (var i = 0; i < bs.length; i++) {
              resourceBundleMethods_.notifyResourceFailed.call(bs[i]);
            }
          },
          registerBundle: function(b) {
            this.bundles.push(b);
          }
        }, resourceBundleMethods_ = {
          addResource: function(o) {
            if (!o) return;
            var url = o, type, force = false, a, i, x;
            if (typeof o === 'object') {
              url = o.url;
              type = o.type;
              force = o.force;
            } else if (o.indexOf('!' > -1)) {
              a = o.split('!');
              url = a[0];
              i = 0;
              while ((x = a[++i])) {
                if (x == 'css') {
                  type = 'css';
                } else if (x == 'force') {
                  force = true;
                }
              } 
            }
            // no URL, so no resource to add
            if (!url) return;
            // make sure URL is absolute -- ensures only one instance of a resource on page
            url = trblib.util.absolutizeUrl(url);
            // if the library already knows of the resource
            var r = Resource.collection[url];
            ++this.loading;
            if (r) {
              if (force || r.failed) {
                this.loaded = false;
                r.type == 'js' ? createScript_(r) : createLink_(r);
              } else if (!r.loaded) {
                this.loaded = false;
              }
            } else {
              r = new Resource(null, url, type, !loadAsync && this.loading > 1);
              if (!r.loaded) this.loaded = false;
            }
            this[this.length++] = r;
            resourceMethods_.registerBundle.call(r, this);
          },
          notifyResourceLoaded: function() {
            if (--this.loading <= 0) {
              this.loaded = true;
              resourceBundleMethods_.update.call(this);
            }
          },
          notifyResourceFailed: function() {
            this.failed = true;
            if (--this.loading <= 0) {
              resourceBundleMethods_.update.call(this);
            }
          },
          update: function() {
            var t = this;
            if (t.ready && !t.executed) {
              if (t.loaded) {
                if (t.success) t.success();
                t.executed = true;
              } else if (t.failed) {
                if (t.failure) t.failure();
              }
            }
          }
        };
    
    
    function ResourceBundle(o) {
      $.extend(this, o);
      this.collection.push(this);
    };
    ResourceBundle.prototype = {
      collection: (ResourceBundle.collection = require.resourceBundles),
      loaded: true,
      loading: 0,
      failed: false,
      executed: false,
      length: 0,
      toArray: function() {
    		return Array.prototype.slice.call(this);
    	},
    	splice: [].splice
    };
    
    function Resource(element, url, type, noExecute) {
      var t = this;
      t.bundles = [];
      if (element) {
        if (!url) url = trblib.util.absolutizeUrl(element.src || element.href);
        t.element = element;
        t.collection[(t.url = url)] = t;
        t.type = element.nodeName == 'SCRIPT' ? 'js' : 'css';
        t.noExecute = element.type && /^text\/(?:javascript|css)$/.test(element.type);
        t.loaded = true;
      } else {
        t.collection[(t.url = url)] = t;
        t.type = type || /(?:(?:(?:[^?]*?)(?:\.css(?:\?|$))){1,1}|!css)/.test(url) ? 'css' : 'js';
        t.noExecute = noExecute;
        t.element = (t.type == 'js' ? createScript_ : createLink_)(t);
      }
    }
    Resource.prototype = {
      collection: (Resource.collection = require.resources),
      loaded: false,
      failed: false
    };
    
    function eventAttach_(s, r) {
      function g() {
        resourceMethods_.notifyLoaded.call(r);
      }
      function f() {
        resourceMethods_.notifyFailed.call(r);
      }
      if (s.addEventListener) {
        s.addEventListener('load', g, false);
        s.addEventListener('error', f, false);
      } else if (s.attachEvent) {
        s.attachEvent('onreadystatechange', function(e) {
          var t = e.srcElement;
          if (!r.loaded && (/loaded|complete/.test(t.readyState))) {
            g();
          }
        });
        s.attachEvent('onerror', f);
      }
    }
    
    function createScript_(r) {
      var s = document.createElement('script');
      eventAttach_(s, r);
      s.src = r.url;
      s.async = true;
      head.appendChild(s);
      return s;
    }
    
    function createLink_(r) {
      var s, b = $.browser;
      if (b.mozilla) {
        s = document.createElement('style');
        s.textContent = '@import "' + r.url + '"';
      } else {
        s = document.createElement('link');
        s.rel = 'stylesheet';
        s.href = r.url;
      }  
      if (r.media) s.media = r.media;
      if (b.msie || b.opera) {
          eventAttach_(s, r);
      } else {
        var x, g, e = {
          // TODO fix non-live custom events for use of poll here
          type: 'poll',
          interval: 50
        }, o = {
          success: function() {
            resourceMethods_.notifyLoaded.call(r);
          },
          failure: function() {
            resourceMethods_.notifyFailure.call(r);
          },
          maxRuns: 200
        };
        if (b.webkit) {
          trblib.run(true, function() {
            var i = 0, h = s.href, l = document.styleSheets;
            while ((x = l[i++])) if (x.href == h) return true;
          }, e, o);
        } else {
          trblib.run(true, function() {
            try {
              return (g || (g = (x || (x = s.sheet || s.styleSheet)).cssRules || x.rules)) && g.length > 0;
            } catch(e) {
              return (e.code == 1000) || (/security|denied/i.test(e.message));
            }
          }, e, o);
        }
      }
      head.appendChild(s);
      return s;
    }
    
    var s, i = 0, ss = $('script[src], link[rel="stylesheet"]'), url, rs = require.resources;
    while ((s = ss[i++])) {
      url = trblib.util.absolutizeUrl(s.src || s.href);
      if (url && !rs[url]) {
        new Resource(s, url);
      }
    }
    
    return require;
  })();
  
  // TODO fix event propagation
  old = trblib.run;
  trblib.run = (function() {
    var a = arguments,
        rLive = /^([^:\s]+?)?(:live)?$/,
        run = $.extend(true, function run() {
          var a = arguments,
              testImmediately = false,
              i = 0,
              options = a[a.length - 1],
              test = a[0];
              
          if (typeof options !== "object") options = {};
          
          if (typeof test === "boolean") {
            testImmediately = test;
            test = a[++i];
          }  
          options.testImmediately = testImmediately;

          if (!(options.test = test) || !(options.events = a[++i])) return;
          var runner = new Runner(options);
          //console.log(runner)
          return runner;
        }, {
          delay: 250,
          // TODO consolidate boilerplate of custom event set and cancel methods
          customEvents: {
            poll: {
              set: function(eventModel, handler) {
                if (!eventModel.interval) eventModel.interval = eventModel.data[0] || eventModel.runner.delay;
                eventModel.id = setInterval((eventModel.handler = function() {
                  $(eventModel.target).trigger('poll');
                }), eventModel.interval);
              },
              cancel: function(eventModel) {
                clearInterval(eventModel.id);
              }
            },
            timedscroll: {
              set: function(eventModel, handler) {
                var lastTimestamp,
                    timeOffset;
                if (!eventModel.interval) eventModel.interval = eventModel.data[0] || eventModel.runner.delay;
                
                $(window).bind('scroll', (eventModel.handler = function(e) {
                  if (!eventModel.id) {
                    if (!lastTimestamp || (timeOffset = e.timeStamp - lastTimestamp) > eventModel.interval) {
                      $(eventModel.target).trigger(eventModel.type);
                      lastTimestamp = e.timeStamp;
                    } else {
                      eventModel.id = setTimeout(function() {
                        lastTimestamp = (new Date).getTime();
                        eventModel.id = null;
                        $(eventModel.target).trigger(eventModel.type);
                      }, eventModel.interval - timeOffset);
                    }
                  }
                }));
              },
              cancel: function(eventModel) {
                clearTimeout(eventModel.id);
                $(window).unbind('scroll', eventModel.handler);
              }
            },
            ready: {
              set: function(eventModel, handler) {
                $(document).bind('ready', (eventModel.handler = function() {
                  eventModel.cancel();
                  $(eventModel.target).trigger('ready');
                }));
              },
              cancel: function(eventModel) {
                $(document).unbind('ready', eventModel.handler);
              }
            }
          },
          runners: []
        }, old);
        
    function Runner(options) {
      var runner = this;
      $.extend(runner, options);
      run.runners.push(runner);
      runner.setEvents(runner.events);
    }
    
    Runner.prototype = {
      delay: run.delay,
      collection: run.runners,
      runs: 0,
      cancel: function(events) {
        var eventHash = this.eventHash,
            eventData, 
            i = -1;
        
        events = events ? _standardizeEvents(events) : this.events;
        while((eventModel = events[++i][eventData])) {
          eventModel.cancel && eventModel.cancel();
        }
      },
      setEvents: function(events) {
        var runner = this,
            eventHash = runner.eventHash || (runner.eventHash = {}),
            eventTypeHash = runner.eventTypeHash || (runner.eventTypeHash = {}),
            eventModel,
            eventData,
            i = -1;
        
        events = _standardizeEvents(events);
        
        runner.events = runner.events ? runner.events.concat(events) : events;
        
        while ((eventData = events[++i])) {
          if ((eventModel = _createEventModel(eventData, runner))) {
            eventHash[eventModel.toString()] = eventModel;
            _setEvent(eventModel);
            (eventTypeHash[eventModel.fulltype] || (eventTypeHash[eventModel.fulltype] = [])).push(eventModel);

            if (runner.testImmediately && !runner.completed) {
              eventModel.trigger();
            }
          }
        }
      }
    };

    function EventModel(eventData) {
      $.extend(this, eventData);
    }
    
    EventModel.prototype = {
      cancel: function() {
        _cancelEvent(this);
      },
      trigger: function(target) {
        _triggerEvent(this, target);
      },
      toString: function() {
        
        var eventModel = this,
            str = this.type;
        
        if (this.live) {
          str += ':live';
        }
        
        if (typeof this.target == 'string') {
          str += '!' + this.target;
        }
        
        if (eventModel.customEvent && eventModel.customEvent.hasOwnProperty('toString')) {
          str += eventModel.customEvent.toString();
        }
        
        return str; 
      }
    };
    
    // TODO should 2 targets exist - one live, one not?
    function _createEventModel(eventData, runner) {
      eventData.runner = runner;
      if (eventData.type) {
        eventData.customEvent = run.customEvents[eventData.type];      
        return new EventModel(eventData);
      }
    }
    
    function _setEvent(eventModel) {
      
      var runner = eventModel.runner,
          relatedModelArray,
          live = eventModel.live,
          extendedTarget,
          relatedModel,
          handler,
          i = 0;
      
      if (false && live && (relatedModelArray = runner.eventTypeHash[eventModel.fulltype]) && (relatedModel = relatedModelArray[0])) {
        
        handler = relatedModelArray[0].handler;
        extendedTarget = relatedModel.target;
        
        while ((relatedModel = relatedModelArray[++i])) {
          extendedTarget += ',' + relatedModel.target;
        }
        
        $(extendedTarget)[(live ? 'die' : 'unbind')](eventModel.type, handler);
        
        extendedTarget += ',' + eventModel.target;
      } else {
        var maxRuns = runner.maxRuns,
            success,
            failure,
            runNumber;
        
        handler = function(event) {
              runNumber = ++runner.runs;
              event.cancel = eventModel.cancel;
              event.eventModel = eventModel;
              event.runner = runner;
              if ((success = runner.test.call(eventModel, event)) || (failure = maxRuns && (runNumber >= maxRuns))) {
                if (!runner.completed) {
                  if (success) {
                    if (runner.success) runner.success(this);
                  } else if (failure) {
                    runner.failed = true;
                    if (runner.failure) runner.failure(this);
                  }
                  runner.completed = true;
                }
                runner.cancel();
              }
            };
        extendedTarget = eventModel.target;
      }
      
      $(extendedTarget)[(live ? 'live' : 'bind')](eventModel.type, (eventModel.handler = handler));
      
      if (eventModel.customEvent) {
        eventModel.customEvent.set(eventModel, handler);
      }
      eventModel.canceled = false;
    }
    
    function _cancelEvent(eventModel) {
      if (!eventModel.canceled) {
        eventModel.canceled = true;
        
        var relatedModelArray,
            type = eventModel.type,
            handler = eventModel.handler,
            live = eventModel.live;
        
        if (live && (relatedModelArray = eventModel.runner.eventTypeHash[eventModel.fulltype]) && relatedModelArray[0]) {
          
          var relatedModel,
              extendedTarget, 
              adjustedExtendedTarget,
              i = -1;
          
          while ((relatedModel = relatedModelArray[++i])) {
            extendedTarget += extendedTarget ? ',' + relatedModel.target : relatedModel.target;
            if (relatedModel != eventModel) {
              adjustedExtendedTarget += adjustedExtendedTarget ? ',' + relatedModel.target : relatedModel.target;
            }
          }
          
          $(extendedTarget)[(live ? 'die' : 'unbind')](type, handler);
          $(adjustedExtendedTarget)[(live ? 'live' : 'bind')](type, handler);
        } else {
          $(eventModel.target)[(live ? 'die' : 'unbind')](type, handler);
        }
        
      
        if (eventModel.customEvent) {
          eventModel.customEvent.cancel(eventModel);
        }
      }
    }
    
    function _triggerEvent(eventModel, target) {
      $(target || eventModel.target).trigger(eventModel.type);
    }
    
    function _standardizeEvents(events) {
      // events may be in string, object or array format; strings and objects are converted to arrays
      if (typeof events == 'string') {
        // pipe-separated serialized event string 
        events = events.split('|');
      } else if (typeof events == 'object') {
        // single event as object
        events = [events];
      }
      
      var eventData,
          i = -1;
      while ((eventData = events[++i])) {
        
        if (typeof eventData == 'string') {
          var eventDataArray = eventData.split('!'),
              eventName = eventDataArray.shift(),
              eventData = {
                data: eventDataArray
              },
              eventMatch;
          if (eventName) {
            eventMatch = eventName.match(rLive);
            eventData.type = eventMatch[1];
            eventData.live = !!eventMatch[2];
          }
          eventData.target = eventData.currentTarget = $.trim(eventDataArray.shift());
        }
        
        eventData.fulltype = eventData.type;
        if (eventData.live) {
          eventData.fulltype += ':live';
        }
        
        // the target is missing; default to document
        if (!eventData.target) eventData.target = document;
        if (!eventData.currentTarget) eventData.currentTarget = eventData.target;
        // live only works for selectors; turn it off if the target isn't a string
        if (eventData.live && !(typeof eventData.target == 'string')) eventData.live = false;
        
        events[i] = eventData;
      }
      
      return events;
    }
    
    return run;
  })();
  
  old = trblib.element;
  trblib.element = $.extend(true, {
    withinViewport: function withinViewport($element, topBuffer, bottomBuffer) {
      if (!(topBuffer = parseInt(topBuffer))) topBuffer = 0;
      if (!(bottomBuffer = parseInt(bottomBuffer))) bottomBuffer = 0;
      var scrollTop = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop,
          scrollBottom = scrollTop + (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight),
          elementTopOffset = $element.offset().top;

      return scrollBottom > elementTopOffset - topBuffer && scrollTop < elementTopOffset + $element.outerHeight() + bottomBuffer;
    },
    replaceWithHtml: function replaceWithHtml(element, html) {
      if (typeof element == 'object' && element.nodeType === 1) {
        var nodeContainer = document.createElement('div'),
            fragment = document.createDocumentFragment(),
            node,
            nodes,
            i = -1;
        nodeContainer.innerHTML = html;
        nodes = nodeContainer.childNodes;
        while ((node = nodes[++i])) {
          fragment.appendChild(node);
        }
        element.parentNode.replaceChild(fragment, element);
      }
    },
    hasAttributeSpacedValue: function hasAttributeSpacedValue(element, attributeName, value) {
      return $.find.matchesSelector(element, '[' + attributeName + '~="' + value + '"]');
    },
    addAttributeSpacedValue: function addAttributeSpacedValue(elements, attributeName, value) {
      if (value && typeof value == 'string' && (value = $.trim(value)) && typeof elements == 'object' && elements.length) {
        var attributeValue,
            addedAttributeValue,
            currentAttributeValue,
            values,
            valueItem,
            element;
      
        for (var i = 0, el = elements.length; i < el; i++) {
          element = elements[i];
          if (typeof element == 'object' && element.nodeType === 1) {
            if ((attributeValue = element.getAttribute(attributeName))) {
              addedAttributeValue = currentAttributeValue = (' ' + attributeValue + ' ').replace(rAttributeCleanup, ' ');
              for (var j = 0, vl = (values || (values = value.split(rSpace))).length; j < vl; j++) {
                if ((valueItem = values[j]) && (valueItem = ' ' + valueItem) && addedAttributeValue.indexOf(valueItem + ' ') < 0) {
                  addedAttributeValue += ' ' + valueItem;
                }
              }
              if (addedAttributeValue != currentAttributeValue) {
                element.setAttribute(attributeName, $.trim(addedAttributeValue.replace(rSpaceGlobal, ' ')));
                if (forceReflow) trblib.element.forceReflow(element);
              }
            } else {
              element.setAttribute(attributeName, value);
              if (forceReflow) trblib.element.forceReflow(element);
            }
          }
        }
      }
    },
    removeAttributeSpacedValue: function removeAttributeSpacedValue(elements, attributeName, value) {
      if (typeof elements == 'object' && elements.length) {
        if (value && typeof value == 'string') {
          var values,
              attributeValue,
              removedAttributeValue,
              currentAttributeValue,
              valueItem,
              element;
          
          for (var i = 0, el = elements.length; i < el; i++) {
            element = elements[i];
            if (typeof element == 'object' && element.nodeType === 1 && (attributeValue = element.getAttribute(attributeName))) {
              removedAttributeValue = currentAttributeValue = (' ' + attributeValue + ' ').replace(rAttributeCleanup, ' ');
              for (var j = 0, vl = (values || (values = value.split(rSpace))).length; j < vl; j++) {
                if ((valueItem = values[j])) {
                  removedAttributeValue = removedAttributeValue.replace(' ' + valueItem + ' ', '');
                }
    					}
              if (removedAttributeValue != currentAttributeValue) {
                removedAttributeValue = $.trim(removedAttributeValue.replace(rSpaceGlobal, ' '));
                if (removedAttributeValue) {
                  element.setAttribute(attributeName, removedAttributeValue);
                } else {
                  element.removeAttribute(attributeName);
                }
                if (forceReflow) trblib.element.forceReflow(element);
              }
            }
          }
        } else {
          for (var i = 0, el = elements.length; i < el; i++) {
            element = elements[i];
            if (typeof element == 'object' && element.nodeType === 1) {
              elements[i].removeAttribute(attributeName);
              if (forceReflow) trblib.element.forceReflow(element);
            }
          }
        }
      }
    },
    // Use to force IE7 to reflow when style is affected by changes to attributes other than style, class, and id
    forceReflow: forceReflow ? function(element) {
      element.className = element.className;
    } : jQuery.noop
  }, old);
  
  trblib.log = function(data) {
    try {
      var console = window.console,
          log;
      if (console && (log = console.log)) {
        log(data);
      }
    } catch(e) {}
  };
  
  old = trblib.fn;
  trblib.fn = $.extend(true, {
    plugins: {
      defaultPath: '/hive/javascripts/plugins/',
      // TODO support changing events on both registered and unregistered plugins
      globalHandlers: {
        // uses trblib.log to output information related to current pluginInstance and event
        trb_log: function(event) {
          var logData = [
              'Event type: ', event.type, '\n',
              'Event live: ', !!event.live, '\n',
              'Event target: ', event.target, '\n',
              'Event baseTarget: '
          ];
          trblib.log(logData.join(''));
          trblib.log(event.currentTarget);
        },
        // stop event propagation
        trb_stopPropagation: function(event) {
          event.stopPropagation();
        },
        trb_stopImmediatePropagation: function(event) {
          event.stopPropagation();
        },
        trb_preventDefault: function(event) {
          event.preventDefault();
        }
      },
      loadByHandler: function(items, type) {
        var item,
            i = -1;
        while ((item = items[++i])) {
          var type = ((item.nodeName == 'IMG') ? 'img' : item.getAttribute('data-type')) || 'comment',
              loadHandler = trblib.fn.plugins.loadHandlers[type];
          if (loadHandler) {
            loadHandler(item);
          }
        }
      },
      loadHandlers: {
        iframe: function(item) {
          var element = document.createElement('iframe');
          trblib.util.expandQueryString(item.getAttribute('data-attr'), element);
          element.setAttribute('src', item.getAttribute('data-url'));
          element.setAttribute('frameBorder', element.frameBorder);
          item.parentNode.replaceChild(element, item);
        },
        comment: function(item) {
          var commentNode,
              nodes = item.childNodes,
              i = -1;
          while((commentNode = nodes[++i])) {
            if (commentNode.nodeType == 8) {
              trblib.element.replaceWithHtml(item, commentNode.data);
            }
          }
        },
        img: function(item) {
          var dataUrl = item.getAttribute('data-url');
          item.removeAttribute('data-url');
          if (dataUrl) {
            item.src = dataUrl;
          }
        },
        noscript: function(item) {
          trblib.element.replaceWithHtml(item, item.innerHTML);
        },
        ajax: function(item) {
          var url = item.getAttribute('data-url');
          if (url) {
            $.ajax(url, {
              success: function(data) {
                trblib.element.replaceWithHtml(item, data);
              }
            });
          }
        }
      },
      hasRole: function hasRole(element, role) {
        return trblib.element.hasAttributeSpacedValue(element, 'data-role', role);
      },
      addRole: function addRole(elements, role) {
        trblib.element.addAttributeSpacedValue(elements, 'data-role', role);
      },
      removeRole: function removeRole(elements, role) {
        trblib.element.removeAttributeSpacedValue(elements, 'data-role', role);
      },
      // TODO change concept of links to mixins; reevaluate necessity and usefulness
      addLink: function addLink(plugin, link) {
        if (!plugin.links) plugin.links = [];
        plugin.links.push(link);
      },
      removeLink: function removeLink(plugin, link) {
        var links = plugin.links,
            offset;
        if (links) {
          if (links.length <= 1) {
            plugin.links = null;
          } else if ((offset = $.inArray(link, links)) > -1) {
            links.splice(offset, 1);
          }
        }
      },
      router: function router(event, linkInstance) {
        //trblib.log(event.type)
        var baseElement = baseElement = event.currentTarget,
            baseElementRole = baseElement.getAttribute('data-role'),
            plugin;
            
        if (baseElementRole) {
          for (var i = 0, roles = baseElementRole.split(' '), rl = roles.length; i < rl; i++) {
            if ((plugin = trblib.plugins[roles[i]]) && plugin.registered) {
              plugin.router(event, linkInstance);
            }
          }
        }
      },
      register: function register(plugin, options, callback, pluginPath) {
        
        if (typeof plugin === 'string') {
          if (trblib.plugins[plugin]) {
            plugin = trblib.plugins[plugin];
          } else {
            trblib.require(true, (pluginPath || trblib.fn.plugins.defaultPath) + plugin + '.js', function() {
              trblib.fn.plugins.register(trblib.plugins[plugin], options, callback, pluginPath);
            });
            return;
          }
        }
        
        if (plugin && !plugin.registered) {
        
          plugin.registered = true;
          if (!plugin.instances) plugin.instances = [];
          
          if (plugin.requiredLinks) {
            var link, i = -1;
            while ((link = trblib.plugins[plugin.requiredLinks[++i]])) {
              trblib.fn.plugins.addLink(link, plugin);
              if (!link.registered) trblib.fn.plugins.register(link);
            }
          }
          
          var pluginName = plugin.name,
              dataIdAttribute = 'data-' + pluginName + '-id',
              routerContinue = true,
              router,
              runner = trblib.fn.plugins.runner,
              pluginConstructor = function() {};
          
          pluginConstructor.prototype = plugin;
          
          if (runner) {
            runner.setEvents(plugin.events);
          } else {
            runner = trblib.fn.plugins.runner = trblib.run(trblib.fn.plugins.router, plugin.events);
          }
          plugin.runner = runner;
          
          router = plugin.router = function router(event, linkInstance) {
              
            var eventHandlers = plugin.handlers,
                links = plugin.links,
                link,
                i = -1;
            
            if (eventHandlers || links) {
              var targetElement = event.target,
                  baseElement = event.currentTarget,
                  eventTypeHandler = eventHandlers && eventHandlers[event.type],
                  globalHandlers = trblib.fn.plugins.globalHandlers,
                  pluginInstance = plugin.instances[baseElement[dataIdAttribute]],
                  currentElement = targetElement,
                  withinBaseElement,
                  pluginOptions,
                  roleData,
                  roleList,
                  role,
                  objectHandler,
                  handlerResponse;
              
              // TODO delay instantiation further by first making sure matching handlers exist
              //      this is reasonable since instance specific handlers are not possible anyway
              if (!pluginInstance) {
                pluginOptions = baseElement.getAttribute('data-opt');
                pluginInstance = plugin.instances[(baseElement[dataIdAttribute] = plugin.instances.length++)] = new pluginConstructor();
                pluginInstance.data = {
                  options: $.trim(pluginOptions) ? trblib.util.expandQueryString(pluginOptions) : {},
                  $baseElement: $(baseElement)
                };
                pluginInstance.baseElement = baseElement;
                if (plugin.init) plugin.init.call(pluginInstance, event);
              }
              
              // TODO split event handlers into instance and static
              if (eventTypeHandler && (typeof eventTypeHandler == 'function' ? eventTypeHandler.call(pluginInstance, event) : true)) {
                do {
                  withinBaseElement = baseElement != currentElement;
                  
                  if ((roleData = $.trim(currentElement.getAttribute('data-role')))) {
                    roleList = roleData.split(' ');
                    
                    while ((role = roleList[++i])) {
                      //console.log(event.type + ': ' + role);
                      objectHandler = eventTypeHandler[role];
                      // TODO consider a filter chain model (using yield-esque functionality) instead
                      if ((objectHandler || (objectHandler = globalHandlers[role])) && objectHandler.call(pluginInstance, event, linkInstance) === false) {
                        routerContinue = false;
                        break;
                      }
                    }
                    i = -1;
                  }
                } while (routerContinue && withinBaseElement && (currentElement = currentElement.parentNode)); 
              }
              
              if (links) {
                while ((link = links[++i])) {
                  link.router(event, pluginInstance);
                }
              }

              //} else {
              //  if ((eventTypeHandler = eventHandlers && eventHandlers[event.type]) && (typeof eventTypeHandler == 'function')) return eventTypeHandler.call(plugin, event);
              //}

              routerContinue = true;
            }
        
          }
          
          router.stopPropagation = function stopPropagation() {
            routerContinue = false;
          }
          
          if (callback) callback(plugin);
        }  

        if (options) {
          if (typeof options == 'string') options = trblib.util.expandQueryString(options);
          for (var x in options) {
            if (plugin.reghelpers && plugin.reghelpers.register[x]) {
              plugin.reghelpers.register[x](plugin, options[x]);
            } else {
              plugin.runner.setEvents(x + '![data-role~=' + plugin.name + ']');
            }
          }
        }
      },
      unregister: function(plugin) {
        
        if (typeof plugin === 'string' && trblib.plugins[plugin]) {
          plugin = trblib.plugins[plugin];
        }
        
        if (plugin && plugin.registered) {
          plugin.registered = false;
          
          for (var pluginInstance, pluginInstances = plugin.instances, pil = pluginInstances.length, i = 0; pil < i; i++) {
            pluginInstance = pluginInstances[i++];
            if (pluginInstance.cleanup) pluginInstance.cleanup.call(pluginInstance);
            try {
              delete pluginInstance.baseElement[attrName];
            } catch(e) {
              pluginInstance.baseElement[attrName] = null;
            }
            pluginInstance.baseElement = null;
          }

          plugin.router.stopPropagation();
        }
        plugin.instances = null;
      },
      getInstanceId: function getInstanceId(plugin, baseElement) {
        if (typeof plugin === 'string' && trblib.plugins[plugin]) {
          plugin = trblib.plugins[plugin];
        }

        if (plugin && typeof baseElement == 'object' && baseElement.nodeType === 1) {
          return parseInt(baseElement['data-' + plugin.name + '-id']);
        }
      },
      getInstance: function getInstance(plugin, baseElement) {
        var instanceId = getInstanceId(plugin, baseElement);
        if (instanceId > -1) return plugin.instances[instanceId];
      },
      unregisterInstance: function unregisterInstance(plugin, baseElement) {

        if (typeof plugin === 'string' && trblib.plugins[plugin]) {
          plugin = trblib.plugins[plugin];
        }

        if (plugin && typeof baseElement == 'object' && baseElement.nodeType === 1) {
          var attrName = 'data-' + plugin.name + '-id',
              instanceId = parseInt(baseElement[attrName]),
              pluginInstance;

          if (instanceId > -1) {
            try {
              delete baseElement[attrName];
            } catch(e) {
              baseElement[attrName] = null;
            }
            if ((pluginInstance = instanceId > -1 ? plugin.instances[instanceId] : null)) {
              plugin.instances[instanceId] = null;
              if (pluginInstance.cleanup) pluginInstance.cleanup.call(pluginInstance);
              pluginInstance.baseElement = null;
              return true;
            }
          }
        }
        return false;
      }
    }
  }, old);
  
  old = trblib.plugins;
  trblib.plugins = $.extend(true, {
    // to use timedscroll, include it as a link from a different module and call cancelScroll once the event has been completed
    delayload: {
      name: 'delayload',
      handlers: {
        timedscroll: function() {
          this.fn.cancelLoadEvents(this);
          this.fn.load(this);
        },
        ready: function() {
          this.fn.cancelLoadEvents(this);
          this.fn.load(this);
        },
        poll: function() {
          this.fn.load(this);
        }
      },
      fn: {
        load: function(pluginInstance) {
          var $baseElement = pluginInstance.data.$baseElement;
          if (trblib.element.withinViewport($baseElement, 500, 500)) {
            var $items = $baseElement.find('[data-role~=delayload_item]');
            if ($items.length) {
              trblib.fn.plugins.removeRole($items, 'delayload_item');
              trblib.fn.plugins.loadByHandler($items);
            }
            pluginInstance.fn.cancel(pluginInstance);
          }
        },
        cancel: function(pluginInstance) {
          trblib.fn.plugins.removeRole(pluginInstance.data.$baseElement, pluginInstance.name);
        },
        cancelLoadEvents: function(pluginInstance) {
          var events = pluginInstance.runner.eventHash;
          if (!jQuery.isReady && events['ready:live![data-role~=delayload]']) {
            events['ready:live![data-role~=delayload]'].cancel();
          }
          if (events['poll:live![data-role~=delayload]']) {
            events['poll:live![data-role~=delayload]'].cancel();
          }
        }
      },
      events: 'timedscroll:live![data-role~=delayload]|poll:live![data-role~=delayload]|ready:live![data-role~=delayload]',
      init: function() {
        var data = this.data,
            options = data.options,
            offsetTop = offsetBottom = 500;
        if (options.offset) {
          options.offset = offsetTop = offsetBottom = parseInt(options.offset);
        }
        if (options.offsetTop) {
          options.offsetTop = offsetTop = parseInt(options.offsetTop);
        }
        if (options.offsetBottom) {
          options.offsetBottom = offsetBottom = parseInt(options.offsetBottom);
        }
        data.offsetTop = offsetTop;
        data.offsetBottom = offsetBottom;
      }
    },
    base: {
      name: 'base',
      handlers: {},
      events: ''
    }
  }, old);
  
  // Automatically register all existing modules
  //for (var plugin in trblib.plugins) {
  //  if (trblib.plugins.hasOwnProperty(plugin)) trblib.fn.plugins.register(trblib.plugins[plugin]);
  //}

})(window, jQuery);

