Source: components/replay.js

define('application/components/replay', [
    'rofl/widgets/component',
    'application/widgets/masonrygrid',
    'antie/runtimecontext',
    'application/managers/search',
    'application/models/configuration',
    'rofl/lib/utils',
    'application/utils',
    'application/formatters/portraitasset',
    'application/formatters/landscapeasset',
    'rofl/events/keyevent',
    'rofl/widgets/label',
    'rofl/lib/l10n',
    'application/widgets/clock',
    'rofl/widgets/container',
    'rofl/widgets/horizontallist',
    'rofl/widgets/verticallist',
    'rofl/lib/promise',
    'application/widgets/replay/filterbutton',
    'rofl/analytics/web/google'
], function (
    Component,
    MasonryGrid,
    RuntimeContext,
    SearchManager,
    KPNConfiguration,
    Utils,
    AppUtils,
    PortraitAssetFormatter,
    LandscapeAssetFormatter,
    KeyEvent,
    Label,
    L10N,
    Clock,
    Container,
    HorizontalList,
    VerticalList,
    Promise,
    FilterButton,
    GoogleAnalytics
) {
    'use strict';

    var searchManager = SearchManager.getInstance(),
        application = RuntimeContext.getCurrentApplication(),
        layout = application.getLayout().replay,
        requiredScreenSize = application.getLayout().requiredScreenSize,
        l10n = L10N.getInstance(),
        device = RuntimeContext.getDevice(),
        GA = GoogleAnalytics.getInstance(),
        MAX_ITEMS = 32,
        filters = {
            films: {
                name: 'Films',
                value: 'Films'
            },
            series: {
                name: 'Series',
                value: 'Series'
            },
            crime: {
                name: 'Misdaad',
                value: 'Misdaad'
            },
            kids: {
                name: 'Kids',
                value: 'Animatie'
            },
            sport: {
                name: 'Sport',
                value: ['Sport', 'Live voetbal']
            },
            news: {
                name: 'Actualiteiten',
                value: 'Nieuws'
            },
            info: {
                name: 'Informatief',
                value: ['Informatief', 'Documentaire', 'Wetenschap', 'Natuur']
            },
            culture: {
                name: 'Cultuur & Religie',
                value: ['Kunst/Cultuur', 'Religieus']
            },
            entertainment: {
                name: 'Amusement',
                value: 'Amusement'
            },
            music: {
                name: 'Muziek',
                value: 'Muziek'
            },
            erotic: {
                name: 'Erotiek',
                value: 'Erotiek'
            }
        };

    return Component.extend({

        /**
         * Initialises the replay component.
         */
        init: function init () {
            init.base.call(this, 'replay');
            this._build();

            this._onKeyDownBound = Utils.bind(this._onKeyDown, this);
            this._onSelectedItemChangeBound = Utils.bind(this._onSelectedItemChange, this);
            this._onSelectBound = Utils.bind(this._onSelect, this);
            this._onSelectAssetsBound = Utils.bind(this._onSelectAssets, this);
        },

        /**
         * Builds the replay component.
         *
         * @private
         */
        _build: function () {
            this._buildPageContainer();
            this._buildHeader();
            this._buildTitle();
            this._buildSubtitle();
            this._buildClock();
            this._buildFilters();
            this._buildGrid();
        },

        /**
         * Builds the filters.
         *
         * @private
         */
        _buildFilters: function () {
            this._buildFiltersList();
        },

        /**
         * Builds the header.
         *
         * @private
         */
        _buildPageContainer: function () {
            var pageContainer = this._pageContainer = new VerticalList();

            this.appendChildWidget(pageContainer);
        },

        /**
         * Builds the header.
         *
         * @private
         */
        _buildHeader: function () {
            var header = this._header = new Container();

            header.addClass('header');
            this._pageContainer.appendChildWidget(header);
        },

        /**
         * Builds the title.
         *
         * @private
         */
        _buildTitle: function () {
            var title = this._title = new Label({ text: l10n.get('replay.title'), classNames: ['top-title'] });

            this._header.appendChildWidget(title);
        },

        /**
         * Builds the subtitle.
         *
         * @private
         */
        _buildSubtitle: function () {
            var subtitle = new Label({ text: l10n.get('replay.subtitle'), classNames: ['main-subtitle'] });

            this._header.appendChildWidget(subtitle);
        },

        /**
         * Builds the clock.
         *
         * @private
         */
        _buildClock: function () {
            var clock = this._clock = new Clock();

            clock.addClass('replay-clock');
            this._header.appendChildWidget(clock);
        },

        /**
         * Builds the filters list.
         *
         * @private
         */
        _buildFiltersList: function () {
            var genreFilters = this._genreFilters = new HorizontalList(),
                genres = ['news', 'entertainment', 'series', 'films', 'kids', 'crime', 'sport', 'info', 'culture', 'music', 'erotic'],
                i;

            genreFilters.addClass('filters');

            for (i = 0; i < genres.length; i++) {

                // Generate buttons
                genres[i] = new FilterButton(l10n.get('replay.filters.' + i), genres[i]);

                genres[i].addClass('filterbtn');
                this._genreFilters.appendChildWidget(genres[i]);
            }

            this._header.appendChildWidget(genreFilters);
        },

        /**
         * Builds the grid.
         *
         * @private
         */
        _buildGrid: function () {
            var grid = this._grid = new MasonryGrid({
                columns: 4,
                types: [
                    {
                        class: 'landscape',
                        height: layout.asset.landscape,
                        percentage: 60,
                        formatter: new LandscapeAssetFormatter()
                    },
                    {
                        class: 'portrait',
                        height: layout.asset.portrait,
                        percentage: 40,
                        formatter: new PortraitAssetFormatter()
                    }
                ]
            });

            this._pageContainer.appendChildWidget(grid);
        },

        /**
         * BeforeRender event.
         */
        onBeforeRender: function () {
            this._loadingTime = application.getDate();
        },

        /**
         * BeforeShow event.
         *
         * @param {Object} e - The event data.
         */
        onBeforeShow: function (e) {
            var firstBtn = this._genreFilters.getChildWidget('news'),
                grid = this._grid,
                startTime = new Date(application.getDate() - 1000 * 60 * 60 * 24 * 7), // 7 days ago
                endTime = application.getDate();

            firstBtn.addClass('selected');
            firstBtn.focus();
            if (!e.fromBack) {
                startTime.setHours(0);
                startTime.setMinutes(0);
                startTime.setSeconds(0);
                startTime.setMilliseconds(0);
                startTime = Date.parse(startTime);
                endTime = Date.parse(endTime);

                grid.empty();
                grid.setStyleTo('left', layout.scrolling.left + 'px');
                grid.setStyleTo('top', layout.scrolling.top + 'px');
                if (this._newFilter) {
                    this._newFilter.removeClass('selected');

                    this._newFilter = firstBtn;
                }
                firstBtn.addClass('selected');
                firstBtn.focus();

                this._loadData('', startTime, endTime, filters.news.value)
                    .then(Utils.bind(this._setData, this))
                    .then(Utils.bind(this._setEventListeners, this));
            }
            this._startReplayPage = application.getDate();
            GA.onPageView('replaytv');
        },

        /**
         * BeforeHide event.
         */
        onBeforeHide: function () {
            var duration = ((application.getDate() - this._startReplayPage) / 60000).toFixed(2);

            this._removeEventListeners();
            GA.onEvent('page', 'time', {
                eventLabel: 'replaytv',
                eventValue: duration
            });
        },

        /**
         * Attempts to set the data.
         *
         * @param {Array} data - The data.
         * @private
         */
        _setData: function (data) {
            var grid = this._grid,
                value = ((application.getDate() - this._loadingTime) / 1000).toFixed(2);

            grid.setStyleTo('top', layout.scrolling.top + 'px');
            grid.setDataItem(data);
            application.hideLoader();

            if (!this._genreFilters.isFocussed()) {
                this._grid.getChildWidgetByIndex(0).focus();
            }

            GA.onEvent('page', 'load', {
                eventLabel: 'replay',
                eventValue: value
            });
        },

        /**
         * Attempts to load the data for a given date.
         *
         * @param {string} query - Query.
         * @param {Date} startTime - Start time.
         * @param {Date} endTime - End time.
         * @param {string} genre - The genre of the item.
         * @returns {Promise} - Promise resolving with data.
         * @private
         */
        _loadData: function (query, startTime, endTime, genre) {
            application.showLoader(true);
            return searchManager.search(query, startTime, endTime, genre)
                .then(Utils.bind(this._randomiseData, this, startTime, endTime, genre))
                ['catch'](function () {
                application.route('error', {
                    type: 'fullscreen',
                    title: L10N.getInstance().get('errors.cannot_load_page'),
                    button: {id: 'error-close-button', label: L10N.getInstance().get('errors.close')},
                    imgUrl: 'src/assets/images/error-icon.png'
                });
            });
        },

        /**
         * Attempts to load the data for a given genres if multiple for one category.
         *
         * @param {string} query - Query.
         * @param {Date} startTime - Start time.
         * @param {Date} endTime - End time.
         * @param {Array} genres - The genre of the item.
         * @returns {Array} - The randomised data.
         * @private
         */
        _connectDataForMultipleGenres: function (query, startTime, endTime, genres) {
            var multipleData = [],
                promises = [],
                i;

            application.showLoader(true);
            for (i = 0; i < genres.length; i++) {
                promises.push(searchManager.search(query, startTime, endTime, genres[i]));
            }

            return Promise.all(promises)
                .then(function (results) {

                    Utils.each(results, function (result) {
                        Utils.each(result, function (item) {
                            multipleData.push(item);
                        });
                    });

                    return multipleData;
                });
        },

        /**
         * Attempts to load the data for a given date.
         *
         * @param {string} query - Query.
         * @param {Date} startTime - Start time.
         * @param {Date} endTime - End time.
         * @param {Array} genres - The genre of the item.
         * @private
         */
        _getDataForMultipleGenres: function (query, startTime, endTime, genres) {
            this._connectDataForMultipleGenres(query, startTime, endTime, genres)
                .then(Utils.bind(this._randomiseData, this, startTime, endTime, genres))
                .then(Utils.bind(this._setData, this));
        },

        /**
         * Randomises the given data and only returns the maximum amount of items.
         *
         * @param {Date} startTime - The start time.
         * @param {Date} endTime - The end time.
         * @param {string} genre - The genre.
         * @param {Array} data - The data.
         * @returns {Array} - The randomised data.
         * @private
         */
        _randomiseData: function (startTime, endTime, genre, data) {
            var combined = [],
                finalArray = [],
                i;

            Utils.each(data, function (items) {
                combined = combined.concat(items);
            });

            AppUtils.randomiseArray(combined);

            for (i = 0; i < combined.length; i++) {
                finalArray.push(combined[i]);

                if (finalArray.length >= MAX_ITEMS) {
                    break;
                }
            }
            return finalArray;
        },

        /**
         * Sets the event listeners.
         *
         * @private
         */
        _setEventListeners: function () {
            this.addEventListener('keydown', this._onKeyDownBound);
            this._genreFilters.addEventListener('select', this._onSelectBound);
            this._grid.addEventListener('select', this._onSelectAssetsBound);
            this._grid.addEventListener('selecteditemchange', this._onSelectedItemChangeBound);
        },

        /**
         * Removes the event listeners.
         *
         * @private
         */
        _removeEventListeners: function () {
            this.removeEventListener('keydown', this._onKeyDownBound);
            this._genreFilters.removeEventListener('select', this._onSelectBound);
            this._grid.removeEventListener('select', this._onSelectAssetsBound);
            this._grid.removeEventListener('selecteditemchange', this._onSelectedItemChangeBound);
        },

        /**
         * KeyDown event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onKeyDown: function (e) {
            var activeAssetIndex = this._grid.getActiveChildIndex();

            switch (e.keyCode) {
                case KeyEvent.VK_LEFT:
                    e.stopPropagation();
                    e.preventDefault();
                    application.focusMenu('main');
                    break;
                case KeyEvent.VK_RIGHT:
                    e.preventDefault();
                    e.stopPropagation();
                    break;
                case KeyEvent.VK_BACK:
                    if (activeAssetIndex !== 0) {
                        this._grid.alignToTop();
                    } else {
                        this._onClose();
                        e.preventDefault();
                        e.stopPropagation();
                    }
                    break;
            }
        },

        /**
         * Gets executed when the.
         *
         * @private
         */
        _onClose: function () {
            this._grid.dispose();

            // Go back and focus the player.
            this.parentWidget.back();
            application.getComponent('player').focus();
        },

        /**
         * Get filtered data.
         *
         * @param {string} query - The genre of the item.
         * @param {Date} startTime - Start time.
         * @param {Date} endTime - End time.
         * @param {string} genre - The genre of the item.
         * @private
         */
        _getFilteredData: function (query, startTime, endTime, genre) {
            this._loadData(query, startTime, endTime, genre)
                .then(Utils.bind(this._setData, this));
        },

        /**
         * Select event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onSelect: function (e) {
            var id = e.target.id,
                films = this._genreFilters.getChildWidgetByIndex(0),
                startTime = new Date(application.getDate() - 1000 * 60 * 60 * 24 * 7), // 7 days ago
                endTime = application.getDate(),
                currentFilter = this._newFilter || films;

            startTime.setHours(0);
            startTime.setMinutes(0);
            startTime.setSeconds(0);
            startTime.setMilliseconds(0);
            startTime = Date.parse(startTime);
            endTime = Date.parse(endTime);

            currentFilter.removeClass('selected');
            e.target.addClass('selected');

            switch (id) {
                case 'films':
                    this._getFilteredData('', startTime, endTime, filters.films.value);
                    break;
                case 'series':
                    this._getFilteredData('', startTime, endTime, filters.series.value);
                    break;
                case 'crime':
                    this._getFilteredData('', startTime, endTime, filters.crime.value);
                    break;
                case 'kids':
                    this._getFilteredData('', startTime, endTime, filters.kids.value);
                    break;
                case 'sport':
                    this._getDataForMultipleGenres('', startTime, endTime, filters.sport.value);
                    break;
                case 'news':
                    this._getFilteredData('', startTime, endTime, filters.news.value);
                    break;
                case 'info':
                    this._getDataForMultipleGenres('', startTime, endTime, filters.info.value);
                    break;
                case 'culture':
                    this._getDataForMultipleGenres('', startTime, endTime, filters.culture.value);
                    break;
                case 'entertainment':
                    this._getFilteredData('', startTime, endTime, filters.entertainment.value);
                    break;
                case 'music':
                    this._getFilteredData('', startTime, endTime, filters.music.value);
                    break;
                case 'erotic':
                    this._getFilteredData('', startTime, endTime, filters.erotic.value);
                    break;
            }
            this._newFilter = e.target;
        },

        /**
         * Select assets event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onSelectAssets: function (e) {
            var dataItem;

            if (e.target.hasClass('asset')) {
                dataItem = e.target.getDataItem();
                application.route('epgdetail', {
                    data: dataItem,
                    callback: Utils.bind(self.focus, self),
                    callingPage: 'replaytv'
                });
            }
        },

        /**
         * Change elements positon on selected item change.
         *
         * @param {Object} e - The selected item change event.
         * @private
         */
        _onSelectedItemChange: function (e) {
            var target = e.target,
                activeTarget = target.getActiveChildWidget();

            // Verticallist is the grid that scrolls
            if (target.hasClass('verticallist')) {
                this.align(activeTarget);
            } else if (target.hasClass('masonrygrid')) {

                if (activeTarget) {
                    this.align(activeTarget.getActiveChildWidget());
                }
            }
        },

        /**
         * Aligns the component based on the given item.
         *
         * @param {Object} item - The item.
         */
        align: function (item) {
            var listElement = this._grid.outputElement,
                scrollingLayout = layout.scrolling,
                windowHeight = requiredScreenSize.height,
                listClientRect,
                listTop,
                left,
                maxBottomPosition,
                dimensions,
                perfectTop,
                difference,
                newTop;

            if (!item || !item.outputElement) {
                return;
            }

            listClientRect = listElement.getBoundingClientRect();
            listTop = listClientRect.top;
            left = scrollingLayout.left;
            maxBottomPosition = -(listClientRect.height) + layout.height - scrollingLayout.fromBottom;

            dimensions = item.outputElement.getBoundingClientRect();
            perfectTop = (windowHeight - dimensions.height) / 2;
            difference = dimensions.top - perfectTop;

            if (difference > 0) {
                newTop = listTop - difference;
            } else {
                newTop = listTop + -difference;
            }

            // -23px - Value on which newTop should be settet as 186px,
            // which is the header height(default grid top position)
            if (newTop >= scrollingLayout.fromTop) {

                newTop = scrollingLayout.top;
            }

            // Small differences are caused by the scaling of the widget, ignore movement.
            if (newTop === listTop
                || newTop - listTop >= -1 && newTop - listTop <= 1) {
                return;
            }

            // If we reached the bottom, stop scrolling.
            if (newTop < maxBottomPosition) {
                newTop = maxBottomPosition;
            }

            device.moveElementTo({
                el: listElement,
                skipAnim: false,
                duration: 300,
                fps: 60,
                to: {
                    left: left,
                    top: newTop
                }
            });
        }
    });
});