Source: widgets/masonrygrid.js

define('application/widgets/masonrygrid', [
    'rofl/widgets/horizontallist',
    'rofl/widgets/verticallist',
    'rofl/lib/utils',
    'application/utils'
], function (
    HorizontalList,
    VerticalList,
    Utils,
    AppUtils
) {
    'use strict';

    return HorizontalList.extend({

        /**
         * Initialises the MasonryGrid.
         *
         * @param {Object} config - The configuration.
         * @param {number} config.columns - The columns needed.
         * @param {Array} config.types - The types of grid items.
         */
        init: function init (config) {
            init.base.call(this);
            this.addClass(['masonrygrid']);

            this._configuration = config;

            this._onSelectedItemChangeBound = Utils.bind(this._onSelectedItemChange, this);
            this.setContinuousListener(true);

            this._setEventListeners();
        },

        /**
         * Sets the event listeners.
         *
         * @private
         */
        _setEventListeners: function () {
            this.addEventListener('selecteditemchange', this._onSelectedItemChangeBound);
        },

        /**
         * Removes the event listeners.
         *
         * @private
         */
        _removeEventListeners: function () {
            this.removeEventListener('selecteditemchange', this._onSelectedItemChangeBound);
        },

        /**
         * Sets the data item.
         *
         * @param {Array} items - The item data.
         */
        setDataItem: function setDataItem (items) {
            setDataItem.base.call(this, items);

            Utils.each(this.getChildWidgets(), function (widget) {
                widget.dispose();
            });
            this.removeChildWidgets();

            this._currentItemHeight = 0;
            this._items = [];
            this._heights = [];
            this._generateColumns();
            this._appendItems(items);
        },

        /**
         * Generates the columns needed.
         *
         * @private
         */
        _generateColumns: function () {
            var amountOfColumns = this._configuration.columns,
                i;

            for (i = 0; i < amountOfColumns; i++) {
                this._appendColumn();
            }
        },

        /**
         * Appends a column.
         *
         * @private
         */
        _appendColumn: function () {
            var column = new VerticalList();

            column.addClass('column');
            column.setContinuousListener(true);

            this.appendChildWidget(column);
        },

        /**
         * Appends the items.
         *
         * @param {Array} items - The items.
         * @private
         */
        _appendItems: function (items) {
            var amountOfColumns = this._configuration.columns,
                itemsPerRow = Math.floor(items.length / amountOfColumns),
                self = this,
                currentColumn,
                currentItems,
                i;

            for (i = 0; i < amountOfColumns; i++) {
                currentColumn = this.getChildWidgetByIndex(i);
                currentItems = this._generateItems(items.splice(0, itemsPerRow));
                this._items[i] = currentItems;
                this._heights[i] = [];

                // eslint-disable-next-line no-loop-func
                Utils.each(currentItems, function (item) {
                    self._heights[i].push(item.height);
                    currentColumn.appendChildWidget(item.widget);
                });
            }
        },

        /**
         * Generates the items.
         *
         * @param {Array} items - The item data.
         * @returns {Array} - The formatted items randomised.
         * @private
         */
        _generateItems: function (items) {
            var types = this._configuration.types,
                formattedItems = [],
                lastItemIndex = 0,
                itemsOfType;

            Utils.each(types, Utils.bind(function (type) {
                itemsOfType = Math.round((items.length / 100) * type.percentage);

                formattedItems = formattedItems.concat(this._generateItemsPerType(type, items.slice(lastItemIndex, lastItemIndex + itemsOfType - 1)));
                lastItemIndex = lastItemIndex + itemsOfType;
            }, this));

            return AppUtils.randomiseArray(formattedItems);
        },

        /**
         * Generates the items per type.
         *
         * @param {Object} type - The type.
         * @param {Array} items - The item data.
         * @returns {Array} - The formatted items.
         * @private
         */
        _generateItemsPerType: function (type, items) {
            var formattedItems = [];

            Utils.each(items, function (item) {
                formattedItems.push({
                    height: type.height,
                    type: type.class,
                    widget: type.formatter.format(item)
                });
            });

            return formattedItems;
        },

        /**
         * Gets executed when the selected item changes.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onSelectedItemChange: function (e) {

            // The item changed was an individual row asset.
            if (e.target.parentWidget === this) {
                this._setCurrentAssetHeight(e);
            }

            // The item changes was a row.
            if (e.target === this) {
                this._setActiveChildWidget(e);
            }
        },

        /**
         * Attempts to set the correct active item according to the current asset height.
         *
         * @param {Object} e - The item.
         * @private
         */
        _setActiveChildWidget: function (e) {
            var rowIndex = e.index,
                heights = this._heights[rowIndex],
                combinedHeights = 0,
                i,
                j;

            if (!heights) {
                return;
            }

            for (i = 0, j = heights.length; i < j; i++) {

                if (heights[i] + combinedHeights > this._currentItemHeight) {
                    break;
                }

                combinedHeights += heights[i];
            }

            this._currentItemHeight = combinedHeights;
            this.getChildWidgetByIndex(rowIndex).setActiveChildIndex(i);
        },

        /**
         * Sets the current asset height.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _setCurrentAssetHeight: function (e) {
            var target = e.target,
                parentWidget = target.parentWidget,
                rowIndex = parentWidget.getIndexOfChildWidget(target),
                heights = this._heights[rowIndex],
                itemIndex = e.index,
                combinedHeights = 0,
                heightsAtIndex;

            if (!heights) {
                return;
            }

            heightsAtIndex = heights.slice(0, itemIndex);

            Utils.each(heightsAtIndex, function (height) {
                combinedHeights += height;
            });

            this._currentItemHeight = combinedHeights;
        },

        /**
         * Empties the grid.
         */
        empty: function () {
            Utils.each(this.getChildWidgets(), function (widget) {
                widget.dispose();
            });

            this._currentItemHeight = 0;
            this._heights = [];
            this._items = [];
        },

        /**
         * Disposes the widget.
         */
        dispose: function () {
            this._removeEventListeners();

            this.empty();
        },

        /**
         * Returns the active child index.
         *
         * @returns {number} - The active child index.
         */
        getActiveChildIndex: function () {
            var activeElement = this.getActiveChildWidget();

            if (!activeElement) {
                return 0;
            }

            return activeElement.getActiveChildWidgetIndex();
        },

        /**
         * Aligns to proper index.
         *
         */
        alignToTop: function () {
            var firstColumn = this.getChildWidgetByIndex(0),
                firstAsset = firstColumn.getChildWidgetByIndex(0);

            // Reset the current item height to allow focus from any position.
            this._currentItemHeight = 0;

            firstAsset.focus();
        }
    });
});