ulmic/tramway-dev

View on GitHub
tramway-landing/app/assets/javascripts/justified_images.js

Summary

Maintainability
D
1 day
Test Coverage
/*jshint white:false*/
/* global jQuery: true*/
// the semi-colon before function invocation is a safety net against concatenated
// scripts and/or other plugins which may not be closed properly.
;
(function($, window, document, undefined) {
    'use strict';
    // undefined is used here as the undefined global variable in ECMAScript 3 is
    // mutable (ie. it can be changed by someone else). undefined isn't really being
    // passed in so we can ensure the value of it is truly undefined. In ES5, undefined
    // can no longer be modified.

    // window and document are passed through as local variable rather than global
    // as this (slightly) quickens the resolution process and can be more efficiently
    // minified (especially when both are regularly referenced in your plugin).

    // Create the defaults once
    var pluginName = 'justifiedImages';


    // The actual plugin constructor

    function Plugin(element, options) {
        this.element = element;
        this.$el = $(element);
        this._name = pluginName;
        this.init(options);
    }

    Plugin.prototype = {
        defaults: {
            template: function(data) {
                return '<div class="photo-container" style="height:' + data.displayHeight + 'px;margin-right:' + data.marginRight + 'px;">' +
                    '<img class="image-thumb" src="' + data.src + '" style="width:' + data.displayWidth + 'px;height:' + data.displayHeight + 'px;" >' +
                    '</div>';
            },
            appendBlocks : function(){ return []; },
            rowHeight: 150,
            maxRowHeight: 350,
            handleResize: false,
            margin: 1,
            imageSelector: 'image-thumb',
            imageContainer: 'photo-container'
        },
        init: function(options) {
            this.options = $.extend({}, this.defaults, options);
            this.displayImages();
            if (this.options.handleResize) {
                this.handleResize();
            }
        },
        getBlockInRow: function(rowNum){
            var appendBlocks = this.options.appendBlocks();
            for (var i = 0; i < appendBlocks.length; i++) {
                var block = appendBlocks[i];
                if(block.rowNum === rowNum){
                    return block;
                }
            }
        },
        displayImages: function() {
            var self = this,
                ws = [],
                rowNum = 0,
                baseLine = 0,
                limit = this.options.images.length,
                photos = this.options.images,
                rows = [],
                totalWidth = 0,
                appendBlocks = this.options.appendBlocks();
            var w = this.$el.width();
            var border = parseInt(this.options.margin, 10);
            var d = this.$el,
                h = parseInt(this.options.rowHeight, 10);

            $.each(this.options.images, function(index, image) {
                var size = self.options.getSize(image);
                var wt = parseInt(size.width, 10);
                var ht = parseInt(size.height, 10);
                if (ht !== h) {
                    wt = Math.floor(wt * (h / ht));
                }
                totalWidth += wt;
                ws.push(wt);
            });

            $.each(appendBlocks, function(index, block){
                totalWidth += block.width;
            });
            var perRowWidth = totalWidth / Math.ceil(totalWidth / w);
            console.log('rows', Math.ceil(totalWidth / w));
            var tw = 0;
            while (baseLine < limit) {
                var row = {
                        width: 0,
                        photos: []
                    },
                    c = 0,
                    block = this.getBlockInRow(rows.length + 1);
                if(block){
                    row.width += block.width;
                    tw += block.width;
                }
                while ((tw + ws[baseLine + c] / 2 <= perRowWidth * (rows.length + 1)) && (baseLine + c < limit)) {
                    tw += ws[baseLine + c];
                    row.width += ws[baseLine + c];
                    row.photos.push({
                        width: ws[baseLine + c],
                        photo: photos[baseLine + c]
                    });
                    c++;
                }
                baseLine += c;
                rows.push(row);
            }
            console.log(rows.length, rows);
            /*for (var i = 1; i < rows.length; i++) {
                var row = rows[i];
                for (var j = 0; j < row.photos.length; j++) {
                    var photo = row.photos[j].photo;
                };
            }*/
            for (var i = 0; i < rows.length; i++) {
                var row = rows[i],
                    lastRow = false;
                rowNum = i + 1;
                if (this.options.maxRows && rowNum > this.options.maxRows) {
                    break;
                }
                if (i === rows.length - 1) {
                    lastRow = true;
                }
                tw = -1 * border;
                var newBlock = this.getBlockInRow(lastRow ? -1 : rowNum), availableRowWidth = w;
                if(newBlock){
                    availableRowWidth -= newBlock.width;
                    tw = 0;
                }

                // Ratio of actual width of row to total width of images to be used.
                var r = availableRowWidth / row.width, //Math.min(w / row.width, this.options.maxScale),
                    c = row.photos.length;

                // new height is not original height * ratio
                var ht = Math.min(Math.floor(h * r), parseInt(this.options.maxRowHeight,10));
                r = ht / this.options.rowHeight;
                var domRow = $('<div/>', {
                    'class': 'picrow'
                });
                domRow.height(ht + border);
                d.append(domRow);

                var imagesHtml = '';
                for (var j = 0; j < row.photos.length; j++) {
                    var photo = row.photos[j].photo;
                    // Calculate new width based on ratio
                    var wt = Math.floor(row.photos[j].width * r);
                    tw += wt + border;

                    imagesHtml += this.renderPhoto(photo, {
                        src: this.options.thumbnailPath(photo, wt, ht),
                        width: wt,
                        height: ht
                    }, newBlock ? false : j === row.photos.length - 1);
                }
                if(imagesHtml === ''){
                    domRow.remove();
                    continue;
                }

                domRow.html(imagesHtml);



                if ((Math.abs(tw - availableRowWidth) < 0.05 * availableRowWidth)) {
                    // if total width is slightly smaller than
                    // actual div width then add 1 to each
                    // photo width till they match
                    var k = 0;
                    while (tw < availableRowWidth) {
                        var div1 = domRow.find('.' + this.options.imageContainer + ':nth-child(' + (k + 1) + ')'),
                            img1 = div1.find('.' + this.options.imageSelector);
                        img1.width(img1.width() + 1);
                        k = (k + 1) % c;
                        tw++;
                    }
                    // if total width is slightly bigger than
                    // actual div width then subtract 1 from each
                    // photo width till they match
                    k = 0;
                    while (tw > availableRowWidth) {
                        var div2 = domRow.find('.' + this.options.imageContainer + ':nth-child(' + (k + 1) + ')'),
                            img2 = div2.find('.' + this.options.imageSelector);
                        img2.width(img2.width() - 1);
                        k = (k + 1) % c;
                        tw--;
                    }
                } else{
                    if( availableRowWidth - tw > 0.05 * availableRowWidth ){
                        var diff = availableRowWidth-tw,
                            adjustedDiff = 0,
                            images = domRow.find('.' + this.options.imageContainer),
                            marginTop = 0;
                        for(var l = 0 ; l < images.length ; l++ ){
                            var currentDiff = diff / (images.length),
                                imgDiv = images.eq(l),
                                img = imgDiv.find('.' + this.options.imageSelector),
                                imageWidth = img.width(),
                                imageHeight = img.height();
                            if( i === images.length - 1 ){
                                currentDiff = diff - adjustedDiff;
                            }
                            img.width( imageWidth + currentDiff );
                            img.height( ( imageHeight / imageWidth ) * (imageWidth + currentDiff) );
                            marginTop = (imageHeight - img.height()) / 2;
                            img.css('margin-top', marginTop);
                            adjustedDiff += currentDiff;
                        }
                    }
                }

                if(newBlock){
                    $('<div />', {
                        class : this.options.imageContainer + ' added-block',
                        css : {
                            width : newBlock.width,
                            height: ht
                        },
                        html : newBlock.html
                    }).appendTo(domRow);
                }
            }
        },
        renderPhoto: function(image, obj, isLast) {
            var data = {},
                d;
            d = $.extend({}, image, {
                src: obj.src,
                displayWidth: obj.width,
                displayHeight: obj.height,
                marginRight: isLast ? 0 : this.options.margin
            });
            if (this.options.dataObject) {
                data[this.options.dataObject] = d;
            } else {
                data = d;
            }
            return this.options.template(data);
        },
        handleResize: function() {},
        refresh: function(options) {
            this.options = $.extend({}, this.defaults, options);
            this.$el.empty();
            this.displayImages();
        }
    };


    // A really lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[pluginName] = function(option) {
        var args = arguments,
            result;

        this.each(function() {
            var $this = $(this),
                data = $.data(this, 'plugin_' + pluginName),
                options = typeof option === 'object' && option;
            if (!data) {
                $this.data('plugin_' + pluginName, (data = new Plugin(this, options)));
            }else{
                if (typeof option === 'string') {
                    result = data[option].apply(data, Array.prototype.slice.call(args, 1));
                } else {
                    data.refresh.call(data, options);
                }
            }
        });

        // To enable plugin returns values
        return result || this;
    };

})(jQuery, window, document);