﻿/**
* Infobubble
* @author: pop-id [kh]
* @classDescription: Creates and positions an overlay bubble
* @version: 0.1
* @dependencies: Prototype v1.6.1, Effects.js (Script.aculo.us)
**/

var InfoBubble = Class.create({
    initialize: function (triggers, options) {

        this.options = Object.extend({
            animationDuration: 0.2, // how long it takes for animation to complete
            appearDelay: 0.2, // time (in seconds) to wait before starting appear effect
            attachTo: false, // $$ (or false) of the element you want to attach the bubble to 
            container: 'ib', // id of infobubble
            hoverClass: 'hover',
            offsetLeft: 0,
            offsetTop: 0,
            separator: '-',
            targets: 'ul' // css selector of element to set as content (relative to triggers)
        }, options || {});

        this.triggerPath = triggers;
        var triggers = $$(triggers);

        // instance variables
        this.targets = [];

        this.triggers = [];

        var size = triggers.length;

        if (size > 0) {

            // IE doesn't handle opacity fades with transparent pngs right, 
            // so they don't get an animation
            if (Prototype.Browser.IE) {
                this.options.animationDuration = 0;
            }

            // event caching
            this.events = {
                mouseenter: this.__mouseEnter.bindAsEventListener(this),
                mouseleave: this.__mouseLeave.bindAsEventListener(this)
            };

            for (var i = 0; i < size; i++) {
                var targets = triggers[i].select(this.options.targets);
                if (targets.length > 0) {

                    this.triggers.push(triggers[i]);
                    var target = targets[0];
                    //this.targets.push(target);

                    this.bubbleWrap(target);

                    // set event listeners
                    triggers[i].observe('mouseenter', this.events.mouseenter);
                    triggers[i].observe('mouseleave', this.events.mouseleave);
                }
            }
        }

    },

    // wrapping the bubbles in markup
    bubbleWrap: function (target) {

        // cloning... @TODO: move this out into a subclass and overload this method
        var clone = target.previousSiblings()[0].clone(true);

        // wrapping
        var content = target.wrap('div', { 'class': this.options.container + this.options.separator + 'content' });
        var middle = content.wrap('div', { 'class': this.options.container + this.options.separator + 'middle' });
        var container = middle.wrap('div', { 'class': this.options.container, 'style': 'display: none' });

        // creating
        var top = new Element('div', { 'class': this.options.container + this.options.separator + 'top' });
        var bottom = new Element('div', { 'class': this.options.container + this.options.separator + 'bottom' });

        // inserting
        content.insert({ top: clone });
        container.insert({ 'top': top }).insert(bottom);
        this.targets.push(container);

    },

    // event handler for mouse enter
    __mouseEnter: function (e) {
        var el = e.findElement(this.triggerPath);
        this.triggers.invoke('removeClassName', this.options.hoverClass);
        el.addClassName(this.options.hoverClass);
        var appearDelay = this.options.appearDelay * 1000; // convert to milliseconds
        this.appearDelay = setTimeout(function () {
            this.showBubble(el);
        } .bind(this), appearDelay);

    },

    // handle mouse leave events
    __mouseLeave: function (e) {
        var el = e.findElement(this.triggerPath);
        this.triggers.invoke('removeClassName', this.options.hoverClass);
        clearTimeout(this.appearDelay); // clear out appear delays
        this.hideBubble(el);
    },


    // display bubble by fading it in
    showBubble: function (el) {

        var index = el.previousSiblings().length;

        var bubble = this.targets[index];

        var elWidth = (this.options.attachTo) ? el.down(this.options.attachTo).getWidth() : el.getWidth();
        var top = this.options.offsetTop + 'px';
        var left = elWidth + this.options.offsetLeft + 'px';

        // IE gets the positioning completely wrong... reworking
        //bubble.clonePosition(el, { setWidth: false, setHeight: false, offsetLeft: left, offsetTop: top });
        if (Prototype.Browser.IE) {
            el.setStyle({ 'zIndex': '1' }); // IE6-7 needs help
        }

        bubble.setStyle({ 'top': top, 'left': left });

        this.appearFx = new Effect.Appear(bubble, {
            duration: this.options.animationDuration
        });

    },

    // hide bubble by fading it out
    hideBubble: function (el) {

        var index = el.previousSiblings().length;

        if (Prototype.Browser.IE) {
            el.setStyle({ 'zIndex': '0' }); // IE6-7 needs help
        }
        var bubble = this.targets[index];

        this.fadeFx = new Effect.Fade(bubble, {
            duration: this.options.animationDuration,
            afterFinish: function (e) {
                bubble.hide(); // make sure it's really hidden
            } .bind(this)
        });

    }

});
