  /*****
    * @author jcfant@
    * @version 1.0.3
    * 
    * Amazon Carousel 
    * ===========================
    * Carousel is a module for displaying content using arrows/buttons to move the content around.
    * For example, having 16 items, only showing 4 and then clicking an arrow to move to the next 4 items.
    * 
    * Set up
    * ===========================
    * Carousel relies on certain items to be present in the DOM. Without these items, the carousel functionality 
    * will not work properly, although carousel will not throw errors when these items are not present.
    *
    * DOM Set Up
    * ===========================
	* <div class="widget" id="widget1">
    *   <div class="leftarrow"></div>
    *   <div class="pageDots">
    *        <div class="pageDot1 on"></div>
    *        <div class="pageDot2 off"></div>
    *        ...
    *        <div class="pageDot10 off"></div>
    *   </div>
    *   <div class="container">
    *       <ul class="content">
    *           <li> <!-- ANY STRUCTURE HERE --></li>
    *           <li> <!-- ANY STRUCTURE HERE --></li>
    *           <li> <!-- ANY STRUCTURE HERE --></li>
    *       </ul>
    *   </div>
    *   <div class="rightarrow"></div>
    * </div>
    *
    * Examples
    * ===========================
    * 1.) Getting the widget to load on document.ready (PREFERRED METHOD)
    *   $(document).ready(function() { $(".widget").carousel(); });
    *   
    * 2.) Example to start a widget as a carousel , place the following code after the widget DOM has been setup.
    *   $(".widget").carousel()
    *   
    * 3.) Customizing the carousel widget (see Options below)
    *   var options = {
    *                   debug   :   true
    *                   };
    *   $(".widget").carousel(options);
    *   
    * Default Options
    * ===========================
    *
    * var options = {
    *                 contentClass       : '.content',
    *                 containerClass     : '.container',
    *                 containerItem      : '.container .content li',
    *                 leftArrow          : '.leftarrow',
    *                 rightArrow         : '.rightarrow',
    *                 pageDots           : '.pageDots',
    *                 pageDotClass       : 'div',
    *                 arrowDisabled      : '.disabled',
    *                 arrowAction        : 'click',
    *                 preventDefault     : false,
    *                 stepSize           : 0, // 0 moves one whole panel at a time.
    *                 isCircular         : true,
    *                 debug              : false
    *             };
    *             
    * Passing In Callbacks using the default css selectors
    * ===========================
    *
    * var options = {
    *                 moveLeft    : function (data) { 
    *                                               var carousel = this;
    *                                               // do something ...
    *                                            },
    *                 moveRight   : function (data) {
    *                                               var carousel = this;
    *                                               // do something ...
    *                                            }
    *                 };
    * $(document).ready(function() { $(".widget").carousel(options); });
    * 
    * Chaining Calls
    * ===========================
    * 
    * var options = {
    *                 moveLeft    : [ function (data) { 
    *                                               var carousel = this;
    *                                               // do something ...
    *                                               return { isOK : true, data : "I AM A RETURN VALUE" }
    *                                            },
    *                                 function (data) { // data is now equal to "I AM A RETURN VALUE"
    *                                                var carousel = this;
    *                                                // do something ...
    *                                               return { isOK : true, data : "" }
    *                                            }
    *                                ],
    *                 moveRight   : [ function (data) { 
    *                                               var carousel = this;
    *                                               // do something ...
    *                                               return { isOK : false, data : "I AM A RETURN VALUE" }
    *                                            },
    *                                 function (data) { // this function isn't executed because of the isOK being false;
    *                                                var carousel = this;
    *                                                // do something ...
    *                                               return { isOK : true, data : "" }
    *                                            }
    *                                ],
    *                 };
    *
    * Callbacks
    * ===========================
    * The callback option will change the value of "this" inside of your function to that of the carousel object.
    * This can be very useful since you will be allowed to execute and retrieve information from the public methods.
    * 
    * If you are chaining functions, the first parameter to your function will be the returned data from the previous
    * function call.
    *   
    * Return Values
    *   You must return an object/hash with a minimum of { isOK : true/false }. If you are chaining events. 
    *   a false value OR no value returned for isOK, will halt the chain from executing.
    *   If you want to pass data to the next action in the chain; in the return object, pass { data: //data here };
    *
    *   Callback List
    *   beforeInit	Triggered before initialization of the carousel
    *   afterInit	Triggered after initialization of the carousel
    *   beforeMove	Triggered on each left/right arrow click. Return false to prevent movement
    *   moveLeft	Triggered on each left arrow click, after beforeMove. Return false to prevent movement
    *   moveRight	Triggered on each right arrow click, after beforeMove. Return false to prevent movement
    *   afterMove       Triggered after carousel animation is completed
    *   onPageSelect    Triggered when pagination dot is selected
    *
  *****/
(function($){
    jQuery.fn.carousel = function(newOptions) {     
         /**
          * Make sure that only one DOM element is present,
          * If not, loop through and call .tabs(newOptions) on each.
          *
          * Make sure to "return this", so calls can be chained.
          */
         if (this.length > 1) {
              for (var i = 0; i < this.length; i++) {
                   $(this[i]).carousel(newOptions);
              }
              return this;
         }
         
         /**
          * Refer to carousel, when "this" is out of scope
          */
         var carousel = this;
              
         /**
          * Base Options needed for the default configuration
          */
         var baseOptions = {
              contentClass   : '.content',
              containerClass : '.container',
              containerItem  : '.container .content li',
              leftArrow      : '.leftarrow',
              rightArrow     : '.rightarrow',
              pageDots       : '.pageDots', 
              pageDotClass   : 'div', 
              arrowDisabled  : '.disabled',    
              arrowAction    : 'click',
              stepSize       : 0,
              preventDefault : false,
              isCircular     : true,
              hideArrowForSinglePage : false,
              debug          : true
         };  
         
         /**
          * Create the options by extending the base options, with options passed in.
          */
         var options = $.extend(baseOptions, newOptions);
         
         /**
          * if call backs are passed in as an option for this item
          * @param {Object} hookList
          */
         var hooks = function(thisObject, hookList, args){
              args = args || [];
              if ($.isFunction(hookList)) {
                   return hookList.apply(thisObject, args).isOK;
              } else if (/object|array/.test(typeof hookList)) {
                   var result = {
                        isOK: true,
                        data: args
                   };
                   for (var i = 0; i < hookList.length; i++) {
                        if (result.isOK) {
                             result = hookList[i].apply(thisObject, result.data || []);
                        }
                   }
                   return result.isOK;
              }
              return true;
         };
         
         /**
          * Animate the carousel to a new position
          * 
          * @param {Object} nextLeftPosition
          */
         var moveCarousel = function(nextLeftPosition){
              movePageDisplay();
              disableArrows();
              carousel.data("contentLeft", nextLeftPosition);
              carousel.content.animate({ left: nextLeftPosition }, 800, 'linear', function() {
                  hooks(carousel, options['afterMove']);
              });
         };
         
         /**
          * Move Page Display Dots if they exist.
          */
         var movePageDisplay = function(){
              var thisPage = carousel.getCurrentPanel();
              if ($(options.pageDots + " " + options.pageDotClass + ".pageDot" + thisPage, carousel).get(0) != undefined) {
                   $(options.pageDots + " " + options.pageDotClass + ".on", carousel).removeClass('on').addClass('off');
                   $(options.pageDots + " " + options.pageDotClass + ".pageDot" + thisPage, carousel).removeClass('off').addClass('on');
              }
         };
         
         /**
          * Enable and disable Arrows if isCiruclar = false;
          * 
          */
         var disableArrows = function(){
              if (!options.isCircular){
                   /** We need to add the disabled tag to the exsisting class for individual targeting **/
                   var rightArrowClass = options.rightArrow.replace(/\./g, '') + options.arrowDisabled;
                   var leftArrowClass = options.leftArrow.replace(/\./g, '') + options.arrowDisabled;

                   $(options.leftArrow, carousel).removeClass(leftArrowClass);
                   $(options.rightArrow, carousel).removeClass(rightArrowClass);
                   
                   if (carousel.getCurrentPanel() == 1){
                        $(options.leftArrow, carousel).addClass(leftArrowClass);
                   }
                   
                   if (carousel.getCurrentPanel() == (carousel.data("totalItems") / carousel.data("shownPerSlide"))){
                        $(options.rightArrow, carousel).addClass(rightArrowClass);
                   }
              }
         };
         
         /****
          *
          * debug logging
          *
          ****/
         var log = function(data){
              if (options.debug) {
                   try {
                        console.log("%o: %o", data, this);
                   } catch (e) {
                        $('BODY').append($("<div class='error'>" + data + "</div>"));
                   }
              }
         };
         
         /**
          * Start Carousel.
          * 1. Find the content.
          * 2. Find the container and calculate its width. (this shouldn't change after we have it)
          * 3. Find each items width.
          * 4. Get the content Length of the items in the list.
          */
         this.initialize = function(){
              hooks(carousel, options['beforeInit']);

              this.findArrows();
              this.findPageDots();
              
              this.content = $(options.contentClass, this).eq(0);
              this.currentItem = $(options.containerItem, this).eq(0);
              this.container = $(options.containerClass, this).eq(0);
              
              this.data("displayWidth", this.container.width());
              this.data("itemWidth", this.currentItem.outerWidth(true));
              this.data("shownPerSlide", this.data("displayWidth") / this.data("itemWidth"));
              this.data("contentLeft", 0);
              this.data("showingItem", 0);
              this.data("stepSize", options.stepSize > 0 ? this.data("itemWidth") * options.stepSize : this.data("displayWidth"));
              
              this.calculateContentLength();
              
              disableArrows();

              // Hide arrow if there is only one page in the carousel;
              if(options.hideArrowForSinglePage && carousel.data("totalItems") <= carousel.data("shownPerSlide")) {
                  $(options.leftArrow, carousel).hide();
                  $(options.rightArrow, carousel).hide();
              }
              
              hooks(carousel, options['afterInit']);

              return this;
         }


         /**
          * Find the arrows we should attach the move slider event to.
          * By passing in a jQuery Element you can limit the scope of the where to find the arrows.
          * @param container - (jQuery Object) 
          */
         this.findArrows = function(container){
              var container = container || this;
              $(options.leftArrow, container).bind(options.arrowAction, function(e){
                   carousel.moveSlider('left');
                   if (options.preventDefault){
                       e.preventDefault();
                   }
              });
              $(options.rightArrow, container).bind(options.arrowAction, function(e){
                   carousel.moveSlider('right');
                   if (options.preventDefault){
                       e.preventDefault();
                   }
              });
         };
         
         /**
          * Find the page dots we should attach a click event to.
          * @param container - (jQuery Object) 
          */
         this.findPageDots = function(container){
              var container = container || this;
              $(options.pageDots + " " + options.pageDotClass, container).bind('click', function(e){
                   var result = $(this).attr('class').match(/^([\w\-]+\s)*pageDot(\d+)(\s[\w\-]+)*$/i);
                   var panelNum = result != null ? result[2] - 1: -1;

                   if(hooks(carousel, options.onPageSelect, [panelNum])) { 
                       carousel.moveToPosition(panelNum * carousel.data("shownPerSlide") + 1 );
                   }

                   if (options.preventDefault){
                       e.preventDefault();
                   }
              });
         };


         /**
          * Move the content to the left or right
          * 
          * @param {String} direction ('left', 'right')
          */
         this.moveSlider = function(direction){
              if (this.data("itemWidth") != this.currentItem.outerWidth(true)) {
                  this.data("itemWidth", this.currentItem.outerWidth(true));
                  this.data("shownPerSlide", this.data("displayWidth") / this.data("itemWidth"));
                  if (options.stepSize > 0) {
                      this.data("stepSize", this.data("itemWidth") * options.stepSize);
                      this.calculateContentLength();
                  }
              }

              if(!hooks(this, options['beforeMove'])) return;

              var hookList;
              if (direction == 'right')
                  hookList = options['moveRight'];
              else if (direction == 'left')
                  hookList = options['moveLeft'];
             
              if(!hooks(this, hookList)) return;

              var maxLeft = this.maxLeft();
              var nextLeftPosition;
              
              switch (direction) {
                   case 'right':
                        nextLeftPosition = this.data("contentLeft") - this.data("stepSize");
                        var currentItem = ( (nextLeftPosition / this.data("itemWidth")) * -1);
                        
                        if (options.stepSize > 0 && 
                             currentItem <= this.data("totalItems") && 
                             currentItem >= (this.data("totalItems") - options.stepSize) &&  
                             this.data("contentLeft") != maxLeft && 
                             nextLeftPosition < maxLeft
                             ){
                             nextLeftPosition = maxLeft;
                        }
                        
                        this.data("showingItem", (nextLeftPosition / this.data("itemWidth")) * -1);
                        break;
                   case 'left':
                        nextLeftPosition = this.data("contentLeft") + this.data("stepSize");
                        var currentItem = ( (nextLeftPosition / this.data("itemWidth")) * -1) + 1;
                                                                        
                        if (options.stepSize > 0 && this.data("contentLeft") < 0 && currentItem <= 0 && currentItem > (options.stepSize * -1)){
                             nextLeftPosition = 0;
                        }
                        
                        this.data("showingItem", this.data("showingItem") != 0 ? (nextLeftPosition / this.data("itemWidth") * -1) : 0);
                        break;
              }

              if (options.isCircular) {
                   // If nextLeftPosition is <= the contentWidth, then set move to 0. This happens 
                   // when we are at the end of the carousel and we need to get back to the begining.
                   if (nextLeftPosition < maxLeft) {
                        nextLeftPosition = 0;
                        this.data("showingItem", 0);
                   // IF nextLeftPosition is greater than 0, means we are at the begining of the list and 
                   // need to show the last slide instead of moving to the next slide.
                   // set the position to be the end of the carousel - the content width that is being shown.
                   } else if (nextLeftPosition > 0 ) {
                        nextLeftPosition = this.maxLeft();
                        this.data("showingItem", (nextLeftPosition / this.data("itemWidth") * -1) || 0);
                   }
              }
              
               // as long as nextLeftPosition is between the negative content Width and 0.
              if (nextLeftPosition >= maxLeft && nextLeftPosition <= 0) {
                   moveCarousel(nextLeftPosition);
              }

              return;
         }
         
         /**
          * Move the carousel to position
          * 
          * @param {Object} position
          */
         this.moveToPosition = function(position){
              position--;
              this.data("showingItem", position);
              var nextLeftPosition = (this.data("itemWidth") * position) * -1;
              var maxLeft = this.maxLeft();
              if (nextLeftPosition < maxLeft){
                   nextLeftPosition = maxLeft;
              }
              
              moveCarousel(nextLeftPosition);
         }
         
         /**
          * Returns the max left based on step size
          */
         this.maxLeft = function() {
              var maxLeft = (this.data("contentWidth") - this.data("stepSize")) * -1;
              if (options.stepSize > 0) {
                   maxLeft = ( 
                                  ( 
                                       ( 
                                            Math.ceil(this.data("contentWidth") / this.data("displayWidth") )
                                       ) * this.data("displayWidth") 
                                  ) - this.data("displayWidth")
                             ) * -1;
              }                            
              return maxLeft;
         }
         
         /**
          * Calculates the total Items in the list, If you are appending items to the content you will need 
          * to call this function.
          */
         this.calculateContentLength = function(){
              this.data("totalItems",$(options.containerItem, this).length);
              this.data("contentWidth", (this.data("itemWidth") * this.data("totalItems")));
         }
         
         /*
          * GETTERS
          */
         this.getDisplayWidth = function(){
              return this.data("displayWidth")
         };
         this.getItemWidth = function(){
              return this.data("itemWidth")
         };
         this.getShownPerSlide = function(){
              return this.data("shownPerSlide")
         };
         this.getShowingItem = function(){
              return this.data("showingItem");
         };
         this.getTotalItems = function(){
              return this.data("totalItems");
         };
         this.getCurrentPanel = function() {
              return (this.data("showingItem") + this.data("shownPerSlide")) / this.data("shownPerSlide");
         };
         
         return this.initialize();
    };
})(jQuery);

 /****
    * @author virajs@
    *
    * @require jQuery
    * 
    * Data Store Utility
    * ===========================
    * Handles sending requests to a service, and setting necessary
    * params on that request. Also stores requests for future use.
    *
    *
    * Examples
    * ===========================
    * var store = new enterprise.util.dataStore({
    *     url: 'http://myservice.com',
    *     fields: {
    *         pageSize: 5
    *     },
    *     idField: 'pageNumber'
    * });
    * 
    * //To make request for 3rd page:
    * store.request(3, function(data) { alert(data); });
    *
    * Default Options
    * ===========================
    *
    * var options = {
    *                 url                     : '',   //url of service
    *                 fields                  : {},   //additional fields to send to url
    *                 idField                 : 'pageNumber', // id of field to change per request
    *                 pagesPreloaded          : 1,    // initalize state of store to aviod making
    *                                                 // unncessary requests
    *                 adjustPreloadedRequests : false // set to true to adjust the request id
    *                                                 // by the number of pages preloaded
    *               };
    *
  *****/
var enterprise = enterprise || {};
enterprise.util = enterprise.util || {};

(function($) {
    enterprise.util.dataStore = function(newOptions) {
        var baseOptions = {
            url                     : '',
            fields                  : {},
            idField                 : 'pageNumber',
            pagesPreloaded          : 1,
            adjustPreloadedRequests : false,
            requestType             : 'POST',
            cache                   : true
        };
                                                                                                                                                             
        /**
         * Create the options by extending the base options, with options passed in.
         */
        var options = $.extend(baseOptions, newOptions);

        var fields = options.fields;
        var requestStore = {};

        // setup store with preloaded requests
        for(var i = 0; i < options.pagesPreloaded; i++)
            requestStore[i] = {status: 'done'};

        /**
         * gets set paramter field
         *
         * @param field name
         * @return value
         */
        var getField = function(field) {
            return fields[field];
        };

        /**
         * sets paramter field for request
         *
         * @param field name
         * @param value to set
         */
        var setField = function(field, value) {
            fields[field] = value;
        };

        /**
         * returns request object given a request id
         *
         * @param request id
         * @return request object
         */
        var getRequest = function(id, returnFailures) {
            if(requestStore[id]) {
                if(returnFailures || requestStore[id].status != 'failed')
                    return requestStore[id];
            } 
            return false;
        };

        /**
         * Make a request given a request Id and onsuccess 
         *
         * @param request id
         * @param onsuccess method to send the requested data to
         * @param context with which to run onsuccess [optional]
         * @return boolean indicating if request was already complete
         */
        var request = function(id, onsuccess, onerror, scope) {
            scope = scope || this;

            if(requestStore[id] && requestStore[id].status && requestStore[id].status == 'done') {
                onsuccess.call(scope, requestStore[id].data);
            } else if(!requestStore[id] || (requestStore[id].status && requestStore[id].status == 'failed')) {
                var args = $.extend(fields, {});
                args[options.idField] = options.adjustPreloadedRequests ? (id - options.pagesPreloaded + 1) : id;

                requestStore[id] = {status: 'pending'};
                $.ajax({
                    url: options.url, 
                    data: args,
                    success: function(data, textStatus) {
                        requestStore[id] = {
                            data: data,
                            status: 'done'
                        };

                        onsuccess.call(scope, data, textStatus);
                    },
                    error: function(XMLHttpRequest, textStatus, errorThrown) {
                        requestStore[id] = {
                            status: 'failed'
                        };

                        onerror.call(scope, XMLHttpRequest, textStatus, errorThrown);
                    },
                    type: options.requestType,
                    cache: options.cache
                });
                return false;
            }

            return true;
        };

        return {
            getField: getField,
            setField: setField,
            getRequest: getRequest,
            request: request
        };
    };
})(jQuery);
  /*****
    * @author virajs@
    * 
    * @require plugins/enterprise.util.data_store.js
    * @require plugins/jquery.amazon.carousel.js
    *
    * Dynamic Carousel 
    * ===========================
    * Wrapper around carousel that handles requesting additional
    * items, presenting a loading icon when in action. Page dots
    * can also be used to make requests.
    * 
    * Set up
    * ===========================
    * same as carousel
    *   
    * Default Options
    * ===========================
    *
    * var options = {
    *                 totalItemsCount       : 0,                 // total number of items to carousel
    *                                                            // through
    *                 requestSize           : null,              // size of each request, defaults to 
    *                                                            // value of carouselPageSize
    *                 carouselPageSize      : 5,                 // number of items in each panel
    *                 carouselOptions       : {},                // options passed to carousel plugin
    *                 dataStoreOptions      : {},                // see dataStore util for config options
    *                                                            // used to request more items
    *                 processData           : function(cont) {}, // given container encompassing retrieved
    *                                                            // data, do postprocessing (add QI...)
    *                 eventTracking         : {},                // Appends moveLeft, moveRight, and 
    *                                                            // onPageSelect handlers to end of those
    *                                                            // handler chains to be applied last
    *                 loadingClass          : 'loading',         // class of loading icon
    *                 loadingElementClass   : 'upsellWidgetItem',// class of each item (for widths sake)
    *                 debug                 : true
    *             };
    *             
  *****/
(function($){
    jQuery.fn.dynamicCarousel = function(newOptions) {     
        /**
         * Make sure that only one DOM element is present,
         * If not, loop through and call .tabs(newOptions) on each.
         *
         * Make sure to "return this", so calls can be chained.
         */
        if (this.length > 1) {
            for (var i = 0; i < this.length; i++) {
                $(this[i]).carousel(newOptions);
            }
            return this;
        }
         
        /**
         * Refer to carousel, when "this" is out of scope
         */
        var carousel = this;

        /**
         * Base Options needed for the default configuration
         */
        var baseOptions = {
            totalItemsCount       : 0,
            requestSize           : null,
            carouselPageSize      : 5,
            carouselOptions       : {},
            dataStoreOptions      : {},
            processData           : function() {},
            eventTracking         : {},
            loadingClass          : 'loading',
            loadingElementClass   : 'upsellWidgetItem',
            errorMessageClass     : 'errorMessage',
            buildDom              : false,
            debug                 : true
        };  
         
         /**
          * Create the options by extending the base options, with options passed in.
          */
         var options = $.extend(baseOptions, newOptions);

         //set defaults of carousel options
         options.carouselOptions = $.extend({
             contentClass: '.upsellWidgetItems',
             containerClass: '.upsellWidgetItemContainer',
             containerItem: '.upsellWidgetItem',
             stepSize: 5,
             pageDots: '.pageDots',
             pageDotClass: 'a',
             leftArrow: '.leftarrow',
             rightArrow: '.rightarrow',
             preventDefault: true
         }, options.carouselOptions);

         // default requestSize if not specified        
         if(typeof options.requestSize != 'number') options.requestSize = options.carouselPageSize;
 
         /***
          *
          * debug logging
          *
          ****/
         var log = function(data){
             if (options.debug) {
                 try {
                     console.log("%o: %o", data, this);
                 } catch (e) {
                     $('BODY').append($("<div class='error'>" + data + "</div>"));
                 }
             }
         };
        
         var loadingStore = {};   //contains existing loading clones by request id 
         var dataStore;           //dataStore object
         var setTabFocus = false; //when tabbing, setting true will set focus after carousel
                                  //moves to prevent a bad state
         
         /***
          * helper function to turn variable into an array
          *
          * @param anything
          * @return array
          ****/
         var makeArray = function(a) {
             if($.isFunction(a)) 
                 return [a];
             else 
                 return $.isArray(a) ? a : []; 
         };

         /***
          * binds an onblur event that moves the carousel right on the last
          * tabable item in el
          *
          * @param element/jquery object
          ****/
         var setPageTabHandler = function(container) {
             var self = this;
             $(options.carouselOptions.containerItem + ':nth-child(' + options.carouselPageSize + 'n+' + 
               options.carouselPageSize + ')', container).each(function() {
                 // bind tab (not shift+tab) event
                 $('*[tabIndex]:last', this).keydown(function(e) {
                     if(!e.shiftKey && e.keyCode == 9) {
                         setTabFocus = true;
                         self.moveSlider('right');
                         e.preventDefault();
                     }
                 });
             });
         };

         /***
          * creates generic loadingMessage object
          *
          * @param request id
          ****/
         var loadingMessage = function(id) {
	     var carousel = this,
                 container,
                 waitingMessage, 
                 errorMessage; 
             
             var hideErrorMessage = function() {
                 if(typeof errorMessage != 'undefined') {
                     errorMessage.hide();
                     $(waitingMessage).show().focus(); 
                 }
             };

             var createErrorMessage = function() {
                 var error = document.createElement('div');
                 error.innerHTML = 'We were unable to load the next set of items. Please '; 

                 var tryAgainLink = document.createElement('a');
                 $(tryAgainLink).attr('href', '#').html('try again').click(function(e) {

                     setTabFocus = true;
                     requestHandler.call(carousel, id);
                     hideErrorMessage();

                     e.preventDefault();
                 });

                 errorMessage = $(error);
                 errorMessage.append(tryAgainLink).attr('tabIndex', 0).css({
                     display: 'none'
                 }).addClass(options.errorMessageClass).appendTo(container);
             };
             
             var showErrorMessage = function() {
                if(typeof errorMessage == 'undefined') createErrorMessage();

                $(waitingMessage).hide();
                errorMessage.show().focus();
             };

             var init = function() {
                 //create waitingMessage
                 waitingMessage = document.createElement('span');
	         waitingMessage.className = 'offscreen';
	         waitingMessage.tabIndex = 0;
	         waitingMessage.innerHTML = 'Loading the next set of items. Please wait.';

                 //create container div
                 var loadContainer = document.createElement('div');
                 loadContainer.className = 'loadingContainer';
                 container = $(loadContainer).eq(0);
          
                 //create fake items to correctly simulate width
                 for(var i = 0; i < options.carouselOptions.stepSize; i++) {
                     var li = document.createElement('li');
                     $(li).addClass(options.loadingElementClass).height(5);

                     // add offscreen text to first blank item
                     if(i == 0) $(li).append(waitingMessage); 

                     container.append(li); 
                 } 
  
                 var loadDiv = document.createElement('div');
                 loadDiv.className = options.loadingClass;

                 container.append(loadDiv).css({
                     position: 'relative',
                     'float': 'left',
                     height: carousel.currentItem.height()
                 });
             };

             init.call();
   
             return {
                 showError: showErrorMessage,
                 hideError: hideErrorMessage,
                 container: container
             };
         };

         /***
          * Clones loading container for specific request 
          *
          * @param requestId
          ****/
         var addLoadingContainer = function(id) {
             if(!loadingStore[id]) {
                 loadingStore[id] = loadingMessage.call(this, id);
                 loadingStore[id].container.appendTo(this.content); 
             }
         };

         /***
          * Makes request, and inserts requested data and hides
          * loading messaging 
          *
          * @param requestId
          ****/
         var requestHandler = function(requestId) {
             var self = this;

             // recalculate contentwidth and number of items
             this.calculateContentLength();

             //hide error is previous request was a fail
             if(loadingStore[requestId]) loadingStore[requestId].hideError();

             var localSetTabFocus = setTabFocus;
             dataStore.request(requestId, function(data) {
                 // wrap data in container and insert before loading container
                 var containerDiv = document.createElement('div');
                 $(containerDiv).append(data).insertBefore(loadingStore[requestId].container);
 
                 //bind handler for shift+tab event
                 var firstEl = $(options.carouselOptions.containerItem + ' *[tabIndex]:first', containerDiv);
                 firstEl.keydown(function(e) { 
                     if(e.shiftKey && e.keyCode == 9) {
                         self.moveSlider('left');
                     }
                 });
                 if(localSetTabFocus) {
                     //set focus on first link in carousel if we were tabbing
                     firstEl.get(0).focus();
                     setTabFocus = false;
                 }

                 loadingStore[requestId].container.remove()
                 self.calculateContentLength();
                 
                 // apply necessary handlers to new items
                 options.processData(containerDiv); 
                 setPageTabHandler.call(self, containerDiv);
             }, function(){
                 loadingStore[requestId].showError.call(this);
             }, carousel);
         };
     
         /***
          * Decides whether to make request, and add loading messaging
          * where necessary 
          *
          * @param next Item to request
          * @param whether to backfill requests (if using pagination dots) [optional]
          ****/
         var makeRequest = function(nextRequestItem, verifyRequest) {
             var nextRequestId = nextRequestItem/options.requestSize;
             //if next page must be requested
             if( nextRequestItem > 0 && 
                 nextRequestItem < options.totalItemsCount && 
                 nextRequestItem % options.requestSize == 0 && 
                 dataStore.getRequest(nextRequestId) === false) {

                 // if jumping to later page, back fill preceding pages
                 for(var i = (verifyRequest === true ? 0 : nextRequestId); i <= nextRequestId; i++) {
                     if(!dataStore.getRequest(i)) addLoadingContainer.call(this, i);
                 }
                     
                 requestHandler.call(this, nextRequestId);
             } 
         };

         /***
          * Arranges callbacks to enable ajax 
          *
          ****/
         var updateCallbacks = function() {
             options.carouselOptions.beforeInit = makeArray(typeof options.carouselOptions.beforeInit);
             options.carouselOptions.afterMove = makeArray(typeof options.carouselOptions.afterMove);
             options.carouselOptions.moveRight = makeArray(typeof options.carouselOptions.moveRight);
             options.carouselOptions.moveLeft = makeArray(typeof options.carouselOptions.moveLeft);
             options.carouselOptions.onPageSelect = makeArray(typeof options.carouselOptions.onPageSelect);

             options.carouselOptions.beforeInit.push(function() {
                 //remove hrefs from arrows and pagination dots for accessibility
                 $(this).find(options.carouselOptions.pageDots + ' ' + options.carouselOptions.pageDotClass + ', ' + 
                                  options.carouselOptions.rightArrow + ', ' + options.carouselOptions.leftArrow).removeAttr('href');

                 setPageTabHandler.call(this, this);
                 return {isOK: true};
             });

             options.carouselOptions.moveRight.push(function() {
                 var showingItem = this.getShowingItem();
                 var nextRequestItem = showingItem + options.carouselOptions.stepSize;
                 makeRequest.call(this, nextRequestItem);
                 return {isOK: true};
             }); 

             options.carouselOptions.moveLeft.push(function() {
                 var showingItem = this.getShowingItem();
                 var nextRequestItem = showingItem - options.carouselOptions.stepSize;
                 makeRequest.call(this, nextRequestItem);
                 return {isOK: true};
             }); 

             options.carouselOptions.afterMove.push(function(panelNum) {
                 if(setTabFocus) {
                     var showingItem = this.getShowingItem();
                     nextTabEl = this.content.find(options.carouselOptions.containerItem + ':eq(' + showingItem + ') *[tabIndex]:first').get(0);
                     if(nextTabEl) nextTabEl.focus();
                     setTabFocus = false;
                 }
                 return {isOK: true};
             });

             options.carouselOptions.onPageSelect.push(function(panelNum) {
                 var nextRequestItem = panelNum * options.carouselPageSize;
                 makeRequest.call(this, nextRequestItem, true);
                 return {isOK: true};
             }); 

             if(options.eventTracking.moveRight) options.carouselOptions.moveRight.push(options.eventTracking.moveRight);
             if(options.eventTracking.moveLeft) options.carouselOptions.moveLeft.push(options.eventTracking.moveLeft);
             if(options.eventTracking.onPageSelect) options.carouselOptions.onPageSelect.push(options.eventTracking.onPageSelect);
         };

         var buildDom = function() {
             var parentNode = document.createElement('div');
             parentNode.className = 'carousel';
             
             var rightArrow = document.createElement('div');
             rightArrow.className = 'rightarrow';
             var leftArrow = document.createElement('div');
             leftArrow.className = 'leftarrow';

             var container = document.createElement('div');
             container.className = 'container';
             $(parentNode).css('width', options.carouselPageSize * carousel.find('li.'+options.loadingElementClass+':first').outerWidth(true));
            
             $(parentNode).append(rightArrow).append(container).append(leftArrow).appendTo($(carousel).parent());
             $(container).append(carousel);
             carousel = parentNode;
         }

         /***
          * Initializes dataStore and carousel 
          *
          ****/
         var initialize = function() {
             if(options.buildDom)
                 buildDom();
                 
             //initialize data store
             dataStore = new enterprise.util.dataStore(options.dataStoreOptions);

             // rearrange carousel callbacks
             updateCallbacks();

             //set up carousel
             return $(carousel).carousel(options.carouselOptions);
         };

         return initialize();
    };
})(jQuery);

