    /*!
     * Ext JS Library 3.3.0
     * Copyright(c) 2006-2010 Ext JS, Inc.
     * licensing@extjs.com
     * http://www.extjs.com/license
     */
    Ext.ns('Ext.ux');

    Ext.ux.Lightbox = (function(){
        var els = {},
            images = [],
            activeImage,
            initialized = false,
            selectors = [];

        return {
            overlayOpacity: 0.85,
            animate: true,
            resizeSpeed: 8,
            borderSize: 10,
            labelImage: "Image",
            labelOf: "of",

            init: function() {
                this.resizeDuration = this.animate ? ((11 - this.resizeSpeed) * 0.15) : 0;
                this.overlayDuration = this.animate ? 0.2 : 0;

                if(!initialized) {
                    Ext.apply(this, Ext.util.Observable.prototype);
                    Ext.util.Observable.constructor.call(this);
                    this.addEvents('open', 'close');
                    this.initMarkup();
                    this.initEvents();
                    initialized = true;
                }
            },

            initMarkup: function() {
                els.shim = Ext.DomHelper.append(document.body, {
                    tag: 'iframe',
                    id: 'ux-lightbox-shim'
                }, true);
                els.overlay = Ext.DomHelper.append(document.body, {
                    id: 'ux-lightbox-overlay'
                }, true);

                var lightboxTpl = new Ext.Template(this.getTemplate());
                els.lightbox = lightboxTpl.append(document.body, {}, true);

                var ids =
                    ['outerImageContainer', 'imageContainer', 'image', 'hoverNav', 'navPrev', 'navNext', 'loading', 'loadingLink',
                    'outerDataContainer', 'dataContainer', 'data', 'details', 'caption', 'imageNumber', 'bottomNav', 'navClose'];

                Ext.each(ids, function(id){
                    els[id] = Ext.get('ux-lightbox-' + id);
                });

                Ext.each([els.overlay, els.lightbox, els.shim], function(el){
                    el.setVisibilityMode(Ext.Element.DISPLAY)
                    el.hide();
                });

                var size = (this.animate ? 250 : 1) + 'px';
                els.outerImageContainer.setStyle({
                    width: size,
                    height: size
                });
            },

            getTemplate : function() {
                return [
                    '',
                        '',
                            '',
                                '',
                                '',
                                    '',
                                    '',
                                '',
                                '',
                                    '',
                                '',
                            '',
                        '',
                        '',
                            '',
                                '',
                                    '',
                                        '',
                                        '',
                                    '',
                                    '',
                                '',
                            '',
                        '',
                    ''
                ];
            },

            initEvents: function() {
                var close = function(ev) {
                    ev.preventDefault();
                    this.close();
                };

                els.overlay.on('click', close, this);
                els.loadingLink.on('click', close, this);
                els.navClose.on('click', close, this);

                els.lightbox.on('click', function(ev) {
                    if(ev.getTarget().id == 'ux-lightbox') {
                        this.close();
                    }
                }, this);

                els.navPrev.on('click', function(ev) {
                    ev.preventDefault();
                    this.setImage(activeImage - 1);
                }, this);

                els.navNext.on('click', function(ev) {
                    ev.preventDefault();
                    this.setImage(activeImage + 1);
                }, this);
            },

            register: function(sel, group) {
                if(selectors.indexOf(sel) === -1) {
                    selectors.push(sel);

                    Ext.fly(document).on('click', function(ev){
                        var target = ev.getTarget(sel);

                        if (target) {
                            ev.preventDefault();
                            this.open(target, sel, group);
                        }
                    }, this);
                }
            },

            open: function(image, sel, group) {
                group = group || false;
                this.setViewSize();
                els.overlay.fadeIn({
                    duration: this.overlayDuration,
                    endOpacity: this.overlayOpacity,
                    callback: function() {
                        images = [];

                        var index = 0;
                        if(!group) {
                            images.push([image.href, image.title]);
                        }
                        else {
                            var setItems = Ext.query(sel);
                            Ext.each(setItems, function(item) {
                                if(item.href) {
                                    images.push([item.href, item.title]);
                                }
                            });

                            while (images[index][0] != image.href) {
                                index++;
                            }
                        }

                        // calculate top and left offset for the lightbox
                        var pageScroll = Ext.fly(document).getScroll();

                        var lightboxTop = pageScroll.top + (Ext.lib.Dom.getViewportHeight() / 10);
                        var lightboxLeft = pageScroll.left;
                        els.lightbox.setStyle({
                            top: lightboxTop + 'px',
                            left: lightboxLeft + 'px'
                        }).show();

                        this.setImage(index);

                        this.fireEvent('open', images[index]);
                    },
                    scope: this
                });
            },

            setViewSize: function(){
                var viewSize = this.getViewSize();
                els.overlay.setStyle({
                    width: viewSize[0] + 'px',
                    height: viewSize[1] + 'px'
                });
                els.shim.setStyle({
                    width: viewSize[0] + 'px',
                    height: viewSize[1] + 'px'
                }).show();
            },

            setImage: function(index){
                activeImage = index;

                this.disableKeyNav();
                if (this.animate) {
                    els.loading.show();
                }

                els.image.hide();
                els.hoverNav.hide();
                els.navPrev.hide();
                els.navNext.hide();
                els.dataContainer.setOpacity(0.0001);
                els.imageNumber.hide();

                var preload = new Image();
                preload.onload = (function(){
                    els.image.dom.src = images[activeImage][0];
                    this.resizeImage(preload.width, preload.height);
                }).createDelegate(this);
                preload.src = images[activeImage][0];
            },

            resizeImage: function(w, h){
                var wCur = els.outerImageContainer.getWidth();
                var hCur = els.outerImageContainer.getHeight();

                var wNew = (w + this.borderSize * 2);
                var hNew = (h + this.borderSize * 2);

                var wDiff = wCur - wNew;
                var hDiff = hCur - hNew;

                var afterResize = function(){
                    els.hoverNav.setWidth(els.imageContainer.getWidth() + 'px');

                    els.navPrev.setHeight(h + 'px');
                    els.navNext.setHeight(h + 'px');

                    els.outerDataContainer.setWidth(wNew + 'px');

                    this.showImage();
                };

                if (hDiff != 0 || wDiff != 0) {
                    els.outerImageContainer.shift({
                        height: hNew,
                        width: wNew,
                        duration: this.resizeDuration,
                        scope: this,
                        callback: afterResize,
                        delay: 50
                    });
                }
                else {
                    afterResize.call(this);
                }
            },

            showImage: function(){
                els.loading.hide();
                els.image.fadeIn({
                    duration: this.resizeDuration,
                    scope: this,
                    callback: function(){
                        this.updateDetails();
                    }
                });
                this.preloadImages();
            },

            updateDetails: function(){
                var detailsWidth = els.data.getWidth(true) - els.navClose.getWidth() - 10;
                els.details.setWidth((detailsWidth > 0 ? detailsWidth : 0) + 'px');

                els.caption.update(images[activeImage][1]);

                els.caption.show();
                if (images.length > 1) {
                    els.imageNumber.update(this.labelImage + ' ' + (activeImage + 1) + ' ' + this.labelOf + '  ' + images.length);
                    els.imageNumber.show();
                }

                els.dataContainer.fadeIn({
                    duration: this.resizeDuration/2,
                    scope: this,
                    callback: function() {
                        var viewSize = this.getViewSize();
                        els.overlay.setHeight(viewSize[1] + 'px');
                        this.updateNav();
                    }
                });
            },

            updateNav: function(){
                this.enableKeyNav();

                els.hoverNav.show();

                // if not first image in set, display prev image button
                if (activeImage > 0)
                    els.navPrev.show();

                // if not last image in set, display next image button
                if (activeImage < (images.length - 1))
                    els.navNext.show();
            },

            enableKeyNav: function() {
                Ext.fly(document).on('keydown', this.keyNavAction, this);
            },

            disableKeyNav: function() {
                Ext.fly(document).un('keydown', this.keyNavAction, this);
            },

            keyNavAction: function(ev) {
                var keyCode = ev.getKey();

                if (
                    keyCode == 88 || // x
                    keyCode == 67 || // c
                    keyCode == 27
                ) {
                    this.close();
                }
                else if (keyCode == 80 || keyCode == 37){ // display previous image
                    if (activeImage != 0){
                        this.setImage(activeImage - 1);
                    }
                }
                else if (keyCode == 78 || keyCode == 39){ // display next image
                    if (activeImage != (images.length - 1)){
                        this.setImage(activeImage + 1);
                    }
                }
            },

            preloadImages: function(){
                var next, prev;
                if (images.length > activeImage + 1) {
                    next = new Image();
                    next.src = images[activeImage + 1][0];
                }
                if (activeImage > 0) {
                    prev = new Image();
                    prev.src = images[activeImage - 1][0];
                }
            },

            close: function(){
                this.disableKeyNav();
                els.lightbox.hide();
                els.overlay.fadeOut({
                    duration: this.overlayDuration
                });
                els.shim.hide();
                this.fireEvent('close', activeImage);
            },

            getViewSize: function() {
                return [Ext.lib.Dom.getViewWidth(), Ext.lib.Dom.getViewHeight()];
            }
        }
    })();

    Ext.onReady(Ext.ux.Lightbox.init, Ext.ux.Lightbox);