/**
 * 汎用タブクラス
 *
 * 使い方:
 *
 * HTML 部分:
 *
 * <!-- タブバー -->
 * <ul id="tab_bar">
 *     <li><a href="#tab_1">タブ1</a></li>
 *     <li><a href="#tab_2" class="active">タブ2</a></li>
 *     <li><a href="#tab_3">タブ3</a></li>
 * </ul>
 *
 * <!-- タブの中身を表示する部分 -->
 * <div>
 *     <p id="tab_1">タブ1に対応する要素</p>
 *     <p id="tab_2">タブ2に対応する要素 (デフォルト)</p>
 *     <p id="tab_3">タブ3に対応する要素</p>
 * </div>
 *
 * スクリプト部分:
 *
 * // onload を待ってから
 * // A タグのリストを直接指定する。
 * // アクティブな要素には active, そうでないクラスには inactive クラスをつける。
 * Tab.Group.byAnchors(document.getElementById('tab_bar')
 *                             .getElementsByTagName('a'),
 *                     'active',
 *                     'inactive');
 *
 * // id="tab_bar" の下の A タグを使う。
 * // アクティブな要素には shown, そうでないクラスには hide クラスをつける。
 * // この場合、あらかじめ shown クラスがつけられたタグがデフォルトになる。
 * Tab.Group.byAnchors('tab_bar', 'shown', 'hide');
 */


// {{{ Tab

/**
 * タブ
 *
 * 使い方:
 *   var tab = new Tab(document.getElementById('tab'), 'click', false);
 *   tab.activate = function(event)
 *   {
 *       // 親メソッドの呼び出し
 *       Tab.prototype.activate(event);
 *       alert('clicked');
 *   }
 *   tab.deactivate = function()
 *   {
 *       Tab.prototype.deactivate();
 *   }
 *
 *   var tabBar = new Tab.Group();
 *   tabBar.add(tab);
 *
 * プロパティ:
 *
 * DOMElement element
 *   タブになっている要素
 * Tab.Event event
 *   イベントハンドラ管理
 * Tab.Group group
 *   タブが属しているグループオブジェクト
 * Function onactivate(DOMEvent event = null)
 *   タブをアクティブにするときの動作
 * Function ondeactivate()
 *   タブを非アクティブにするときの動作
 */
function Tab(element, type, useCapture)
{
    this.element = element;
    var __self = this;
    this.event = new Tab.Event(
        element,
        type || 'click',
        function(event)
        {
            __self.onactivate(event);
        },
        useCapture || false);
}

Tab.prototype.onactivate = function()
{
    this.group.deactivateExcept(this);
};

Tab.prototype.ondeactivate = function() {};

/**
 * A 要素の href="#content_id" から Tab を作成する。
 *
 * element の hash プロパティから、ペアとなる要素 (target) 取得する。
 * アクティブになったとき ( = element がクリックされたとき)
 * element と target の className プロパティ から
 * inactive クラスを削除し active クラス を加える。
 * 非アクティブになったときは逆。
 *
 * 細かい設定はできない。
 */
Tab.byAnchor = function(element, active, inactive)
{
    var tab = null, target = null, pathname = '';

    if (typeof element == 'string') {
        element = document.getElementById(element);
    }

    /**
     * 順に
     * DOM 要素でない
     * A 要素でない
     * ハッシュがない
     */
    if (typeof element.tagName != 'string' ||
        element.tagName.toLowerCase() != 'a' ||
        !element.hash.match(/^#(.+)$/)
    ) {
        return null;
    }

    pathname = element.pathname;
    if (pathname.charAt(0) != '/') {
        pathname = '/' + pathname;
    }
    // 別ページへのリンク
    if (pathname != document.location.pathname) {
        return null;
    }

    target = document.getElementById(RegExp.$1);
    if (!target) {
        return null;
    }

    tab = new Tab(element);
    tab.target = target;
    tab.active = active || 'active';
    tab.inactive = inactive || 'inactive';
    tab.toggle = Tab.byAnchor.toggle;
    tab.re = new RegExp('\\b(' + tab.active + '|' + tab.inactive + ')\\b');

    tab.onactivate = function(event)
    {
        if (event) {
            event.preventDefault();
        }
        Tab.prototype.onactivate.apply(this);
        this.toggle(this.element, true);
        this.toggle(this.target, true);
    };

    tab.ondeactivate = function()
    {
        Tab.prototype.ondeactivate.apply(this);
        this.toggle(this.element, false);
        this.toggle(this.target, false);
    };

    return tab;
};

Tab.byAnchor.toggle = function(element, on)
{
    var re = this.re;
    if (!re.test(element.className)) {
        element.className += ' ' + (on ? this.active : this.inactive);
        return;
    } else if (on  && RegExp.$1 == this.active ||
               !on && RegExp.$1 == this.inactive) {
        return;
    }
    element.className = element.className.replace(re, RegExp.$1 == this.active
                                                      ? this.inactive
                                                      : this.active);
};

// - Tab }}}

// {{{ Tab.Event

/**
 * イベント管理用ユーティリティ
 *
 * IE/その他の差異を吸収するためのイベント設定関数。
 */
Tab.Event = function(element, type, callback, useCapture)
{
    this.element = element;
    this.type = type;
    this.callback = callback;
    this.useCapture = useCapture;
    this.start();
};

if (window.attachEvent) {

    Tab.Event.listeners = [];

    Tab.Event.prototype.start = function()
    {
        this.started = true;
        var __self = this;
        this._callback = function(event)
        {
            event.preventDefault = function()
            {
                this.returnValue = false;
            };
            __self.callback(event);
        };
        this.element.attachEvent('on' + this.type, this._callback);
        Tab.Event.listeners.push(this);
    };

    Tab.Event.prototype.stop = function()
    {
        if (!this.started) {
            return;
        }

        this.element.detachEvent('on' + this.type, this._callback);
    };

    // IE 用 メモリリーク対策
    window.attachEvent('onunload', function()
    {
        var listeners = Tab.Event.listeners,
            i = 0,
            len = listeners.length;
        for ( ; i < len; i++) {
            listeners[i].stop();
        }
    });

} else {

    Tab.Event.prototype.start = function()
    {
        this.element.addEventListener(this.type,
                                      this.callback,
                                      this.useCapture);
    };

}

// - Tab.Event }}}

// {{{ Tab.Group

/**
 * タブグループ
 *
 * プロパティ:
 *
 * Array members
 *   Tab の配列
 *
 * Function add(Tab tab)
 *   グループにタブを加える。
 *   tab の group プロパティがこのグループになる。
 *
 * Function deactivateExcept(Tab tab)
 *   tab 以外の member を deactivate() する。
 *
 * @param Array tabs
 *   Tab の配列
 */
Tab.Group = function(tabs)
{
    this.members = tabs || [];
};

Tab.Group.prototype.add = function(tab)
{
    tab.group = this;
    this.members.push(tab);
}

Tab.Group.prototype.deactivateExcept = function(tab)
{
    for (var members = this.members, i = 0, len = members.length; i < len; i++) {
        if (members[i] !== tab) {
            members[i].ondeactivate();
        }
    }
};

/**
 * ids から Tab.byAnchor でつくった Tab インスタンスを取得し、
 * Tab.Group にして返す。
 */
Tab.Group.byAnchors = function(ids, active, inactive)
{
    if (typeof ids == 'string') {
        ids = document.getElementById(ids);
        ids = ids ? ids.getElementsByTagName('a') : [];
    } else if (ids.getElementsByTagName) {
        ids = ids.getElementsByTagName('a');
    }

    var len = ids.length,
        i = 0,
        tab = null,
        group = new Tab.Group(),
        j = 0,
        jlen = 0,
        toActivate = null,
        re = new RegExp('\\b' + (active || 'active') + '\\b');

    for ( ; i < len; i++) {
        tab = Tab.byAnchor(ids[i], active, inactive);
        if (!tab) {
            continue;
        }

        if (re.test(tab.element.className)) {
            toActivate = tab;
        }

        group.add(tab);
    }

    if (toActivate) {
        toActivate.onactivate(null);
    }

    return group;
};

// - Tab.Group }}}

