
/**
 * @file tabs.js
 * jQuery UI Tabs (Tabs 3)
 *
 * This is nothing more than the pure jquery UI tabs implementation.
 * It has been implemented under the Drupal.Views.Tabs namespace to
 * avoid conflicts with alternatve versions of jquery, jquery UI.
 */

Drupal.Views = Drupal.Views || {};

Drupal.Views.Tabs = function(el, options) {

  this.source = el;

  this.options = $.extend({

    // basic setup
    initial: 0,
    event: 'click',
    disabled: [],
    // TODO bookmarkable: $.ajaxHistory ? true : false,
    unselected: false,
    toggle: options.unselected ? true : false,

    // Ajax
    spinner: 'Loading&#8230;',
    cache: false,
    hashPrefix: 'tab-',

    // animations
    /*fxFade: null,
    fxSlide: null,
    fxShow: null,
    fxHide: null,*/
    fxSpeed: 'normal',
    /*fxShowSpeed: null,
    fxHideSpeed: null,*/

    // callbacks
    add: function() {},
    remove: function() {},
    enable: function() {},
    disable: function() {},
    click: function() {},
    hide: function() {},
    show: function() {},
    load: function() {},

    // CSS classes
    navClass: 'ui-tabs-nav',
    selectedClass: 'ui-tabs-selected',
    disabledClass: 'ui-tabs-disabled',
    containerClass: 'ui-tabs-container',
    hideClass: 'ui-tabs-hide',
    loadingClass: 'ui-tabs-loading'

  }, options);

  this.tabify(true);

  // save instance for later
  var uuid = 'instance-' + Drupal.Views.Tabs.prototype.count++;
  Drupal.Views.Tabs.instances[uuid] = this;
  this.source.data('UI_TABS_UUID', uuid);

};

// static
Drupal.Views.Tabs.instances = {};

$.extend(Drupal.Views.Tabs.prototype, {
  animating: false,
  count: 0,
  tabify: function(init) {

    this.$tabs = $('a:first-child', this.source);
    this.$containers = $([]);

    var self = this, o = this.options;

    this.$tabs.each(function(i, a) {
      // inline tab
      if (a.hash && a.hash.replace('#', '')) { // safari 2 reports '#' for an empty hash
        self.$containers = self.$containers.add(a.hash);
      }
      // remote tab
      else {
        var id = a.title && a.title.replace(/\s/g, '_') || o.hashPrefix + (self.count + 1) + '-' + (i + 1), url = a.href;
        a.href = '#' + id;
        a.url = url;
        self.$containers = self.$containers.add(
          $('#' + id)[0] || $('<div id="' + id + '" class="' + o.containerClass + '"></div>')
            .insertAfter( self.$containers[i - 1] || self.source )
        );
      }
    });

    if (init) {

      // Try to retrieve initial tab from fragment identifier in url if present,
      // otherwise try to find selected class attribute on <li>.
      this.$tabs.each(function(i, a) {
        if (location.hash) {
          if (a.hash == location.hash) {
            o.initial = i;
            // prevent page scroll to fragment
            //if (($.browser.msie || $.browser.opera) && !o.remote) {
            if ($.browser.msie || $.browser.opera) {
              var $toShow = $(location.hash), toShowId = $toShow.attr('id');
              $toShow.attr('id', '');
              setTimeout(function() {
                $toShow.attr('id', toShowId); // restore id
              }, 500);
            }
            scrollTo(0, 0);
            return false; // break
          }
        } else if ( $(a).parents('li:eq(0)').is('li.' + o.selectedClass) ) {
          o.initial = i;
          return false; // break
        }
      });

      // attach necessary classes for styling if not present
      $(this.source).is('.' + o.navClass) || $(this.source).addClass(o.navClass);
      this.$containers.each(function() {
        var $this = $(this);
        $this.is('.' + o.containerClass) || $this.addClass(o.containerClass);
      });

      // highlight tab accordingly
      var $lis = $('li', this.source);
      this.$containers.addClass(o.hideClass);
      $lis.removeClass(o.selectedClass);
      if (!o.unselected) {
        this.$containers.slice(o.initial, o.initial + 1).show();
        $lis.slice(o.initial, o.initial + 1).addClass(o.selectedClass);
      }

      // trigger load of initial tab is remote tab
      if (this.$tabs[o.initial].url) {
        this.load(o.initial + 1, this.$tabs[o.initial].url);
        if (o.cache) {
          this.$tabs[o.initial].url = null; // if loaded once do not load them again
        }
      }

      // disabled tabs
      for (var i = 0, position; position = o.disabled[i]; i++) {
        this.disable(position);
      }

    }

    // setup animations
    var showAnim = {}, hideAnim = {}, showSpeed = o.fxShowSpeed || o.fxSpeed,
      hideSpeed = o.fxHideSpeed || o.fxSpeed;
    if (o.fxSlide || o.fxFade) {
      if (o.fxSlide) {
        showAnim['height'] = 'show';
        hideAnim['height'] = 'hide';
      }
      if (o.fxFade) {
        showAnim['opacity'] = 'show';
        hideAnim['opacity'] = 'hide';
      }
    } else {
      if (o.fxShow) {
        showAnim = o.fxShow;
      } else { // use some kind of animation to prevent browser scrolling to the tab
        showAnim['min-width'] = 0; // avoid opacity, causes flicker in Firefox
        showSpeed = 1; // as little as 1 is sufficient
      }
      if (o.fxHide) {
        hideAnim = o.fxHide;
      } else { // use some kind of animation to prevent browser scrolling to the tab
        hideAnim['min-width'] = 0; // avoid opacity, causes flicker in Firefox
        hideSpeed = 1; // as little as 1 is sufficient
      }
    }

    // callbacks
    var click = o.click, hide = o.hide, show = o.show;

    // reset some styles to maintain print style sheets etc.
    var resetCSS = { display: '', overflow: '', height: '' };
    if (!$.browser.msie) { // not in IE to prevent ClearType font issue
      resetCSS['opacity'] = '';
    }

    // hide a tab, animation prevents browser scrolling to fragment
    function hideTab(clicked, $hide, $show) {
      $hide.animate(hideAnim, hideSpeed, function() { //
        $hide.addClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
        hide(clicked, $show, $hide[0]);
        if ($show) {
          showTab(clicked, $hide, $show);
        }
      });
    }

    // show a tab, animation prevents browser scrolling to fragment
    function showTab(clicked, $hide, $show) {
      // show next tab
      if (!(o.fxSlide || o.fxFade || o.fxShow)) {
        $show.css('display', 'block'); // prevent occasionally occuring flicker in Firefox cause by gap between showing and hiding the tab containers
      }
      $show.animate(showAnim, showSpeed, function() {
        $show.removeClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
        if ($.browser.msie) {
          $hide[0].style.filter = '';
          $show[0].style.filter = '';
        }
        show(clicked, $show[0], $hide[0]);
        self.animating = false;
      });

    }

    // switch a tab
    function switchTab(clicked, $hide, $show) {
      /*if (o.bookmarkable && trueClick) { // add to history only if true click occured, not a triggered click
        $.ajaxHistory.update(clicked.hash);
      }*/
      $(clicked).parents('li:eq(0)').addClass(o.selectedClass)
        .siblings().removeClass(o.selectedClass);
      hideTab(clicked, $hide, $show);
    }

    // tab click handler
    function tabClick(e) {

      //var trueClick = e.clientX; // add to history only if true click occured, not a triggered click
      var $li = $(this).parents('li:eq(0)'), $hide = self.$containers.filter(':visible'), $show = $(this.hash);

      // if tab may be closed
      if (o.toggle && !$li.is('.' + o.disabledClass) && !self.animating) {
        if ($li.is('.' + o.selectedClass)) {
          $li.removeClass(o.selectedClass);
          hideTab(this, $hide);
          this.blur();
          return false;
        } else if (!$hide.length) {
          $li.addClass(o.selectedClass);
          showTab(this, $hide, $show);
          this.blur();
          return false;
        }
      }

      // If tab is already selected or disabled, animation is still running or click callback
      // returns false stop here.
      // Check if click handler returns false last so that it is not executed for a disabled tab!
      if ($li.is('.' + o.selectedClass + ', .' + o.disabledClass)
        || self.animating || click(this, $show[0], $hide[0]) === false) {
        this.blur();
        return false;
      }

      self.animating = true;

      // show new tab
      if ($show.length) {

        // prevent scrollbar scrolling to 0 and than back in IE7, happens only if bookmarking/history is enabled
        /*if ($.browser.msie && o.bookmarkable) {
          var showId = this.hash.replace('#', '');
          $show.attr('id', '');
          setTimeout(function() {
            $show.attr('id', showId); // restore id
          }, 0);
        }*/

        if (this.url) { // remote tab
          var a = this;
          self.load(self.$tabs.index(this) + 1, this.url, function() {
            switchTab(a, $hide, $show);
          });
          if (o.cache) {
            this.url = null; // if loaded once do not load them again
          }
        } else {
          switchTab(this, $hide, $show);
        }

        // Set scrollbar to saved position - need to use timeout with 0 to prevent browser scroll to target of hash
        /*var scrollX = window.pageXOffset || document.documentElement && document.documentElement.scrollLeft || document.body.scrollLeft || 0;
        var scrollY = window.pageYOffset || document.documentElement && document.documentElement.scrollTop || document.body.scrollTop || 0;
        setTimeout(function() {
          scrollTo(scrollX, scrollY);
        }, 0);*/

      } else {
        throw Drupal.t('jQuery UI Tabs: Mismatching fragment identifier.');
      }

      this.blur(); // prevent IE from keeping other link focussed when using the back button

      //return o.bookmarkable && !!trueClick; // convert trueClick == undefined to Boolean required in IE
      return false;

    }

    // attach click event, avoid duplicates from former tabifying
    this.$tabs.unbind(o.event, tabClick).bind(o.event, tabClick);

  },
  add: function(url, text, position) {
    if (url && text) {
      var o = this.options;
      position = position || this.$tabs.length; // append by default
      if (position >= this.$tabs.length) {
        var method = 'insertAfter';
        position = this.$tabs.length;
      } else {
        var method = 'insertBefore';
      }
      if (url.indexOf('#') == 0) { // ajax container is created by tabify automatically
        var $container = $(url);
        // try to find an existing element before creating a new one
        ($container.length && $container || $('<div id="' + url.replace('#', '') + '" class="' + o.containerClass + ' ' + o.hideClass + '"></div>'))
          [method](this.$containers[position - 1]);
      }
      $('<li><a href="' + url + '"><span>' + text + '</span></a></li>')
        [method](this.$tabs.slice(position - 1, position).parents('li:eq(0)'));
      this.tabify();
      o.add(this.$tabs[position - 1], this.$containers[position - 1]); // callback
    } else {
      throw Drupal.t('jQuery UI Tabs: Not enough arguments to add tab.');
    }
  },
  remove: function(position) {
    if (position && position.constructor == Number) {
      this.$tabs.slice(position - 1, position).parents('li:eq(0)').remove();
      this.$containers.slice(position - 1, position).remove();
      this.tabify();
    }
    this.options.remove(); // callback
  },
  enable: function(position) {
    var $li = this.$tabs.slice(position - 1, position).parents('li:eq(0)'), o = this.options;
    $li.removeClass(o.disabledClass);
    if ($.browser.safari) { // fix disappearing tab after enabling in Safari... TODO check Safari 3
      $li.animate({ opacity: 1 }, 1, function() {
        $li.css({ opacity: '' });
      });
    }
    o.enable(this.$tabs[position - 1], this.$containers[position - 1]); // callback
  },
  disable: function(position) {
    var $li = this.$tabs.slice(position - 1, position).parents('li:eq(0)'), o = this.options;
    if ($.browser.safari) { // fix opacity of tab after disabling in Safari... TODO check Safari 3
      $li.animate({ opacity: 0 }, 1, function() {
          $li.css({ opacity: '' });
      });
    }
    $li.addClass(this.options.disabledClass);
    o.disable(this.$tabs[position - 1], this.$containers[position - 1]); // callback
  },
  click: function(position) {
    this.$tabs.slice(position - 1, position).trigger('click');
  },
  load: function(position, url, callback) {
    var self = this,
      o = this.options,
      $a = this.$tabs.slice(position - 1, position).addClass(o.loadingClass),
      $span = $('span', $a),
      text = $span.html();

    // shift arguments
    if (url && url.constructor == Function) {
      callback = url;
    }

    // set new URL
    if (url) {
      $a[0].url = url;
    }

    // load
    if (o.spinner) {
      $span.html('<em>' + o.spinner + '</em>');
    }
    setTimeout(function() { // timeout is again required in IE, "wait" for id being restored
      $($a[0].hash).load(url, function() {
        if (o.spinner) {
          $span.html(text);
        }
        $a.removeClass(o.loadingClass);
        // This callback is needed because the switch has to take place after loading
        // has completed.
        if (callback && callback.constructor == Function) {
          callback();
        }
        o.load(self.$tabs[position - 1], self.$containers[position - 1]); // callback
      });
    }, 0);
  }
});
