UNPKG

jive-sdk

Version:

Node.js SDK for Jive Software to assist with the development of add-ons

2,737 lines 91.6 kB
window.GalleryList = Backbone.Model.extend({
    defaults: {
        galleryIDs: []
    },
    useAppData : true,

    appDataKey: function() {
        return "gallery_list";
    },

    appDataFields:function () {
        return ["gallery_list"];
    },

    initialize: function() {
    },

    parse:function(content) {
        //there should be only one user
        for (var userId in content) {

            //there should be only one data
            for (var dataId in content[userId]) {
                if (dataId && dataId.indexOf('gallery_list') == 0) {
                    var content = JSON.parse(content[userId][dataId]);
                    this.set('galleryIDs', content.galleryIDs);
                }
            }
        }

        //// ????
        for(var i in content) {
            this.set(i, content[i]);
        }

        this.trigger("parsed", this.get("galleryIDs") );
    },

    add: function( galleryID ) {
        var ids = this.get("galleryIDs");
        console.log("current", ids);
        if ( $.inArray(galleryID, ids) < 0 ) {
            ids.push( galleryID );
        }
        console.log("after", ids);
    },

    subtract: function( galleryID ) {
        var ids = this.get("galleryIDs");
        ids.splice(ids.indexOf(galleryID), 1);
    }

});
;window.GalleryModel = Backbone.Model.extend({
    defaults: {
        title: '',
        imgIDs: '',
        preview: '',
        created: 0,
        parentList: null
    },
    useAppData : true,

    appDataKey: function() {
        if(this.isNew()) {
            var create_ts = new Date().getTime();
            return $.trim( "gallery_" + create_ts );
        } else {
            return this.get('id');
        }
    },

    appDataFields:function () {
        return [this.get('id')];
    },

    initialize: function() {
    },

    parse:function(content) {
        //there should be only one user
        for (var userId in content) {

            //there should be only one data
            for (var dataId in content[userId]) {
                if (dataId && dataId.indexOf('gallery_') == 0) {
                    var content = JSON.parse(content[userId][dataId]);

                    this.set('id', dataId);
                    this.set('title', content.title);
                    this.set('imageIDs', content.imageIDs);
                    this.set('preview', content.preview);
                    this.set('created', content.created);
                }
            }
        }

        //// ????
        for(var i in content) {
            this.set(i, content[i]);
        }

        this.trigger("parsed", this );
    },

    save: function(key, value, options) {
        if( this.isNew() ) {
            this.set('created', new Date().getTime());
        }
        //super call
        Backbone.Model.prototype.save.call(this, key, value, options);
    },

    destroy: function(passedData, callback) {
        
        var imgs = (this.get('imgIDs') == '' ? [] : this.get('imgIDs') );
        //Delete each image that was in the gallery.
        $.each(imgs, function(index, val) {
            var data = {imageId: val};
            var galleryEntryModel = new GalleryEntry();
            galleryEntryModel.destroy( data, function() {
                console.log("Destroyed image '" + val + "' in GalleryModel.destroy");
            });
        });

        Backbone.Model.prototype.destroy.call(this);
    }


});
;window.GalleryEntry = Backbone.Model.extend({
    initialize: function() {
    },

    fetch:function() {
        // override fetch to delegate to submodels for thumb and full source
        this.fetchThumb();
        this.fetchSource();
    },

    fetchThumb: function( callback ) {
        var imageId = this.get("id");
        var thumb = new GalleryImage({id:"thumb_"+ imageId});
        var self = this;

        thumb.bind("parsed", function( thumbImage ) {
            self.trigger("fetchedThumbnail", thumbImage );
            if ( callback ) {
                callback( thumbImage );
            }
        });

        thumb.fetch();
    },

    fetchSource: function(callback) {
        var imageId = this.get("id");
        var full = new GalleryImage({id:"image_"+ imageId});
        var self = this;

        full.bind("parsed", function( fullImage ) {
            self.trigger("fetchedFullSource", fullImage );
            if ( callback ) {
                callback( fullImage );
            }
        });

        full.fetch();
    },

    save: function( data, callback ) {

        // cascading save (thumbnail first, full image next)

        var thumb = new GalleryImage();
        thumb.set( {
            "id" :  "thumb_" + data.imageId,
            "src": data.previewSrc,
            "width": data.width,
            "height": data.height
        } );

        var onThumbSave = function () {
            var fullImage = new GalleryImage();
            fullImage.set( {
                "id" :  "image_" + data.imageId,
                "src": data.fullSrc,
                "width": data.width,
                "height": data.height
            } );

            fullImage.bind("saved", function() {
                callback( data.imageId );
            });

            fullImage.save();
        };

        thumb.bind("saved", onThumbSave );
        thumb.save();

    },

    destroy: function( data, callback ) {

        // cascading save (thumbnail first, full image next)

        var thumb = new GalleryImage();
        thumb.set( {
            "id" :  "thumb_" + data.imageId,
            "src": data.previewSrc,
            "width": data.width,
            "height": data.height
        } );

        var fullImage = new GalleryImage();
        fullImage.set( {
            "id" :  "image_" + data.imageId,
            "src": data.fullSrc,
            "width": data.width,
            "height": data.height
        } );

        var onThumbDestroy = function () {
            fullImage.destroy();
        };

        thumb.bind("destroy", onThumbDestroy );
        fullImage.bind("saved", function() {
            callback( data.imageId );
        });

        thumb.destroy();

    }
});;window.GalleryImage = Backbone.Model.extend({
    defaults: {
        width: '',
        height: 0,
        src: '',
        created: 0
    },
    useAppData : true,

    appDataKey: function() {
        return this.get('id');
    },

    getImageKey: function() {
        return this.get('id');
    },

    appDataFields:function () {
        return [this.get('id')];
    },

    initialize: function() {
    },

    parse:function(content) {
        //there should be only one user
        for (var userId in content) {

            //there should be only one data
            for (var dataId in content[userId]) {
                if (dataId && dataId.indexOf( this.getImageKey() ) == 0) {
                    var content = JSON.parse(content[userId][dataId]);

                    this.set('id', dataId);
                    this.set('width', content.width);
                    this.set('height', content.height);
                    this.set('src', content.src);
                    this.set('created', content.created);
                }
            }
        }

        //// ????
        for(var i in content) {
            this.set(i, content[i]);
        }

        this.trigger("parsed", this );
    },

    save: function(key, value, options) {
        if( this.isNew() ) {
            this.set('created', new Date().getTime());
        }
        //super call
        Backbone.Model.prototype.save.call(this, key, value, options);
    },

    destroy: function(key, value, options) {
        Backbone.Model.prototype.destroy.call(this, key, value, options);

    }
});
;function ImageProcessor() {

    // private

    var imgWidth = 90,
        imgHeight = 90,
        zindex = 0;

    var getDimensions = function(maxWidth, maxHeight, width, height ) {
        var neww, newh = 0;
        var rw = width / maxWidth; // width and height are maximum thumbnail's bounds
        var rh = height / maxHeight;

        if (rw > rh)
        {
            newh = height / rw;
            neww = maxWidth;
        }
        else
        {
            neww = width / rh;
            newh = maxHeight;
        }

        return {
            width: neww,
            height: newh
        };
    };

    var compareWidthHeight = function (width, height) {
        var diff = [];
        if(width > height) {
            diff[0] = width - height;
            diff[1] = 0;
        } else {
            diff[0] = 0;
            diff[1] = height - width;
        }
        return diff;
    };

    //Black & White image effect
    //by Marco Lisci - http://badsharkco.com/
    var grayscale = function(context) {
        var imgd = context.getImageData(0, 0, imgWidth, imgHeight);
        var pix = imgd.data;
        for (var i = 0, n = pix.length; i < n; i += 4) {
            var grayscale = pix[i  ] * .3 + pix[i+1] * .59 + pix[i+2] * .11;
            pix[i  ] = grayscale;
            pix[i+1] = grayscale;
            pix[i+2] = grayscale;
        }
        context.putImageData(imgd, 0, 0);
    };

    //canvas-blur effect
    //by Matt Riggott - http://www.flother.com/
    var blurry = function(context, image, diff) {
        var i, x, y,
            blureffect = 4;

        context.globalAlpha = 0.1;
        for (i = 1; i <= blureffect; i++) {
            for (y = -blureffect/2; y <= blureffect/2; y++) {
                for (x = -blureffect/2; x <= blureffect/2; x++) {
                    context.drawImage(image, diff[0]/2, diff[1]/2, image.width-diff[0], image.height-diff[1], x, y, imgWidth, imgHeight);
                }
            }
        }
        context.globalAlpha = 1.0;
    };

    var addNoise = function(ctx, alpha) {
        var imageData = ctx.getImageData(0,0, ctx.canvas.width, ctx.canvas.height);
        var pixels = imageData.data;
        var alpha1 = 1 - alpha;

        // Pick the best array length
        var rl = Math.round(ctx.canvas.width * 3.73);
        var randoms = new Array(rl);

        // Pre-calculate random pixels
        for (var i = 0; i < rl; i++) {
            randoms[i] = Math.random() * alpha + alpha1;
        }

        // Apply random pixels
        for (var i = 0, il = pixels.length; i < il; i += 4) {
            pixels[i] =  (pixels[i] * randoms[i % rl]) | 0;
        }

        ctx.putImageData(imageData, 0, 0);
    };

    var mkImage = function(image, width, height, effect, croping ) {
        var canvas = document.createElement('canvas');
        canvas.width = width;
        canvas.height = height;
        var ctx = canvas.getContext('2d');

        //default resize variable
        var diff = [0, 0];
        if(croping == 'crop') {
            //get resized width and height
            diff = compareWidthHeight(image.width, image.height);
        }

        //draw canvas image
        ctx.drawImage(image, diff[0]/2, diff[1]/2, image.width-diff[0], image.height-diff[1], 0, 0, width, height);

        //addNoise( ctx, 0.2 );
        //apply effects if any
        if(effect == 'grayscale') {
            grayscale(ctx);
        } else if(effect == 'blurry') {
            blurry(ctx, image, diff);
        } else {}

        return canvas.toDataURL("image/jpeg", 0.95);
    };

    var getPreviewImage = function(image) {
        var effect = $('input[name=effect]:checked').val();
        var croping = $('input[name=croping]:checked').val();
        var dimensions = getDimensions(90, 90, image.width, image.height );

        return {
            width: dimensions.width,
            height: dimensions.height,
            url: mkImage(image, dimensions.width, dimensions.height, effect, croping )
        };
    };

    var getSourceImage = function(image) {
        var effect = $('input[name=effect]:checked').val();
        var croping = $('input[name=croping]:checked').val();
        var dimensions = getDimensions(350, 350, image.width, image.height );

        return {
            width: dimensions.width,
            height: dimensions.height,
            url: mkImage(image, dimensions.width, dimensions.height, effect, croping )
        };
    };

    var randomNum = function(maxVal) {
        var maxRandomVal = maxVal ? maxVal : 1000000;

        return Math.floor(Math.random()*maxRandomVal);
    };

    var convertToKBytes = function(number) {
        return (number / 1024).toFixed(1);
    };

    //convert datauri to blob
    var dataURItoBlob = function(dataURI) {
        var BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder || window.BlobBuilder;

        //skip if browser doesn't support BlobBuilder object
        if(typeof BlobBuilder === "undefined") {
            $('#err').html('Ops! There have some limited with your browser! <br/>New image produced from canvas can\'t be upload to the server...');
            return dataURI;
        }

        // convert base64 to raw binary data held in a string
        // doesn't handle URLEncoded DataURIs
        var byteString;
        if (dataURI.split(',')[0].indexOf('base64') >= 0)
            byteString = atob(dataURI.split(',')[1]);
        else
            byteString = unescape(dataURI.split(',')[1]);

        // separate out the mime component
        var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

        // write the bytes of the string to an ArrayBuffer
        var ab = new ArrayBuffer(byteString.length);
        var ia = new Uint8Array(ab);
        for (var i = 0; i < byteString.length; i++) {
            ia[i] = byteString.charCodeAt(i);
        }

        // write the ArrayBuffer to a blob, and you're done
        var bb = new BlobBuilder();
        bb.append(ab);
        return bb.getBlob(mimeString);
    };

    var buildImage = function( fileName, base64, successCallback ) {
        return $('<img/>')
            .load(function() {
                //when image fully loaded
                var thumb = getPreviewImage(this);
                var full = getSourceImage(this);
                console.log(thumb, full);

                var imgData = {};
                imgData.imageId = randomNum();
                imgData.thumbSrc = thumb.url;
                imgData.fullSrc = full.url;
                imgData.imageWidth = full.width;
                imgData.imageHeight = full.height;
                imgData.fileName = fileName.substr(0, fileName.lastIndexOf('.')); //subtract file extension

                if ( successCallback )  {
                    successCallback( imgData );
                }
            })
            .attr('src', base64 );
    };

    var readFile = function(file, startedCallback, successCallback, errorCallback) {
        if( (/image/i).test(file.type) ) {
            //define FileReader object
            var reader = new FileReader();

            startedCallback( file );

            //init reader onload event handlers
            reader.onload = function(e) {
                $('<img/>')
                    .load(function() {
                        //when image fully loaded
                        var thumb = getPreviewImage(this);
                        var full = getSourceImage(this);
                        console.log(thumb, full);

                        var imgData = {};
                        imgData.imageId = randomNum();
                        imgData.thumbSrc = thumb.url;
                        imgData.fullSrc = full.url;
                        imgData.imageWidth = full.width;
                        imgData.imageHeight = full.height;
                        imgData.fileName = file.name.substr(0, file.name.lastIndexOf('.')); //subtract file extension
                        imgData.fileOriSize = convertToKBytes(file.size);
                        imgData.fileUploadSize = convertToKBytes(dataURItoBlob(thumb.url).size); //convert new image URL to blob to get file.size

                        successCallback( file, imgData );
                    })
                    .attr('src', e.target.result);
            };

            //begin reader read operation
            reader.readAsDataURL(file);
        } else {
            //some message for wrong file format
            errorCallback(file, '*Selected file format not supported!');
        }
    };

    return {
        readFile: readFile,
        buildImage: buildImage
    };

};window.GalleryPreviewView = (function() {

    function getImageUrl(relativeUrl, proxied) {
        var gadgetURL = _args()["url"];
        var baseURL= gadgetURL.replace(/[a-zA-Z0-9_]+\.xml(\?.*)?/,"");
        return proxied ? gadgets.io.getProxyUrl(baseURL + relativeUrl) : baseURL + relativeUrl;
    }

    return Backbone.View.extend({
        events:{
        },

        generatePreview:function ( imgContainerDom, title, numImages ) {

            // uses 3 canvases
            function makeStaticSlide(image, $thumbNails, title, numImgs) {
                var playGraphic = new Image();
                playGraphic.src = getImageUrl("images/play-graphic.png", true);
                var embedBgImg = new Image();
                embedBgImg.src = getImageUrl("images/background_wood.jpg", true);

                /////////////////////////////////

                var imgObj = new Image();//necessary to get actual height / width
                imgObj.src = $(image).attr("src");

                var actualWidth = imgObj.width;
                var actualHeight = imgObj.height;
                imgObj = null; //To cleanup data from image.

                var baseHeight = numImgs == 1 ? 240 : 180;
                var baseWidth = 240;
                var imageWidth, imageHeight;

                var ratio = actualWidth / actualHeight;
                if (actualHeight > actualWidth ) {
                    imageHeight = baseHeight;
                    imageWidth = imageHeight * ratio;

                } else {
                    imageWidth = baseWidth;
                    imageHeight = imageWidth * 1/ratio;
                }

                //For the case where we have > 1 image and the width > height, make sure we didn't make the height too large
                //This is needed due to the fact that the above code is for embedding rectangles inside squares, not inside rectangles
                if (imageHeight > baseHeight) {
                    var ratio2 = baseHeight / imageHeight;
                    imageHeight *= ratio2;
                    imageWidth *= ratio2;
                }

                var excessWidth = baseWidth - imageWidth;
                var excessHeight = baseHeight - imageHeight;

                // var imageWidth = 240, imageHeight = numImgs == 1 ? 240 : 180; //if 1 image, expand to fill
                var titleAreaHeight = 40;
                var borderWidth = 10, thumbBorderWidth = 13;
                var thumbWidth = 50, thumbHeight = 50;

                var titleAreaY = Math.max(titleAreaHeight + borderWidth, imageHeight + excessHeight / 2 - titleAreaHeight + borderWidth);

                var topLevelCanvas = $('<canvas></canvas>')[0];
                var thumbnailCanvas = $('<canvas></canvas>')[0];
                var titleCanvas = $('<canvas></canvas>')[0];

                topLevelCanvas.width = baseWidth + 2*borderWidth;
                topLevelCanvas.height = topLevelCanvas.width;

                thumbnailCanvas.width = baseWidth;
                thumbnailCanvas.height = thumbHeight;

                titleCanvas.width = baseWidth;
                titleCanvas.height = titleAreaHeight;

                // draw topLevelCanvas background
                var topLevelContext = topLevelCanvas.getContext('2d');
                topLevelContext.drawImage(embedBgImg, 0, 0, topLevelCanvas.width, topLevelCanvas.height);

                // draw thumbnails
                var thumbnailContext = thumbnailCanvas.getContext('2d');
                var tnLeft = 0;
                $thumbNails.each(function(index, thumbNailImg) {
                    if ( index <=3 ) { //Draw at most 4 images.
                        thumbnailContext.drawImage(thumbNailImg, tnLeft, 0, thumbWidth, thumbHeight);
                        tnLeft += (thumbWidth + thumbBorderWidth);
                    }
                });

                var imageData = thumbnailContext.getImageData(0, 0, thumbnailCanvas.width, thumbnailCanvas.height);
                topLevelContext.putImageData(imageData, borderWidth, borderWidth + baseHeight + borderWidth);

                //Draw main image with transparency as specified by Amy
                //Must do it this way, putImageData function doesn't work with transparency

                topLevelContext.globalAlpha=0.8;
                topLevelContext.drawImage(image, borderWidth + excessWidth / 2, borderWidth + excessHeight / 2, imageWidth, imageHeight);

                //Draw transparent title area
                topLevelContext.globalAlpha=0.4;
                topLevelContext.fillStyle="black";
                topLevelContext.fillRect(borderWidth, titleAreaY, baseWidth, titleAreaHeight); //Draw the title bar

                //Draw the title text
                var maxTitleLength = 20;
                var wasAbbreviated = false;
                var oldTitleLen = title.length;
                if (title.length > maxTitleLength) {
                    wasAbbreviated = true;
                }
                title = title.slice(0, Math.min(maxTitleLength, title.length)); //limit to 20 chars.

                if (wasAbbreviated) {
                    title += "...";
                } else {
                    for (var i = 0; i < (maxTitleLength - oldTitleLen); i++)
                        title += " ";
                    title += "   ";
                }

                title += " (" + numImgs +")";

                var fontSize = 14;
                topLevelContext.globalAlpha = 1.0;
                topLevelContext.font = fontSize + "px Georgia";
                topLevelContext.fillStyle="#ffffff";
                topLevelContext.fillText(title, borderWidth*2, titleAreaY + titleAreaHeight/2 + fontSize/2);

                return topLevelCanvas.toDataURL("image/jpeg");
            }

            var firstImg = imgContainerDom.find("img").first();
            return makeStaticSlide(firstImg[0], imgContainerDom.find("img").slice(1), title, numImages);
        },

        generateListEntry:function ( imgContainerDom ) {

            function makeStaticSlide(image) {
                /////////////////////////////////

                var imgObj = new Image();//necessary to get actual height / width
                imgObj.src = $(image).attr("src");

                var actualWidth = imgObj.width;
                var actualHeight = imgObj.height;
                imgObj = null; //To cleanup data from image.

                var baseHeight = 120;
                var baseWidth = 120;
                var imageWidth, imageHeight;

                var ratio = actualWidth / actualHeight;
                if (actualHeight > actualWidth ) {
                    imageHeight = baseHeight;
                    imageWidth = imageHeight * ratio;

                } else {
                    imageWidth = baseWidth;
                    imageHeight = imageWidth/ratio;
                }

                //For the case where we have > 1 image and the width > height, make sure we didn't make the height too large
                //This is needed due to the fact that the above code is for embedding rectangles inside squares, not inside rectangles
                if (imageHeight > baseHeight) {
                    var ratio2 = baseHeight / imageHeight;
                    imageHeight *= ratio2;
                    imageWidth *= ratio2;
                }

                var excessWidth = baseWidth - imageWidth;
                var excessHeight = baseHeight - imageHeight;

                var borderWidth = 10;

                var topLevelCanvas = $('<canvas></canvas>')[0];
                topLevelCanvas.width = baseWidth + 2*borderWidth;
                topLevelCanvas.height = topLevelCanvas.width;

                var rotatedCanvas = $('<canvas></canvas>')[0];
                rotatedCanvas.width = baseWidth + 2*borderWidth;
                rotatedCanvas.height = rotatedCanvas.width;

                var adj = baseHeight * 0.05;

                var rotatedContext = rotatedCanvas.getContext('2d');
                rotatedContext.globalAlpha=0.4;
                rotatedContext.rotate(-0.1);
                rotatedContext.lineWidth = 1;
                rotatedContext.strokeStyle = 'red';
                rotatedContext.rect((borderWidth + excessWidth / 2)-adj, (borderWidth + excessHeight / 2)+adj, imageWidth, imageHeight);
                rotatedContext.drawImage(image, (borderWidth + excessWidth / 2)-adj, (borderWidth + excessHeight / 2)+adj, imageWidth-1, imageHeight-1);
                rotatedContext.stroke();
                rotatedContext.globalAlpha=1.0;

                debugger;
                var imageData = rotatedContext.getImageData(0, 0, rotatedCanvas.width, rotatedCanvas.height);

                // draw topLevelCanvas background
                var topLevelContext = topLevelCanvas.getContext('2d');
                topLevelContext.fillStyle="blue";
                topLevelContext.fillRect(0,0, baseWidth, baseHeight); //Draw the title bar

                topLevelContext.putImageData(imageData, 0,0);
                topLevelContext.drawImage(image, borderWidth + excessWidth / 2, borderWidth + excessHeight / 2, imageWidth, imageHeight);
                topLevelContext.lineWidth = 1;
                topLevelContext.strokeStyle = 'red';
                topLevelContext.rect((borderWidth + excessWidth / 2), (borderWidth + excessHeight / 2), imageWidth, imageHeight);
                topLevelContext.stroke();

                return topLevelCanvas.toDataURL("image/jpeg");
            }

            var firstImg = imgContainerDom.find("img").first();
            return makeStaticSlide(firstImg[0]);
        }

    });
})();;window.GalleryListItemView = Backbone.View.extend({
    events:{
    },

    initialize:function () {
    },

    render:function () {
        var gallery = this.model;

        $(this.el).html(
            $(_.template(this.options.container.getTemplate('#GalleryListItem'), {
                "title" : gallery.get("title"),
                "galleryID" : gallery.get("id"),
                "listSrc" : gallery.get("list")
            } ) )
        );

        return this.el;
    },

    createNewGallery : function() {
        Backbone.history.navigate('gallery-create', true);
        return false;
    }

});
;window.GalleryListView = Backbone.View.extend({
    events:{
        "click #btn_create_an_album"             : "createNewGallery"
    },

    initialize:function () {
        this.collection = new GalleryList();
    },

    render:function () {
    },

    createNewGallery : function() {
        Backbone.history.navigate('gallery-create', true);
        return false;
    },

    renderNoGalleries: function() {
        $(this.el).html(_.template(this.options.container.getTemplate('#GalleryListNone') ) );
    },

    renderGalleries: function() {
        $(this.el).html(_.template(this.options.container.getTemplate('#GalleryList')));
    },

    renderGallery: function( gallery ) {

        var galleryListItemView = new GalleryListItemView( {model: gallery, container: this.options.container } );
        var galleryListItemDom = galleryListItemView.render();
        $(galleryListItemDom).attr("name", gallery.get('created'));

        var dom = $(this.el);
        var container = dom.find("#listcontent");
        var galleries = dom.find("#listcontent > .j-album");

        if (galleries.length <= 0) {
            container.append( galleryListItemDom );
        }
        else { //Insert in order
            var wasInserted = false;
            for (var i = 0; i < galleries.length; i++) {
                if ($(galleries[i]).attr("name") > gallery.get('created')) {
                    $(galleries[i]).before(galleryListItemDom);
                    wasInserted = true;
                    break;
                }
            }
            if (! wasInserted ) {
                container.append(galleryListItemDom);
            }

        }

        return this.el;
    }

});
;window.GalleryEditView = Backbone.View.extend({

    didNotChangeImages: true,

    events:{
        "click  #uploadbtn"             : "triggerUpload",
        "click  #link_save"             : "saveGallery",
        "click  #link_cancel"           : "cancelEdit",
        "change #upload"                : "uploadFiles",
        "drop #result"                  : "uploadFiles"
    },

    initialize:function (options) {
        this.render();
        this.imageProcessor = new ImageProcessor();
    },

    render:function () {
        var dom = $(this.el);
        var resultDom = dom.find("#result");
        dom.html(_.template(this.options.container.getTemplate('#GalleryEdit')));
        resultDom.sortable().disableSelection();
    },

    deactivateIE: function() {
        var dom = $(this.el);
        var ieUploadButton = dom.find("#ieUpload");
        ieUploadButton.hide();
    },

    setupIE: function() {
        debugger;
        var dom = $(this.el);
        var ieUploadButton = dom.find("#ieUpload");

        dom.find("#upload-files").hide();

        var base = gadgets.util.getUrlParameters()["url"].replace("/app.xml", "");
        var imageto46 = gadgets.io.getProxyUrl(base + "/flashupload/bin/flashupload.swf?cachebust=" + Math.random());
        var expressInstall = gadgets.io.getProxyUrl(base + "/flashupload/expressInstall.swf");
        var version = "10.0.0";

        $.fn.addFlashUpload = function() {
            return this.each(function() {
                var $this = $(this);
                var uploadId = "flashUpload-" + $.fn.addFlashUpload.id++;
                var width = $this.outerWidth();
                var height = $this.outerHeight();
                var position = $this.css("position");

                // this span will get replaced by the Flash content
                $this.attr("data-uploadid", uploadId).append('<span id="' + uploadId + '"></span>');

                // be sure the parent (button) has relative/absolute positioning so that our Flash can sit on top
                if (position !== "absolute" && position !== "relative") {
                    $this.css("position", "relative");
                }

                // pass in the uploadId to the Flash for it to pass back out on upload
                var flashvars = {
                    uploadId: uploadId
                };

                // make transparent and able to access JavaScript
                var params = {
                    wmode: "transparent",
                    allowscriptaccess: "always"
                };

                // position over the top of the button the Flash is inside of
                var attributes = {
                    style: "position: absolute; z-index: 100; left: 0; right: 0; top: 0; bottom: 0;"
                };

                // embed the Flash
                swfobject.embedSWF(imageto46, uploadId, width, height, version, expressInstall, flashvars, params, attributes);
            });
        };
        $.fn.addFlashUpload.id = 1;


        // handle the uploads and use jQuery's events to notify listeners of this button's upload
        window.processBase64Encodings = function( fileName, fileData, uploadId ) {
            $( "[data-uploadid=" + uploadId + "]" ).trigger( "upload", [ fileName, fileData ] );
        };

        //
        ieUploadButton.addFlashUpload().bind( "upload", function( event, fileName, fileData ) {
            var mime = fileName.split(".").pop();
            if (mime === "jpg") mime = "jpeg";

            var container = this.options.container;
            var successCallback = function(imgData ) {
                var galleryEntryView = new GalleryEntryView({ container: container });
                var imgDom = galleryEntryView.render(imgData);

                imgDom.prependTo("#result")
                    .hide()
                    .css({ 'z-index':self.zindex++ })
                    .show();

                $("#link_save").show();
            };
            this.imageProcessor.buildImage( fileName,  "data:image/" + mime + ";base64," + fileData, successCallback );
        });
    },

    renderGallery: function( gallery ) {
        var dom = $(this.el);

        var title = gallery.get("title");
        if ( title ) {
            dom.find("#gallery_title").val(title);
        } else {
            dom.find("#gallery_title").val(this.prettyDate(new Date()) );
        }

        // load images
        var ids = gallery.get("imgIDs");

        if ( !ids || ids.length < 1 ) {
            // nothing else to do if there are no images
            return;
        }

        console.log("Prepping image id manifest", ids);

        var galleryEntryMap = {};
        var container = this.options.container;

        // pre-seed the gallery entry doms
        $.each( ids, function() {
            var imageId = this;

            var model = new GalleryEntry({id: imageId});
            var view = new GalleryEntryView({model: model, container: container });
            galleryEntryMap[imageId] = { view: view, model: model };

            var entryDom = view.render();
            dom.find("#result").append( entryDom );
        });

        // now load
        $.each( ids, function() {
            var imageId = this;
            console.log("loading image id " + imageId );

            var galleryEntry = galleryEntryMap[imageId];

            var model = galleryEntry.model;
            var view =  galleryEntry.view;

            model.bind("fetchedThumbnail", function(thumbImage) {
                view.renderThumbnail(thumbImage);
            });

            model.bind("fetchedFullSource", function(fullImage) {
                view.attachFullImageSource(fullImage);
            });

            model.fetch();
        });

        var that = this;

        $(this.el).find("#result").sortable();
        $(this.el).find("#result img").draggable();

//        $(this.el).find("#trashcan_img").droppable({
//            //accept: "#result img",
//            tolerance: "touch",
//            drop: function(event, ui) {
//                //remove image from the DOM. Upon saving it will be removed
//                ui.draggable.remove();
//            },
//            activate: function(event, ui) {
//                $(that.el).find("#trashcan_img").hide();
//                $(that.el).find("#trashcan_img_active").show();
//            },
//            deactivate: function(event, ui) {
//                $(that.el).find("#trashcan_img_active").hide();
//                $(that.el).find("#trashcan_img").show();
//            }
//
//        });


    },

    triggerUpload: function() {
        $("#upload").click();
    },

    saveGallery: function() {
        var dom = $(this.el);
        var gallery = this.model;

        var images = dom.find("#result img");
        if ( images.length < 1 ) {
            console.log("Nothing to save");
            return;
        }

        //First pass, generate new IDs, set -1 if it didn't have one already (new image)
        var imageIDs = [];
        $.each( images, function() {
            var image = $(this);
            var imageID = image.attr("data-id") || -1; //-1 means we need to gen a new ID

            imageIDs.push(imageID);
        });

        //Second pass--generate random IDs, checking that they don't exist already.
        for (var i = 0; i < imageIDs.length; i++) {
            if (imageIDs[i] === -1) {
                do {
                    var newId = randomNum();
                } while (imageIDs.indexOf(newId) >= 0);
                imageIDs[i] = newId;
            }
        }


        // save gallery images first
        var container = this.options.container;
        this.saveGalleryImages( images, imageIDs, function() {
            var title = dom.find("#gallery_title").val();
            var previewView = new GalleryPreviewView({ container: container });

            $.each( images, function() {
                var i = $(this);
                var t = $("<img>").attr("src", i.attr("data-fullsrc"));
                $("#secret").append(t);
            });

            window.setTimeout( function() {

                var previewSrc = previewView.generatePreview($("#secret"), title, images.length);
                var listSrc = previewView.generateListEntry($("#secret"));

                gallery.set( {
                    "title": title,
                    "imgIDs": imageIDs,
                    "preview": previewSrc,
                    "list": listSrc
                } );

                // tell the gallery model to save itself
                gallery.save();

            }, 150);
        });
    },

    saveGalleryImages: function(images, imageIDs, successCallback) {
        var savedCount = 0;
        var destroyCount = 0;
        var gallery = this.model;
        var oldIDs = gallery.get('imgIDs') == "" ? [] : gallery.get('imgIDs');

        //figure out difference between oldIDs and imageIDs in DOM (previous gallery state vs. new state)
        var toDelete = [];
        var toUpload = [];
        var that = this;

        //Go through the new IDs, and mark for upload if they're not in the old IDs
        $.each(imageIDs, function(index, imgId) {
            if (oldIDs.indexOf(imgId) < 0) {
                toUpload.push(
                    {
                        id: imgId,
                        dom: $(that.el).find("#result").find("[data-id=" + imgId + "]")[0]
                    }); //place new ID (may be generated) and img element in array
            }
        });

        //Go through the old IDs, and mark for deletion if they're not in the new IDs
        $.each(oldIDs, function(index, imgId) {
            if (imageIDs.indexOf(imgId) < 0 ) {
                toDelete.push({ id: imgId }); //for deleting, just push ID
            }
        });

        //Callback so that images are deleted sequentially after saving.
        var deleteImagesAfterSaving = function() {
            if (toDelete.length == 0) {
                successCallback();
            }
            $.each( toDelete, function(index, value) {
                destroyCount++;

                var data = {
                    imageId : value.id
                };

                var callback = function( theID ) {

                    console.log("Destroyed image id " + theID + " source");

                };

                var galleryEntryModel = new GalleryEntry();
                galleryEntryModel.destroy( data, callback );
                if ( destroyCount == toDelete.length ) {
                    console.log("Done destroying " + destroyCount);
                    successCallback();
                }
            })
        };

        if (toUpload.length == 0) { //Don't make a save request if there are no images to upload
            deleteImagesAfterSaving();
        } else {
            $.each( toUpload, function(index, value) {
                var image = $(value.dom);

                var data = {
                    previewSrc: image.attr("src"),
                    fullSrc : image.attr("data-fullsrc"),
                    width : image.attr("data-width"),
                    height : image.attr("data-height"),
                    imageId : value.id
                };

                var callback = function( imageId ) {

                    console.log("Saved image id " + imageId + " source");
                    savedCount++;

                    if ( savedCount == toUpload.length) {
                        console.log("Done saving " + savedCount);
                        deleteImagesAfterSaving();
                    }
                };

                var galleryEntryModel = new GalleryEntry();
                galleryEntryModel.save( data, callback ); //Callback = delete imgs in toDelete
            });
        }

    },

    cancelEdit: function() {
        Backbone.history.navigate('gallery-edit-cancel', true);
    },

    uploadFiles: function(event) {
        var self = this;
        if (event.originalEvent && event.originalEvent.dataTransfer && event.originalEvent.dataTransfer.files) {
            event.preventDefault();
            var files = event.originalEvent.dataTransfer.files;
        } else {
            var files = event.target.files;
        }
        this.didNotChangeImages = false;

        var startedCallback = function(file) {
            $("#link_save").hide();
        };

        var successCallback = function(file, imgData ) {

            var galleryEntryView = new GalleryEntryView({ container: self.options.container });
            var imgDom = galleryEntryView.render(imgData);

            imgDom.prependTo("#result")
                .hide()
                .css({ 'z-index':self.zindex++ })
                .show();

            $("#link_save").show();
        };

        var errorCallback = function(file, error) {
            alert(error);
        };

        if(files && typeof FileReader !== "undefined") {
            //process each files only if browser is supported
            for(var i=0; i<files.length; i++) {
                this.imageProcessor.readFile(
                    files[i],
                    startedCallback,
                    successCallback,
                    errorCallback
                );
            }
        } else {
            // hmm??
            // xxx todo -- a good error!
            debugger;
        }
    },

    prettyDate: function(date) {

        function pad(number) {
            var r = String(number);
            if ( r.length === 1 ) {
                r = '0' + r;
            }
            return r;
        }

        //It makes me cry that this isn't provided by javascript
        function getPrettyMonth(m) {
            switch (m) {
            case 0: return "Jan.";
            case 1: return "Feb.";
            case 2: return "Mar.";
            case 3: return "Apr.";
            case 4: return "May";
            case 5: return "June";
            case 6: return "July";
            case 7: return "Aug.";
            case 8: return "Sep.";
            case 9: return "Oct.";
            case 10: return "Nov.";
            case 11: return "Dec.";
            }
            return ""; //fail
        }

        function getPrettyTime(h, m) {
            return ( (h % 12 == 0) ? "12" : ( h  % 12 ) ) + ":" + pad(m) + (h < 12 ? " AM" : " PM");
        }

        return getPrettyMonth(date.getMonth())
            + ' ' + date.getDate()
            + ', ' + date.getFullYear()
            + ' ' + getPrettyTime( date.getHours(), date.getMinutes());
    }
});
;window.GalleryEntryView = Backbone.View.extend({
    events:{
    },

    render:function (imgData) {

        if ( !imgData ) {
            // render a blank entry
            var imageId = this.model.get("id");
            imgData =  {
                "thumbSrc" : app.getProxiedUrl("/images/ajax_loader_large.gif"),
                "imageId" : imageId,
                "fileName" : "",
                "fullSrc" : "",
                "imageWidth" : "90",
                "imageHeight" : "90"
            };
            $(this.el).html( _.template(this.options.container.getTemplate('#GalleryEntry'), imgData ) );
            this.imgDom = $(this.el).find("img");
        } else {
            // render according to imgData
            this.imgDom = $(_.template(this.options.container.getTemplate('#GalleryEntry'), imgData ));
            $(this.el).html( this.imgDom );
        }

        return this.imgDom;
    },

    renderThumbnail: function( thumb ) {
        var previewSrc = thumb.get("src");
        var width = thumb.get("width");
        var height = thumb.get("height");
        this.imgDom.attr("src", previewSrc);
        this.imgDom.attr("data-width", width);
        this.imgDom.attr("data-height", height);

        return this.imgDom;
    },

    attachFullImageSource: function( fullImage ) {
        var fullSrc = fullImage.get("src");
        this.imgDom.attr("data-fullsrc", fullSrc);
    }

});
;window.GalleryViewer = Backbone.View.extend({
    events:{
        "click  #close"             : "close"
    },

    initialize:function () {
    },

    close: function() {
        osapi.jive.core.container.closeApp();
    },

    render:function () {

        var gallery = this.model;

        var dom = $(this.el);
        dom.html(_.template(this.options.container.getTemplate('#GalleryViewer'), {
            "title": gallery.get("title")
        } ) );

        var ids = gallery.get("imgIDs");

        var sourceToProcessQueue = new Array();
        var loadCount = 0;

        var loaded = function( loadCount) {
            if  ( loadCount < ids.length ) {
                return;
            }
            console.log(loadCount, "versus", ids.length );

            console.log("Loaded " + loadCount++);
            $(".j-loading").hide();
            $(".slideshow").show();

            gadgets.window.adjustWidth();

            $('.slideshow').cycle({
                next : "#next",         // JQuery selector for "next" control
                pause: 1,               // Pause transitions when mouse is over the image
                prev : "#previous",     // JQuery selector for "previous" control
                speed: 100,            // Time to complete a cycle (ms)
                timeout: 5000,           // Time between cycles (ms)
                pager: "#pager",
                pagerAnchorBuilder: function(idx, slide) {
                    // return selector string for existing anchor
                    return '#pager li:eq(' + idx + ') a';
                }

            });

            window.setTimeout( function() {
                $.each( sourceToProcessQueue, function() {
                    var fn = this;
                    fn();
                });
            }, 2000 );

        };


        console.log("Prepping image id manifest", ids);

        var galleryEntryMap = {};

        var containerHeight = dom.find(".slideshow").css("height").replace('px','');
        var containerWidth = dom.find(".slideshow").css("width").replace('px','');

        // pre-seed the gallery entry doms
        var container = this.options.container;
        $.each( ids, function() {
            var imageId = this;

            var model = new GalleryEntry({id: imageId });
            var view = new GalleryEntryView({model: model, container: container });
            galleryEntryMap[imageId] = { view: view, model: model };

            var entryDom = view.render();
            entryDom.hide();
            var thumb  = entryDom.clone().css("width",50).css("height", 50).show();

            dom.find(".slideshow").append( entryDom );



            var li = $("<li></li>");
            var a = $("<a href='#'></a>");

            li.append( a.append(  thumb ) );
            dom.find("#pager").append( li );
        });

        // now load
        $.each( ids, function() {
            var imageId = this;
            console.log("loading image id " + imageId );

            var galleryEntry = galleryEntryMap[imageId];

            var model = galleryEntry.model;
            var view =  galleryEntry.view;

            model.fetchThumb( function(thumbImage) {
                var dom = view.renderThumbnail(thumbImage);

                $("#pager img[data-id=" + imageId + "]")
                    .attr("src", thumbImage.get("src"));

                dom.css("width", thumbImage.get("width") * 1.5 );
                dom.css("height", thumbImage.get("height") * 1.5 );

                var containerHeight = $(".slideshow").css("height").replace('px','');
                var containerWidth = $(".slideshow").css("width").replace('px','');

                var imgWidth = dom.css("width").replace('px','');
                var imgHeight = dom.css("height").replace('px','');
                var deltaWidth = containerWidth - imgWidth;
                var deltaHeight = containerHeight - imgHeight;
                dom.css("margin-left", deltaWidth / 2);
                dom.css("margin-top", deltaHeight / 2);

                loadCount++;
                loaded(loadCount);

                sourceToProcessQueue.push( function() {
                    model.fetchSource( function( source ) {
                        console.log("loaded image id " + imageId );
                        var fullSrc = source.get("src");
                        var imgDom = $(".slideshow img[data-id=" + imageId + "]");
                        imgDom.show();
                        imgDom.attr("src", fullSrc);

                    });
                });
            });
        });


        return this.el;
    }

});;function Container() {
    return {

        mostRecentSize:{ width:0, height:0 },

        props: {},

        appContext: {},

        getValue:function (object, prop) {
            if (!(object && object[prop])) return null;
            return _.isFunction(object[prop]) ? object[prop]() : object[prop];
        },

        sync:function (method, model, options) {
            if (model.useAppData /* && model.appDataKey && model.appDataFields */) {
                // Ensure that we have the appropriate request data.
                if (!options.data && model && (method == 'create' || method == 'update')) {
                    var data = model.toJSON();
                    var key = model.appDataKey();

                    var obj = {};
                    obj[key] = JSON.stringify(data);
                    osapi.appdata.update({
                        data:obj,
                        userId:'@me',
                        escapeType:opensocial.EscapeType.NONE
                    }).execute(function (response) {
                            if (response.error) {
                                console.log("update error", response.error);
                                if (options.error) {
                                    options.error( response.error);
                                }
                            } else {
                                console.log("update response", response);
                                model.set('id', key);
                                if (options.postSuccess) {
                                    options.postSuccess(model);
                                }
                                model.trigger("saved");
                            }
                        });
                } else if (method == 'read') {
                    var payload = {
                        userId:'@me',
                        fields:model.appDataFields(),
                        escapeType:opensocial.EscapeType.NONE
                    };

                    osapi.appdata.get(payload).execute(function (response) {
                        if (response.error) {
                            console.log("fetch error", response.error);
                            if (options.error) {
                                options.error( response.error );
                            }
                        }
                        else {
                            model.parse(response);
                            if (options.postSuccess) {
                                options.postSuccess(model);
                            }
                        }
                    });
                } else if (method == 'delete') {
                    var payload = {
                        keys:model.appDataFields(),
                        userId:'@me'
                    };
                    console.log("to remove", payload);
                    osapi.appdata['delete'](payload).execute(function (response) {
                        if (response.error) {
                            console.log("delete error", response.error);
                            if (options.error) {
                                options.error(response.error);
                            }
                        }
                        else {
                            if (options.postSuccess) {
                                options.postSuccess(model);
                            }
                            model.trigger("deleted");
                        }
                    });
                }
            } else {
                // a call to some resource local to the app?

                var base = gadgets.util.getUrlParameters()['url'];
                base = base.substring(0, base.lastIndexOf('/'));

                var href = base + this.getValue(model, 'url') + "?ts=" + new Date().getTime();
                var payload = { 'href':href, 'format':'json', 'authz':null }; // xxx AUTHZ is unsigned for now!!! REMOVE THIS HACK WHEN GOING AGAINST REAL SERVICE
                if (model.headers) {
                    payload.headers = this.getValue(model, 'headers');
                }
                payload.callback = function (data) {
                    if (data.status == 200 || data.status == 204) {
                        model.parse(data.content);
                        if (options.postSuccess) {
                            options.postSuccess(model, data);
                        }
                    } else {
                        var resp;
                        if (data.content && data.content.jiveFault) {
                            resp = data.content;
                        } else {
                            resp = {"jiveFault":{"stackTrace":"", "errorMessage":data.status, "errorCode":"UNKNOWN_ERROR", "requestId":"", "blocking":false}};
                        }
                        options.error(model, resp);
                    }
                };

                if ((method == "read" && model.postForRead) || method == 'create') {
                    osapi.http.post(payload).execute(payload.callback);
                } else if (method == "read") {
                    osapi.http.get(payload).execute(payload.callback);
                } else {
                    alert('Unsupported method');
                }
            }

        },

        loadExperience:function () {
	        var container = this;
	        $.get(gadgets.io.getProxyUrl(gadgets.util.getUrlParameters()['url'].replace(/(\/app.xml)|(\/gadget.xml)/, '/templates.html')) + "&nocache=1",
		          function(templates) {
		              container.$templates = $(templates);
		              $('body').append(templates);
		              var experience = container.appContext.defaultRoute || 'main';
		              Backbone.history.navigate( experience, true );
		          });
        },

	    getTemplate: function(selector) {
	        return this.$templates.filter(selector).html();
	    },

        httpPost:function (options) {

            var base = gadgets.util.getUrlParameters()['url'];
            base = base.substring(0, base.lastIndexOf('/'));

            var payload = {
                'href':base + this.getValue(options, 'url'),
                'format':'json',
                'authz':'signed'
            };

            if (options.headers) {
                payload.headers = this.getValue(options, 'headers');
            }

            if (options.data) {
                payload.body = JSON.stringify(this.getValue(options, 'data'));
            }

            osapi.http.post(payload).execute(function (data) {
                if (data.status == 200 || data.status == 204) {
                    options.success(data.content);
                } else {
                    var resp;
                    if (data.content && data.content.jiveFault) {
                        resp = data.content;
                    } else {
                        resp = {"jiveFault":{"stackTrace":"", "errorMessage":data.status, "errorCode":"UNKNOWN_ERROR", "requestId":"", "blocking":false}};
                    }
                    //TODO: Generic Error Handling
                    if (options.error) {
                        options.error(resp);
                    }
                }
            });

        },

        httpGet:function (options) {

            var base = gadgets.util.getUrlParameters()['url'];
            base = base.substring(0, base.lastIndexOf('/'));

            var payload = {
                'href':base + this.getValue(options, 'url'),
                'format':'json',
                'authz':'signed'
            };

            if (options.headers) {
                payload.headers = this.getValue(options, 'headers');
            }

            osapi.http.get(payload).execute(function (data) {
                if (data.status == 200 || data.status == 204) {
                    options.success(data.content);
                } else {
                    var resp;
                    if (data.content && data.content.jiveFault) {
                        resp = data.content;
                    } else {
                        resp = {"jiveFault":{"stackTrace":"", "errorMessage":data.status, "errorCode":"UNKNOWN_ERROR", "requestId":"", "blocking":false}};
                    }
                    //TODO: Generic Error Handling
                    if (options.error) {
                        options.error(resp);
                    }
                }
            });

        },

        getProxyUrl:function (url) {
            return gadgets.io.getProxyUrl(url) + "&nocache=" + gadgets.util.getUrlParameters()['nocache'];
        },

        close:function (closeEvents) {
            if (closeEvents) {
                osapi.jive.core.container.closeApp({data:{events:closeEvents}});
            } else {
                osapi.jive.core.container.closeApp();
            }
        },

        getViewerId:function () {
            return opensocial.data.getDataContext().getDataSet('sbsContext').user.userID;
        },

        adjustSize:function () {
            this.adjustWidth();
            this.adjustHeight();
        },

        adjustWidth:function (opt_width) {
            if (gadgets.window.adjustWidth) {
                gadgets.window.adjustWidth(opt_width);
            }
        },

        adjustHeight:function (opt_height) {
            if (gadgets.window.adjustHeight) {
                gadgets.window.adjustHeight(opt_height);
            }
        },

        windowWidthHasChanged:function (opt_width) {

            var thisWidth = parseInt(opt_width || gadgets.window.getWidth(), 0);
            if (thisWidth == this.mostRecentSize.width)
                return false;

            this.mostRecentSize.width = thisWidth;
            return true;

        },

        windowHeightHasChanged:function (opt_height) {

            var thisHeight = parseInt(opt_height || gadgets.window.getHeight(), 0);
            if (thisHeight == this.mostRecentSize.height)
                return false;

            this.mostRecentSize.height = thisHeight;
            return true;

        },

        getUser:function (owner, callback) {
            osapi.people.get({"userId": owner}).execute(callback);
        },

        setProps: function( props ) {
            this.props = props;
        },

        getProps: function() {
            return this.props;
        },

        setAppContext: function( context ) {
            if ( context ) {
                this.appContext = context;
            }
        },

        getAppContext: function() {
            return this.appContext;
        }

    };
}
;window.GalleryController = function(container, viewManager) {

    this.viewManager = viewManager;
    this.container = container;

    this.galleryCreate = function () {
        var galleryModel = new GalleryModel();
        var galleryEditView = new GalleryEditView({model:galleryModel, container: this.container });

        if ( !$.browser.msie ) {
            galleryEditView.deactivateIE();
        }

        this.viewManager.showView(galleryEditView);
        if (  $.browser.msie ) {
            galleryEditView.setupIE();
        }

        galleryModel.bind("saved", function () {

            var galleryID = galleryModel.get("id");
            var galleryListModel = new GalleryList();
            galleryListModel.bind("saved", function () {
                Backbone.history.navigate('gallery-saved/' + galleryID, true);
            });
            galleryListModel.bind("parsed", function () {
                galleryListModel.add(galleryID);
                galleryListModel.save();
            });

            galleryListModel.fetch();
        });

        galleryEditView.renderGallery(galleryModel);
    };

    this.galleryEdit = function (galleryID) {

        var galleryModel = new GalleryModel({id:galleryID});
        var galleryEditView = new GalleryEditView({model:galleryModel, container: this.container });

        if ( !$.browser.msie ) {
            galleryEditView.deactivateIE();
        }

        viewManager.showView(galleryEditView);
        if (  $.browser.msie ) {
            galleryEditView.setupIE();
        }

        galleryModel.bind('parsed', function (gallery) {
            galleryEditView.renderGallery(gallery);
        });

        galleryModel.bind("saved", function () {
            var galleryListModel = new GalleryList();
            galleryListModel.bind("saved", function () {
                Backbone.history.navigate('gallery-saved/' + galleryID, true);
            });
            galleryListModel.bind("parsed", function () {
                galleryListModel.add(galleryID);
                galleryListModel.save();
            });

            galleryListModel.fetch();
        });

        galleryModel.fetch();
    };

    this.galleryDelete = function (galleryID) {
        var galleryModel = new GalleryModel({id:galleryID});
        galleryModel.bind("deleted", function () {
            var galleryListModel = new GalleryList();
            galleryListModel.bind("saved", function () {
                Backbone.history.navigate('main', true);
            });
            galleryListModel.bind("parsed", function () {
                galleryListModel.subtract(galleryID);
                galleryListModel.save();
            });

            galleryListModel.fetch();
        });

        
        galleryModel.fetch( {
            postSuccess: function(model) {
                model.destroy();
            },
            error: function(model, response) {
                console.log("Error fetching Gallery Model to be deleted", response);
            }
        });
    };

    this.galleryDrop = function (galleryID) {
        var model = new GalleryModel({id:galleryID});
        model.fetch({postSuccess:function () {

            // xxxx todo move this

            function getImageUrl(relativeUrl, proxied) {
                var gadgetURL = _args()["url"];
                var baseURL = gadgetURL.replace(/[a-zA-Z0-9_]+\.xml(\?.*)?/, "");
                return proxied ? gadgets.io.getProxyUrl(baseURL + relativeUrl) : baseURL + relativeUrl;
            }

            osapi.jive.corev3.people.get({id:"@me"}).execute(function (result) {
                var id = result.id;
                var outgoing = {
                    display:{
                        "type":"image",
                        "previewImage":getImageUrl("images/1x1.GIF")
                    },
                    target:{
                        "type":"embed",
                        "view":"show",
                        "context":{
                            "galleryID":galleryID
                        }
                    }
                };

                osapi.jive.version.getVersionInfo().execute(function (data) {
                    osapi.jive.core.container.artifacts.create(outgoing, 'com.jivesoftware.inline.gallery.show',
                                                               function (markupResponse) {
                                                                   var artifactMarkup = markupResponse.markup, error = markupResponse.error;
                                                                   var artifactDom = $('<span>' + artifactMarkup + '</span>');
                                                                   artifactDom.find('a').html('<img src="' + model.get("preview") + '"/>');
                                                                   var html = artifactDom.html() + "<div>" + artifactMarkup + "</div>";
                                                                   osapi.jive.core.container.editor().insert(html);
                                                               }, false, true);
                });
            });
        }});


    };

    this.galleryView = function( galleryID ) {

        var gallery = new GalleryModel({id:galleryID});
        var view = new GalleryViewer({model: gallery, container: this.container });

        var viewManager = this.viewManager;
        gallery.bind("parsed", function(gallery) {
            view.render();
            viewManager.showView(view);
        });

        gallery.fetch();
    };

}
;window.GalleryListController = function(container, viewManager) {

    this.viewManager = viewManager;
    this.container = container;

    this.galleryList = function () {
        var galleryListModel = new GalleryList();
        var galleryListView = new GalleryListView({ container: this.container });

        galleryListModel.bind('parsed', function (galleryIDs) {

            if (!galleryIDs || galleryIDs.length < 1) {
                galleryListView.renderNoGalleries();
            }
            else {
                galleryListView.renderGalleries();

                $.each(galleryIDs, function () {
                    var galleryID = this;

                    var gallery = new GalleryModel({id:galleryID});
                    gallery.bind('parsed', function (gallery) {
                        galleryListView.renderGallery(gallery);
                    });

                    gallery.fetch();
                });
            }

        });

        galleryListView.render();
        this.viewManager.showView(galleryListView);

        galleryListModel.fetch();
    }
}
;function Router() {

    var defaultRoutes = {
            'main'                  :         'showMain',
            'gallery-create'        :         'galleryCreate',
            'gallery-edit/:id'      :         'galleryEdit',
            'gallery-edit-cancel'   :         'galleryEditCancel',
            'gallery-delete/:id'    :         'galleryDelete',
            'gallery-drop/:id'      :         'galleryDrop',
            'gallery-select/:id'      :       'galleryDrop',
            'gallery-view/:id'      :         'galleryView',
            'gallery-saved/:id'     :         'gallerySaved',
            'close'                 :         'close',
            '*actions'              :         'defaultAction'
    };

    var container;
    var AppRouter = Backbone.Router.extend({


        showMain:function () {
            this.galleryList();
        },

        galleryList: function() {
            this.galleryListController.galleryList();
        },

        galleryCreate:function () {
            this.galleryController.galleryCreate();
        },

        galleryEdit:function (galleryID) {
            this.galleryController.galleryEdit(galleryID);
        },

        galleryEditCancel:function (galleryID) {
            this.showMain();
        },

        galleryDelete:function (galleryID) {
            this.galleryController.galleryDelete(galleryID);
        },

        gallerySaved:function (galleryID) {
            this.showMain();
        },

        galleryDrop:function (galleryID) {
            this.galleryController.galleryDrop(galleryID);
        },

        galleryView:function(galleryID ) {
            this.galleryController.galleryView(galleryID);
        },

        close: function() {
            osapi.jive.core.container.closeApp();
        },

        defaultAction:function () {
            container.loadExperience();
        }

    });

    var initialize = function (appContainer) {

        var routes = defaultRoutes;
	    container = appContainer;

        // override any routes according to context
        var appContext = container.getAppContext();
        if ( appContext ) {
            var routeOverrides = appContext.routes;
            if ( routeOverrides ) {
                for (var key in routeOverrides) {
                    if (routeOverrides.hasOwnProperty(key)) {
                        var val = routeOverrides[key];
                        if ( val ) {
                            routes[key] = val;
                        }
                    }
                }
            }
        }
        var app_router = new AppRouter( {
            routes: routes
        });

        var viewManager = new ViewManager();
        app_router.galleryListController = new GalleryListController(container, viewManager);
        app_router.galleryController = new GalleryController(container, viewManager);

        Backbone.history.start();
    };

    return {
        initialize:initialize
    };
}
;function App() {

    var container = new Container();
    var initialize = function ( context ) {
        if ( context ) {
            container.setAppContext( context );
        }
        if ( container.initialize) {
            container.initialize();
        }
        var router = new Router();
        router.initialize(container);
    };

    Backbone.sync = function (method, model, options) {
        container.sync(method, model, options);
    };

    Backbone.View.prototype.close = function () {
        this.remove();
        this.unbind();
        if (this.onClose) {
            this.onClose();
        }
    };

    return {
        initialize: initialize,

        getAbsoluteUrl: function(path) {
            return gadgets.util.getUrlParameters()['url'].replace(/(\/app.xml)|(\/gadget.xml)/, path);
        },
            
        getProxiedUrl: function(path) {
            return gadgets.io.getProxyUrl(gadgets.util.getUrlParameters()['url'].replace(/(\/app.xml)|(\/gadget.xml)/, path)) + "&nocache=1";
        }
    };
};var jigPersistence = function( userId ) {

    userId = userId || '@me';

    var fetch = function( fields, success, error ) {
        if ( fields.length < 1 ) {
            var options = {
                userId: userId,
                escapeType: opensocial.EscapeType.NONE
            };
        } else {
            var options = {
                fields : fields,
                userId: userId,
                escapeType: opensocial.EscapeType.NONE
            };
        }

        osapi.appdata.get( options ).execute(function(response) {
            if (response.error) {
                console.log( "fetch error", response.error );
                if ( error ) error();
            }
            else {
                if ( success ) success(response);
            }
        });
    };

    var remove = function( fields, success, error ) {
        var options = {
            keys : fields,
            userId: '@me'
        };
        console.log( "to remove", options );
        osapi.appdata['delete']( options ).execute(function(response) {
            if (response.error) {
                console.log( "delete error", response.error );
                if ( error ) error();
            }
            else {
                if ( success ) success(response);
            }
        });
    };

    var update = function( key, value, success, error ) {
        var obj = {};
        obj[key] = value;
        osapi.appdata.update({
            data : obj,
            userId: '@me',
            escapeType: opensocial.EscapeType.NONE
        }).execute(function(response) {
                if (response.error) {
                    console.log( "update error", response.error );
                    if ( error ) error();
                } else {
                    console.log( "update response", response );
                    if ( success ) success();
                }
            });
    };

    this.save = function ( json, success, key ) {
        update( key || "jig", json, success );
    };

    this.load = function( success, key) {
        fetch( [key], function( response ) {

            // get data for the first user (should be only one anyway)
            var theData;
            for( var user in response ) {
                if(response.hasOwnProperty(user)) {
                    theData = response[user];
                    break;
                }
            }

            for( var id in theData ) {
                console.log( theData );
                if (theData.hasOwnProperty(id) ) {
                    var raw = theData[key || "jig"];
                    if ( raw ) {
                        var json = JSON.parse( raw );
                        success( json );
                        return;
                    }
                }
            }

            success(null);

        });
    };

};
;gadgets.util.registerOnLoadHandler(function() {

    var didNotChangeImages = true;
    var playGraphic = new Image();

    $(document).ready(function() {

        playGraphic.src = app.getProxiedUrl("/images/play-graphic.png");

        /*****************************
         Variables
         *****************************/
        var imgWidth = 90,
            imgHeight = 90,
            zindex = 0;

            dropzone = $('#result'),
            uploadBtn = $('#uploadbtn'),
            defaultUploadBtn = $('#upload');


        /*****************************
         Events Handler
         *****************************/
        dropzone.on('dragover', function() {
            //add hover class when drag over
            dropzone.addClass('hover');
            return false;
        });
        dropzone.on('dragleave', function() {
            //remove hover class when drag out
            dropzone.removeClass('hover');
            return false;
        });
        dropzone.on('drop', function(e) {
            //prevent browser from open the file when drop off
            e.stopPropagation();
            e.preventDefault();
            dropzone.removeClass('hover');

            //retrieve uploaded files data
            var files = e.originalEvent.dataTransfer.files;
            didNotChangeImages = false;
            processFiles(files);

            return false;
        });

        uploadBtn.on('click', function(e) {
            e.stopPropagation();
            e.preventDefault();
            //trigger default file upload button
            defaultUploadBtn.click();
        });
        defaultUploadBtn.on('change', function() {
            //retrieve selected uploaded files data
            var files = $(this)[0].files;
            didNotChangeImages = false;
            processFiles(files);

            return false;
        });

        $("#link_save").click( saveGallery );
        $("#link_clear").click( clearGallery );
        $("#link_load").click( loadGallery );

        $("#result").sortable();
        $( "#result" ).disableSelection();

        loadGallery();

        /*****************************
         internal functions
         *****************************/

        function clearGallery() {
            didNotChangeImages = false;
            $("#result img").remove();
        }

        function loadGallery() {
            clearGallery();
            didNotChangeImages = true;

            var persistence = new jigPersistence();

            // pre-seed the gallery
            persistence.load( function(ids) {
                console.log("Loaded image id manifest", ids);


                if ( !ids ) {
                    return;
                }

                // pre seed the gallery dom
                $.each( ids.images, function() {
                    var imageId = this;
                    var imgDom = $(document.createElement("img"));
                    imgDom.attr("src","http://apphosting.jivesoftware.com/apps/dev/jigtest/images/ajax_loader_large.gif").attr("data-id", imageId );
                    $("#result").append(imgDom);
                });

                console.log("Loaded image id manifest", ids);

                $.each( ids.images, function() {
                    var imageId = this;
                    console.log("loading image id " + imageId );

                    persistence.load( function( thumb ) {
                        var imgDom = $("#result img[data-id=" + imageId + "]");
                        var previewSrc = thumb.imageSrc;
                        var width = thumb.width;
                        var height = thumb.height;
                        imgDom.attr("src", previewSrc);
                        imgDom.attr("data-width", width);
                        imgDom.attr("data-height", height);

                        // load source now
                        persistence.load( function( source ) {
                            var fullSrc = source.imageSrc;
                            imgDom.attr("data-fullsrc", fullSrc);
                        }, "jig_src_" + imageId );

                    }, "jig_thumb_" + imageId );

                });

            }, "jig_ids" );
        }

        function saveGallery() {
            debugger;
            var images = $("#result").find("img");
            if ( images.length < 1 ) {
                alert("Nothing to save");
                return;
            }


            var persistence = new jigPersistence();
            var savedCount = 0;

            var saveManifest = function() {
                // gather the ids

                var galleryIdsJson = {
                    "images": []
                };

                $.each( images, function() {
                    var image = $(this);
                    var imageId = image.attr("data-id") || randomNum();
                    galleryIdsJson.images.push( imageId );
                });

                persistence.save( galleryIdsJson, function() {
                    console.log("Saved gallery image IDs");
                }, "jig_ids");

            };

            var saved = function() {
                if ( savedCount == images.length ) {
                    saveManifest();

                    alert("Done saving " + savedCount);
                }
            };

            if ( didNotChangeImages ) {
                saveManifest();

                alert("Did not change images, maybe just order. Saving that.");
                // did not change images, only persist the order
                return;
            }

            $.each( images, function() {
                var image = $(this);
                var previewSrc = image.attr("src");
                var fullSrc = image.attr("data-fullsrc");
                var width = image.attr("data-width");
                var height = image.attr("data-height");

                var imageId = image.attr("data-id") || randomNum();

                var thumbJson = {
                    "imageSrc": previewSrc,
                    "width": width,
                    "height": height
                };

                var fullJson = {
                    "imageSrc": fullSrc,
                    "width": width,
                    "height": height
                };

                persistence.save(thumbJson , function() {
                    console.log("Saved image id " + imageId + " thumb");

                    persistence.save(fullJson , function() {
                        console.log("Saved image id " + imageId + " source");
                        savedCount++;
                        saved();
                    }, "jig_src_" + imageId);


                }, "jig_thumb_" + imageId);
            });

        }

            //Bytes to KiloBytes conversion
        function convertToKBytes(number) {
            return (number / 1024).toFixed(1);
        }

        function compareWidthHeight(width, height) {
            var diff = [];
            if(width > height) {
                diff[0] = width - height;
                diff[1] = 0;
            } else {
                diff[0] = 0;
                diff[1] = height - width;
            }
            return diff;
        }

        //convert datauri to blob
        function dataURItoBlob(dataURI) {
            var BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder || window.BlobBuilder;

            //skip if browser doesn't support BlobBuilder object
            if(typeof BlobBuilder === "undefined") {
                $('#err').html('Ops! There have some limited with your browser! <br/>New image produced from canvas can\'t be upload to the server...');
                return dataURI;
            }

            // convert base64 to raw binary data held in a string
            // doesn't handle URLEncoded DataURIs
            var byteString;
            if (dataURI.split(',')[0].indexOf('base64') >= 0)
                byteString = atob(dataURI.split(',')[1]);
            else
                byteString = unescape(dataURI.split(',')[1]);

            // separate out the mime component
            var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

            // write the bytes of the string to an ArrayBuffer
            var ab = new ArrayBuffer(byteString.length);
            var ia = new Uint8Array(ab);
            for (var i = 0; i < byteString.length; i++) {
                ia[i] = byteString.charCodeAt(i);
            }

            // write the ArrayBuffer to a blob, and you're done
            var bb = new BlobBuilder();
            bb.append(ab);
            return bb.getBlob(mimeString);
        }

        /*****************************
         canvas filter function
         *****************************/
            //Black & White image effect
            //by Marco Lisci - http://badsharkco.com/
        var grayscale = function(context) {
            var imgd = context.getImageData(0, 0, imgWidth, imgHeight);
            var pix = imgd.data;
            for (var i = 0, n = pix.length; i < n; i += 4) {
                var grayscale = pix[i  ] * .3 + pix[i+1] * .59 + pix[i+2] * .11;
                pix[i  ] = grayscale;
                pix[i+1] = grayscale;
                pix[i+2] = grayscale;
            }
            context.putImageData(imgd, 0, 0);
        }

        //canvas-blur effect
        //by Matt Riggott - http://www.flother.com/
        var blurry = function(context, image, diff) {
            var i, x, y,
                blureffect = 4;

            context.globalAlpha = 0.1;
            for (i = 1; i <= blureffect; i++) {
                for (y = -blureffect/2; y <= blureffect/2; y++) {
                    for (x = -blureffect/2; x <= blureffect/2; x++) {
                        context.drawImage(image, diff[0]/2, diff[1]/2, image.width-diff[0], image.height-diff[1], x, y, imgWidth, imgHeight);
                    }
                }
            }
            context.globalAlpha = 1.0;
        }



        /*****************************
         Process FileList
         *****************************/
        var processFiles = function(files) {
            if(files && typeof FileReader !== "undefined") {
                //process each files only if browser is supported
                for(var i=0; i<files.length; i++) {
                    readFile(files[i]);
                }
            } else {

            }
        }


        /*****************************
         Read the File Object
         *****************************/
        var readFile = function(file) {
            if( (/image/i).test(file.type) ) {
                //define FileReader object
                var reader = new FileReader();

                $("#link_save").hide();

                //init reader onload event handlers
                reader.onload = function(e) {
                    var image = $('<img/>')
                        .load(function() {
                            //when image fully loaded
                            var preview = getPreviewImage(this);
                            var src = getSourceImage(this);
                            console.log(preview, src);
                            createPreview(file, preview.url, src.url, src.width, src.height );
                        })
                        .attr('src', e.target.result);

                    $("#link_save").show();

                };

                //begin reader read operation
                reader.readAsDataURL(file);

                $('#err').text('');
            } else {
                //some message for wrong file format
                $('#err').text('*Selected file format not supported!');
            }
        }

        var mkImage = function(image, width, height, effect, croping ) {
            var canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            var ctx = canvas.getContext('2d');

            //default resize variable
            var diff = [0, 0];
            if(croping == 'crop') {
                //get resized width and height
                diff = compareWidthHeight(image.width, image.height);
            }

            //draw canvas image
            ctx.drawImage(image, diff[0]/2, diff[1]/2, image.width-diff[0], image.height-diff[1], 0, 0, width, height);

            //apply effects if any
            if(effect == 'grayscale') {
                grayscale(ctx);
            } else if(effect == 'blurry') {
                blurry(ctx, image, diff);
            } else {}

            return canvas.toDataURL("image/jpeg");
        }

        /*****************************
         Get New Canvas Image URL
         *****************************/

        var getDimensions = function(maxWidth, maxHeight, width, height ) {
            var neww, newh = 0;
            var rw = width / maxWidth; // width and height are maximum thumbnail's bounds
            var rh = height / maxHeight;

            if (rw > rh)
            {
                newh = height / rw;
                neww = maxWidth;
            }
            else
            {
                neww = width / rh;
                newh = maxHeight;
            }

            return {
                width: neww,
                height: newh
            };
        }

        var getPreviewImage = function(image) {
            var effect = $('input[name=effect]:checked').val();
            var croping = $('input[name=croping]:checked').val();
            var dimensions = getDimensions(90, 90, image.width, image.height );

            return {
                width: dimensions.width,
                height: dimensions.height,
                url: mkImage(image, dimensions.width, dimensions.height, effect, croping )
            };
        }

        var getSourceImage = function(image) {
            var effect = $('input[name=effect]:checked').val();
            var croping = $('input[name=croping]:checked').val();
            var dimensions = getDimensions(500, 500, image.width, image.height );

            return {
                width: dimensions.width,
                height: dimensions.height,
                url: mkImage(image, dimensions.width, dimensions.height, effect, croping )
            };
        }


        /*****************************
         Draw Image Preview
         *****************************/
        var createPreview = function(file, newURL, srcURL, width, height ) {
            //populate jQuery Template binding object
            var imageObj = {};
            imageObj.imageId = randomNum();
            imageObj.filePath = newURL;
            imageObj.fullSrc = srcURL;
            imageObj.imageWidth = width;
            imageObj.imageHeight = height;
            imageObj.fileName = file.name.substr(0, file.name.lastIndexOf('.')); //subtract file extension
            imageObj.fileOriSize = convertToKBytes(file.size);
            imageObj.fileUploadSize = convertToKBytes(dataURItoBlob(newURL).size); //convert new image URL to blob to get file.size

            //extend filename
            var effect = $('input[name=effect]:checked').val();
            if(effect == 'grayscale') {
                imageObj.fileName += " (Grayscale)";
            } else if(effect == 'blurry') {
                imageObj.fileName += " (Blurry)";
            }

            //append new image through jQuery Template
            var randvalue = Math.floor(Math.random()*31)-15;  //random number
            var img = $("#imageTemplate").tmpl(imageObj).prependTo("#result")
                .hide()
                .css({
                    'z-index': zindex++
                })
                .show();

            if(isNaN(imageObj.fileUploadSize)) {
                $('.imageholder span').last().hide();
            }
        }

        //file upload via original byte sequence
        var processFileInIE = function(file) {

            var imageObj = {};
            var extension = ['jpg', 'jpeg', 'gif', 'png'];
            var filepath = file.value;
            if (filepath) {
                //get file name
                var startIndex = (filepath.indexOf('\\') >= 0 ? filepath.lastIndexOf('\\') : filepath.lastIndexOf('/'));
                var filedetail = filepath.substring(startIndex);
                if (filedetail.indexOf('\\') === 0 || filedetail.indexOf('/') === 0) {
                    filedetail = filedetail.substring(1);
                }
                var filename = filedetail.substr(0, filedetail.lastIndexOf('.'));
                var fileext = filedetail.slice(filedetail.lastIndexOf(".")+1).toLowerCase();

                //check file extension
                if($.inArray(fileext, extension) > -1) {
                    //append using template
                    $('#err').text('');
                    imageObj.filepath = filepath;
                    imageObj.filename = filename;
                    var randvalue = Math.floor(Math.random()*31)-15;
                    $("#imageTemplate").tmpl(imageObj).prependTo( "#result" )
                        .hide()
                        .css({
                            'z-index': zindex++
                        })
                        .show();
                    $('#result').find('figcaption span').hide();
                } else {
                    $('#err').text('*Selected file format not supported!');
                }
            }
        }

    });

    function randomNum(maxVal) {
        var maxRandomVal = maxVal ? maxVal : 1000000;

        return Math.floor(Math.random()*maxRandomVal);
    }

});
;function ViewManager() {
    return {

        showView:function (view) {

            if (!this.currentView || view != this.currentView) {

                if (this.currentView)
                    this.currentView.close();

                this.currentView = view;
                // console.log("view", view.el );
                $("#main-content").html(this.currentView.el);
            }

        }

    };
};(function() {
    if (!window.console){console={log:function(s){}}};

    var routes = {
        'gallery-select/:id'     :         'galleryDrop'
    };

    var appContext = {
        routes: routes
    };
    window.app = new App();
    app.initialize(appContext);
})();