﻿/**
* Carousel v1
* PB October 2008;
*
* Carousel jQuery plug-in.
* Scroll though an unordered list of content, requires at least 3 items;
*
* Requires:
*    jQuery (1.2.6);
*    jquery.easing.js
*
* Supported browsers Firefox 2 & 3(mac & PC), IE6 & 7, Safari 2(Mac), Opera 9.5 (mac & PC).
*
* Example use;
* var options = {
*   scrollIncrement: 1
* }
* $("#carousel").carousel(options);
*
* Supported options;
*    navigation: Boolean, default true,
*    easing: String, default "backout",
*    speed: Number, default 800,
*    visibleItems: Number, default 3,
*    scrollIncrement: Number, default 1
*
* Carousel also supports the following custom events that can be listened for;
*    init:         fired on initialisation of the carousel;
*    preanimate:   fired imediately before the scrolling animation;
*    complete:     fired after the animation has finished;
*
* Theses can be subscibed to in the following manner;
*    $(document).bind('init', function(){
*       custom code here...;
*    });
*
* To do;
* All marked with **HACK**;
*/
(function(jq) {
    jq.fn.carousel = function(options) {
        jQuery.extend(jq.fn.carousel.defaults, options);
        return this.each(function() {
            var carousel = new Carousel(this.id);
        });
    };
    jq.fn.carousel.defaults = {
        navigation: true,
        easing: "linear",
        speed: 400,
        visibleItems: 3,
        scrollIncrement: 1,
        infinate: true
    };
    // Box class;
    function Carousel(id) {
        this.id = id;
        this.init();
    };
    // Box props & methods;
    Carousel.prototype = {
        id: null,   // id of current carousel;
        dist: null, // distance to move carousel animation;
        current: 0, // index of the left most visible item;
        count: 0,   // number of items in carousel;
        items: [],
        init: function() {
            var self = this;
            // gr-carousel class used for css only;
            if (!jq("#" + this.id).hasClass("gr-carousel"))
                jq("#" + this.id).addClass("gr-carousel");
            // show carousel when loaded;
            if (jq("#" + this.id).hasClass("hide"))
                jq("#" + this.id).removeClass("hide");
            jq("li", "#" + this.id).css(this.css.item);
            jq("#" + this.id).css({
                width: this.getWidth(),
                position: "relative"
            });
            
            jq(document).bind('showComplete', function() {
                self.bind.call(self);
            })
            this.setItems();
            this.setDistance();
            this.formatItems();
            this.dimItems();
            this.highlightItem();
            this.addNavigation();
            // fire custom event on carousel initialisation;
            jq(document).trigger('init', this.items[this.getSelected()]);
            return this;
        },
        formatItems: function() {
            var self = this;
            jq("li", jq("#" + this.id)).each(function(i, v) {
                this.id = 'item-' + i;
                jq(this).css({ left: i * self.dist });
            });
            jq("ul", "#" + this.id)
                .addClass("clear")
                .css(this.css.list)
                .css({ left: parseInt(-this.dist) })
                .height(this.getHeight())
                .width(this.getItemsWidth())
                .wrap('<div class="gr-container"></div>');
            jq(".gr-container", "#" + this.id)
                .css(this.css.mask)
                .css({ height: this.getHeight() });
        },
        getWidth: function() {
            return parseInt(jq("li", "#" + this.id).innerWidth()) * jq.fn.carousel.defaults.visibleItems;
        },
        getHeight: function() {
            return jq("li", "#" + this.id).outerHeight();
        },
        getItemsWidth: function() {
            return parseInt(jq("li", "#" + this.id).innerWidth()) * this.count;
        },
        dimItems: function() {
            jq("li", "#" + this.id).each(function(i, val) {
                jq("<div class='gr-mask' id='gr-mask" + i + "'></div>")
                    .css({
                        position: "absolute",
                        top: "5px",
                        left: "5px",
                        bottom: "5px",
                        right: "5px",
                        height: jq(this).height(),  // IE6 required;
                        width: jq(this).width(),    // IE6 required;
                        background: '#333',
                        opacity: .55
                    }).appendTo(jq(this));
            });
        },
        setItems: function() {
            var self = this;
            this.items = [];
            jq("li", "#" + this.id).each(function() {
                self.items.push(this);
            });
            this.setItemsCount();
        },
        dimAll: function() {
            jq("li", "#" + this.id).each(function() {
                jq(".gr-mask", this).css({ display: "block" });
            });
        },
        highlightItem: function() {
            jq(".gr-mask", jq(this.items[this.getSelected()])).css({ display: "none" });
        },
        setItemsCount: function() {
            this.count = this.items.length;
        },
        setDistance: function() {
            this.dist = parseInt(jq("li", "#" + this.id).innerWidth()) * jq.fn.carousel.defaults.scrollIncrement;
        },
        addNavigation: function() {
            var self = this;
            jq('<span href="#" id="next" class="next">n &raquo;</span>')
                .css(this.css.nav)
                .appendTo(jq("#" + this.id));
            jq('<span href="#" id="prev" class="prev">&laquo; </span>')
                .css(this.css.nav)
                .appendTo(jq("#" + this.id));
            // binding now done via an external
            // event needs re-working;
            // this.bind();
        },
        bind: function() {
            var self = this;
            jq.each(['next', 'prev'], function(i, v) {
                jq('#' + v).css({
                        opacity: 1
                    }).bind('click', function() {
                        self[v].call(self);
                        return false;
                    });
        })
    },
    unbind: function() {
        var self = this;
        jq.each(['next', 'prev'], function(i, v) {
            jq('#' + v).css({
                opacity: .8
            }).unbind('click');
        })
    },
    next: function() {
        //if (jq.fn.carousel.defaults.infinate)
        //    this.pushItem();
        //else
        //    if (this.current + 1 >= this.count) return false;
        // fire custom event on before the animation happens;
        jq(document).trigger('preanimate', [this.items[this.getSelected()]]);
        this.pushItem();
        //var val = this.getX() - parseInt(this.dist);
        this.current += jq.fn.carousel.defaults.scrollIncrement;
        this.unbind();
        this.move('forward');
    },
    prev: function() {
        if (this.current == 0) {
            // this.popItem();
        };
        // fire custom event on before the animation happens;
        //if (jq.fn.carousel.defaults.infinate)
        //    this.popItem();
        jq(document).trigger('preanimate', [this.items[this.getSelected()]]);
        this.popItem();
        //var val = this.getX() + parseInt(this.dist);
        this.current -= jq.fn.carousel.defaults.scrollIncrement;
        this.unbind();
        this.move('back');
    },
    getX: function(elem) {
        return parseInt(jq(elem).css("left"));
    },
    move: function(dir) {
        var self = this;
        jq.each(this.items, function(i, v) {
            var dist = dir == 'forward' ?
                    self.getX.call(self, this) - parseInt(self.dist) :
                    self.getX.call(self, this) + parseInt(self.dist);
            jq(v).animate({ left: dist },
                    jq.fn.carousel.defaults.speed,
                    jq.fn.carousel.defaults.easing,
                    function() {
                        if (i == self.items.length - 1)
                            self.complete.call(self);
                    })
        });
    },
    complete: function() {
        this.dimAll();
        this.highlightItem();
        //this.bind();
        // fire custom event that can be listened for on completion of animation;
        jq(document).trigger('complete', [jq(this.items[this.getSelected()])[0]]);
    },
    pushItem: function() {
        var l = parseInt(jq(this.items[this.items.length - 1]).css("left")) + this.dist;
        jq(this.items[0])
                .appendTo(jq('ul', jq('#' + this.id)))
                .css({ left: l });
        this.setItems();
    },
    popItem: function() {
        var l = parseInt(jq(this.items[0]).css("left")) - this.dist;
        jq(this.items[this.items.length - 1])
                .prependTo(jq('ul', jq('#' + this.id)))
                .css({ left: l });
        this.setItems();
    },
    getSelected: function() {
        // prevent mayhem in the event of 
        // less than 3 items in carousel;
        if( this.items.length == 1 )
            return 0;
        else if( this.items.length == 2 )
            return 1
        else
            return Math.floor(jq.fn.carousel.defaults.visibleItems / 2) + 1;
    },
    css: {
        nav: {
            display: "block",
            position: "absolute",
            color: "#fff",
            cursor: 'pointer'
        },
        mask: {
            overflow: "hidden",
            position: "relative",
            padding: '10px 0'
        },
        list: {
            position: "absolute",
            top: "10px",
            left: "0"
        },
        item: {
            padding: "5px",
            position: "absolute",
            height: "84px"
        }
    }
};
})(jQuery);
