Source: components/uibuilder.js

define('application/components/uibuilder', [
    'rofl/widgets/component',
    'rofl/lib/utils',
    'rofl/lib/l10n',
    'application/widgets/uibuilder/hero',
    'application/widgets/uibuilder/carousel',
    'application/widgets/uibuilder/filter',
    'application/widgets/uibuilder/herocarousel',
    'application/widgets/uibuilder/heroslider',
    'application/widgets/clock',
    'rofl/widgets/verticallist',
    'antie/widgets/horizontallist',
    'rofl/events/keyevent',
    'antie/runtimecontext',
    'rofl/widgets/label',
    'rofl/widgets/container',
    'application/formatters/vodasset',
    'application/formatters/homeepgasset',
    'application/formatters/heroslider',
    'application/formatters/sports',
    'application/formatters/editorials',
    'application/formatters/bookmarkasset',
    'rofl/analytics/web/google',
    'application/managers/halo'
], function (
    Component,
    Utils,
    L10N,
    Hero,
    Carousel,
    Filter,
    HeroCarousel,
    HeroSlider,
    Clock,
    VerticalList,
    HorizontalList,
    KeyEvent,
    RuntimeContext,
    Label,
    Container,
    VodFormatter,
    EpgFormatter,
    HeroSliderFormatter,
    SportsFormatter,
    EditorialFormatter,
    BookmarkFormatter,
    GoogleAnalytics,
    HaloManager
) {
    'use strict';

    var application = RuntimeContext.getCurrentApplication(),
        l10n = L10N.getInstance(),
        GA = GoogleAnalytics.getInstance(),
        UPDATE_CAROUSEL_INTERVAL_TIME = 120000, // 2 minutes.
        PROGRESS_INTERVAL_TIME = 5000,
        UIBuilderComponent,
        WIDGET_TYPES = {
            EDITORIAL_BLOCK: 'editorial',
            HERO: 'hero',
            HERO_SLIDER: 'heroSlider',
            CAROUSEL: 'carousel',
            FILTERED_RESULT: 'filteredResult'
        },
        layout = application.getLayout(),
        VOD_CAROUSEL_PARAMS = {
            formatter: VodFormatter,
            layout: layout.asset.vod,
            numberOfVisibleItemsOnScreen: 8,
            watchAll: {
                classNames: []
            }
        },
        EPG_CAROUSEL_PARAMS = {
            formatter: EpgFormatter,
            layout: layout.asset.epg,
            numberOfVisibleItemsOnScreen: 4,
            watchAll: {
                classNames: ['landscape-watchall']
            }
        },
        BOOKMARK_CAROUSEL_PARAMS = {
            formatter: BookmarkFormatter,
            layout: layout.asset.epg,
            numberOfVisibleItemsOnScreen: 4,
            watchAll: {
                classNames: ['landscape-watchall']
            }
        },
        VOD_GRID = {
            grid: {
                columns: 8,
                width: layout.requiredScreenSize.width,
                height: layout.watchall.gridHeight,
                alignPoint: 0,
                culling: true,
                continuousListener: true,
                navigateNext: true,
                animOptions: {
                    easing: 'easeInOut',
                    fps: 60,
                    duration: 200,
                    skipAnim: false
                }
            },
            asset: {
                width: layout.asset.vod.width,
                height: layout.asset.vod.height,
                formatter: VodFormatter
            }
        },
        EPG_GRID = {
            grid: {
                columns: 4,
                width: layout.requiredScreenSize.width,
                height: layout.watchall.gridHeight,
                alignPoint: 0,
                culling: true,
                continuousListener: true,
                navigateNext: true,
                animOptions: {
                    easing: 'easeInOut',
                    fps: 60,
                    duration: 200,
                    skipAnim: false
                }
            },
            asset: {
                width: layout.asset.epg.width,
                height: layout.asset.epg.height,
                formatter: EpgFormatter
            }
        },
        BOOKMARK_GRID = {
            grid: {
                columns: 4,
                width: layout.requiredScreenSize.width,
                height: layout.watchall.gridHeight,
                alignPoint: 0,
                culling: true,
                continuousListener: true,
                navigateNext: true,
                animOptions: {
                    easing: 'easeInOut',
                    fps: 60,
                    duration: 200,
                    skipAnim: false
                }
            },
            asset: {
                width: layout.asset.epg.width,
                height: layout.asset.epg.height,
                formatter: BookmarkFormatter
            }
        },
        WIDGETS_PARAMS = {
            home: {
                hero: {
                    id: 'hero',
                    formatter: HeroSliderFormatter,
                    numberOfVisibleItemsOnScreen: 4,
                    layout: layout.home.slider
                },
                carousels: {
                    vod: VOD_CAROUSEL_PARAMS,
                    epg: EPG_CAROUSEL_PARAMS,
                    bookmark: BOOKMARK_CAROUSEL_PARAMS
                },
                grid: {
                    vod: VOD_GRID,
                    epg: EPG_GRID,
                    bookmark: BOOKMARK_GRID
                }
            },
            movies: {
                hero: {
                    id: 'hero',
                    formatter: HeroSliderFormatter,
                    numberOfVisibleItemsOnScreen: 4,
                    layout: layout.home.slider
                },
                carousels: {
                    vod: VOD_CAROUSEL_PARAMS
                },
                grid: {
                    vod: VOD_GRID
                }
            },
            series: {
                hero: {
                    id: 'hero',
                    formatter: HeroSliderFormatter,
                    numberOfVisibleItemsOnScreen: 4,
                    layout: layout.home.slider
                },
                carousels: {
                    vod: VOD_CAROUSEL_PARAMS
                },
                grid: {
                    vod: VOD_GRID
                }
            },
            sports: {
                editorialBlocks: {
                    id: 'hero',
                    formatter: EditorialFormatter,
                    numberOfVisibleItemsOnScreen: 4,
                    layout: layout.home.slider
                },
                carousels: {
                    epg: EPG_CAROUSEL_PARAMS
                },
                grid: {
                    epg: EPG_GRID
                }
            }
        };

    UIBuilderComponent = Component.extend({

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

            this.addClass('page');
            this._buildFiltersContainer();
            this._buildGenreFilters();

            this._build();
            this._onKeyDownBound = Utils.bind(this._onKeyDown, this);
            this._onToplistFocusBound = Utils.bind(this._onToplistFocus, this);

            this._activeFilters = {};

            this._onSelectedItemChangeBound = Utils.bind(this._onSelectedItemChange, this);
            this._onSelectBound = Utils.bind(this._onSelect, this);
            this._unlockAssetsBound = Utils.bind(this._unlockAssets, this);
            this._lockAssetsBound = Utils.bind(this._lockAssets, this);
        },

        /**
         * Builds the page layout.
         *
         * @private
         */
        _build: function () {
            this._list = this.appendChildWidget(new VerticalList());
            this._topList = this._list.appendChildWidget(new HorizontalList());
            this._widgetList = this._list.appendChildWidget(new VerticalList());

            this._widgetList.addClass('widget-list');
        },

        /**
         * Builds the filters container.
         *
         * @private
         */
        _buildFiltersContainer: function () {
            var filtersContainer = this._filterscontainer = new HorizontalList();

            filtersContainer.addClass('filters-container');
            filtersContainer.addClass('filters');
        },

        /**
         * Builds genre filters.
         *
         * @private
         */
        _buildGenreFilters: function () {
            var genreFiltersButton = this._genrefilter = new Filter({
                text: l10n.get('genre.all'),
                id: 'filter-genre-btn',
                isDropDown: true,
                classList: 'icon-button',
                type: 'genre'
            }),

            commonFiltersButton = this._commonfilter = new Filter({
                text: l10n.get('filter.filter'),
                id: 'filter-filter-btn',
                isDropDown: true,
                classList: 'icon-button',
                type: 'filter'
            }),

            sortingFiltersButton = this._sortfilter = new Filter({
                text: l10n.get('sort.all'),
                id: 'filter-sort-btn',
                isDropDown: true,
                classList: 'icon-button',
                type: 'sort'
            });

            this._filterscontainer.appendChildWidget(genreFiltersButton);
            this._filterscontainer.appendChildWidget(commonFiltersButton);
            this._filterscontainer.appendChildWidget(sortingFiltersButton);
        },

        /**
         * Builds the title.
         *
         * @param {Object} params - Title params.
         * @param {Object} params.label.text - Label text.
         * @param {Array} params.label.classNames - Label class names..
         * @private
         */
        _buildTitle: function (params) {
            var titleParams = params || {},
                title = this._title = new Label(titleParams.label),
                branding = this._branding = new Container();

            branding.addClass('page-branding');
            branding.addClass('page-title');
            branding.appendChildWidget(title);
            this._header.appendChildWidget(branding);
        },

        /**
         * BeforeRender event.
         */
        onBeforeRender: function () {
            this.addEventListener('keydown', this._onKeyDownBound);
            this._topList.addEventListener('keydown', this._onKeyDownBound);
            this._topList.addEventListener('focus', this._onToplistFocusBound);
            this.addEventListener('selecteditemchange', this._onSelectedItemChangeBound);
            this.addEventListener('select', this._onSelectBound);
            application.addEventListener('$unlocked', this._unlockAssetsBound);
            application.addEventListener('$locked', this._lockAssetsBound);

            HaloManager.getInstance()
                .getServiceMessage()
                .then(function (r) {
                    if (r) {
                        application.route('service');
                    }
                });
        },

        /**
         * BeforeHide event.
         */
        onBeforeHide: function () {
            this.removeEventListener('keydown', this._onKeyDownBound);
            this._topList.removeEventListener('keydown', this._onKeyDownBound);
            this._topList.removeEventListener('focus', this._onToplistFocusBound);
            this.removeEventListener('selecteditemchange', this._onSelectedItemChangeBound);
            this.removeEventListener('select', this._onSelectBound);
            application.removeEventListener('$unlocked', this._unlockAssetsBound);
            application.removeEventListener('$locked', this._lockAssetsBound);
            this._clearProgressInterval();
            this._clearUpdateCarouselsInterval();

            this._grid = null;
        },

        /**
         * AddFilterWidget to the page layout event.
         */
        _addFilterWidget: function () {
            this._topList.appendChildWidget(this._filterscontainer);
        },

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

            clock.addClass('header-clock');
            this._topList.appendChildWidget(clock);
        },

        /**
         * Toplist focus event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onToplistFocus: function (e) {

            if (e.target === this._hero) {
                if (!this._hero.isVisible()) {
                    this._hero.show();

                    e.index = 0;
                    e.item = e.target;
                    this._scroll(e, true, true);
                    this._widgetList.setActiveChildIndex(0);

                    if (this._grid) {
                        this._grid.alignToFirstItem();
                    }
                }

                if (this._grid && this._grid.getActiveChildIndex() > 0) {
                    this._grid.alignToFirstItem();
                }
            }
        },

        /**
         * KeyDown event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onKeyDown: function (e) {
            var filtersContainerItem;

            switch (e.keyCode) {
                case KeyEvent.VK_BACK:
                    this._onBack();
                    break;
                case KeyEvent.VK_LEFT:
                    e.preventDefault();
                    e.stopPropagation();
                    application.focusMenu('main');
                    break;
                case KeyEvent.VK_RIGHT:
                    e.preventDefault();
                    e.stopPropagation();
                    break;
                case KeyEvent.VK_DOWN:
                    if (this._filterscontainer && this._filterscontainer.isFocussed()) {
                        if (this.heroFound) {
                            this._hero.focusAction(this._hero.getButtonCount() - 1);
                            if (!this._hero.isVisible()) {
                                this._widgetList.focus();
                                if (this._grid) {
                                    this._grid.alignToActiveIndex();
                                }
                            }

                            e.preventDefault();
                            e.stopPropagation();
                        } else if (this._grid) {
                            this._widgetList.focus();
                            this._grid.alignToActiveIndex();
                        }
                    }
                    break;
                case KeyEvent.VK_UP:
                    if (this.heroFound && this._hero.isFocussed()) {
                        this._hero.setButtonActiveChildIndex(this._hero.getButtonCount() - 1);

                        filtersContainerItem = this._filterscontainer && this._filterscontainer.getChildWidgetByIndex(0);

                        if (filtersContainerItem) {
                            filtersContainerItem.focus();
                        }

                        e.preventDefault();
                        e.stopPropagation();
                    }
                    break;
            }
        },

        /**
         * Builds the widgets and appends them to the component.
         *
         * @param {Array} widgets - The widgets.
         */
        buildWidgets: function (widgets) {
            this.heroFound = false;

            Utils.each(widgets, function (widget) {

                this.buildWidget(widget);
            }, this);

            if (!this.heroFound) {
                this.addClass('no-hero-item');
                this._list.getChildWidgetByIndex(0).focus();
            } else {
                if (this.hasClass('no-hero-item')) {
                    this.removeClass('no-hero-item');
                }
            }
        },

        /**
         * Builds the individual widget.
         *
         * @param {Object} widget - The widget. See WIDGET_TYPES for available widgets.
         */
        buildWidget: function (widget) {
            var list = this._widgetList,
                widgetsParams = WIDGETS_PARAMS[this.pageId],
                editorialBlocks,
                sliderItems,
                carouselsWidgetParams,
                widgetLayout;

            switch (widget.getWidgetType()) {
                case WIDGET_TYPES.HERO:
                    if (widget._item && widget.hasRights() && !this.heroFound) {
                        if (!widget._item.isLocked()) {
                            this._hero = new Hero(widget, widgetsParams.hero);
                            this._topList.insertChildWidget(0, this._hero);
                            this.heroFound = true;
                        }
                    }
                    break;
                case WIDGET_TYPES.CAROUSEL:
                    if (widget.getItems().length) {
                        widgetLayout = widget.getLayout();
                        carouselsWidgetParams = this._getWidgetParams(widgetLayout);

                        list.appendChildWidget(new Carousel(widget, carouselsWidgetParams));
                    }
                    break;
                case WIDGET_TYPES.EDITORIAL_BLOCK:
                    editorialBlocks = widget.getEditorialBlocks({hasContent: true});

                    if (editorialBlocks.length) {
                        this._hero = list
                            .appendChildWidget(new HeroSlider(editorialBlocks, widgetsParams.editorialBlocks));
                        this.heroFound = true;
                    }
                    break;
                case WIDGET_TYPES.HERO_SLIDER:
                    sliderItems = widget.getItems();

                    this._hero = list.appendChildWidget(new HeroSlider(sliderItems, widgetsParams.hero));
                    this.heroFound = true;
                    break;

                    // No default.
            }
        },

        /**
         * Returns the widgets params for the type of layout.
         *
         * @param {string} widgetLayout - The type of layout of the widget.
         * @returns {Object} - The widget's params.
         */
        _getWidgetParams: function (widgetLayout) {
            var widgetsParams = WIDGETS_PARAMS[this.pageId];

            switch (widgetLayout) {
                case 'EPG_LARGE':
                    return widgetsParams.carousels.epg;
                case 'VOD_SMALL':
                    return widgetsParams.carousels.vod;
                case 'BOOKMARK_LARGE_HORIZONTAL':
                    return widgetsParams.carousels.bookmark;
                default:
                    return widgetsParams.carousels.vod || widgetsParams.carousels.epg;
            }
        },

        /**
         * Aligns the carousels to force them to render items.
         */
        alignCarousels: function () {
            Utils.each(this._widgetList.getChildWidgets(), function (widget) {

                if (widget && widget instanceof Carousel) {
                    widget.alignToIndex(0);
                }
            });
        },

        alignCarouselsToLastIndex: function () {
            Utils.each(this._widgetList.getChildWidgets(), function (widget) {

                if (widget && widget instanceof Carousel) {
                    widget.alignToLastIndex();
                }
            });
        },

        /**
         * Removes all the widgets.
         */
        removeAll: function () {
            this._hero = null;
            this._list.removeChildWidgets();
            this._widgetList.removeChildWidgets();
            this._topList.removeChildWidgets();
            this._list.appendChildWidget(this._topList);
            this._list.appendChildWidget(this._widgetList);
        },

        /**
         * Returns true if the component has a hero element attached.
         *
         * @returns {boolean} - True if the component has a hero element attached.
         */
        hasHeroElement: function () {
            return this._hero !== null;
        },

        /**
         * Shows the Hero widget.
         */
        showHero: function () {
            var self = this;

            if (this.hasHeroElement()) {
                if (!this._hero.isVisible() || this._isHidingHero) {
                    this._isShowingHero = true;
                    this._hero.show({
                        duration: 500,
                        onComplete: function () {
                            self._isShowingHero = false;
                        }
                    });
                }
            }
        },

        /**
         * Hides the Hero widget.
         *
         * @param {boolean} skipAnimation - Whether to skip animation.
         */
        hideHero: function (skipAnimation) {
            var self = this;

            if (this.hasHeroElement()) {
                if (this._hero.isVisible() || this._isShowingHero) {
                    if (skipAnimation) {
                        this._hero.hide({skipAnim: true});
                    } else {
                        this._isHidingHero = true;

                        this._hero.hide({
                            duration: 300,
                            onComplete: function () {
                                self._isHidingHero = false;
                            }
                        });
                    }
                }
            }
        },

        /**
         * Routes to watch all module.
         *
         * @param {Promise} watchAllItem - The watchall item.
         */
        goToWatchAll: function (watchAllItem) {
            var widgetsParams = WIDGETS_PARAMS[this.pageId],
                watchAllEndpoint = watchAllItem.getWatchAll(),
                gridParams;

            if (watchAllItem.isEpgLayout()) {
                gridParams = widgetsParams.grid.epg;
            } else if (watchAllItem.isBookmarkLayout()) {
                gridParams = widgetsParams.grid.bookmark;
            } else {
                gridParams = widgetsParams.grid.vod;
            }

            application.route('watchall', {
                watchAll: watchAllEndpoint,
                gridParams: gridParams
            });
        },

        /**
         * Unlock the assets.
         *
         * @private
         */
        _unlockAssets: function () {
            var rows = this._widgetList.getChildWidgets(),
                assets;

            this._assetsUnlocked = true;
            Utils.each(rows, function (row) {
                if (row.getCarousel) {
                    assets = row.getCarousel().getChildWidgets();

                    Utils.each(assets, function (asset) {
                        if (asset.hasClass('asset')) {
                            asset.unlock();
                        }
                    });
                }
            });

            if (this._grid) {
                Utils.each(this._grid.getList().getChildWidgets(), function (row) {

                    Utils.each(row.getChildWidgets(), function (asset) {
                        if (asset.hasClass('asset')) {
                            asset.unlock();
                        }
                    });
                });
            }
        },

        /**
         * Lock the assets.
         *
         * @private
         */
        _lockAssets: function () {
            var rows = this._widgetList.getChildWidgets(),
                assets;

            this._assetsUnlocked = false;
            Utils.each(rows, function (row) {
                if (row.getCarousel) {
                    assets = row.getCarousel().getChildWidgets();

                    Utils.each(assets, function (asset) {
                        if (asset.hasClass('asset')) {
                            asset.lock();
                        }
                    });
                }
            });

            if (this._grid) {
                Utils.each(this._grid.getList().getChildWidgets(), function (row) {

                    Utils.each(row.getChildWidgets(), function (asset) {
                        if (asset.hasClass('asset')) {
                            asset.lock();
                        }
                    });
                });
            }
        },

        /**
         * Routes to the details page of the content.
         *
         * @param {Object} routeParams - The route params.
         */
        _routeToDetails: function (routeParams) {
            application.route('details', {
                data: routeParams.data,
                callback: Utils.bind(this.focus, this)
            });
        },

        /**
         *
         * @param {Object} routeParams - The player's route params.
         * @param {string} playerRoute - The player to be used (liveplayer, vodplayer, catchupplayer...).
         * @private
         */
        _routeToPlayer: function (routeParams, playerRoute) {
            application.route(playerRoute || 'player', routeParams);
            GA.onEvent('player', 'started', {eventLabel: this.pageId});
        },

        /**
         * Back event.
         *
         * @private
         */
        _onBack: function () {
            this.parentWidget.back();
            application.focusMenu('main');
        },

        /**
         * Starts the progressbars update interval.
         */
        _updateProgressbars: function () {
            var now = application.getDate(),
                rows = this._widgetList.getChildWidgets(),
                rowItems,
                currentProgramFinished,
                dataItem,
                hasProgressbar,
                updateableCarousel;

            if (this._progressInterval) {
                this._clearProgressInterval();

                Utils.each(rows, Utils.bind(function (row) {
                    updateableCarousel = row.getCarousel && !row.hasClass('hero') &&
                        this._isUpdateableCarousel(row.getCarouselLayout());

                    if (updateableCarousel) {
                        rowItems = row.getCarousel().getChildWidgets();

                        Utils.each(rowItems, Utils.bind(function (rowItem) {

                            if (rowItem.hasClass('asset')) {
                                hasProgressbar = rowItem.updateProgressBar();
                                dataItem = rowItem.getDataItem();

                                currentProgramFinished = now >= dataItem.endTime;

                                // A program has finished in the row, add it to the update list.
                                if (currentProgramFinished && hasProgressbar) {
                                    if (!this._updateRowsList) {
                                        this._updateRowsList = [];
                                    }

                                    if (!Utils.contains(this._updateRowsList, row)) {
                                        this._updateRowsList.push(row);
                                    }
                                }
                            }
                        }, this));
                    }
                }, this));
            }

            if (!this._updateCarouselsInterval) {
                this._updateCarouselsInterval = setInterval(Utils.bind(this._updateCarousels, this), UPDATE_CAROUSEL_INTERVAL_TIME);
            }

            this._progressInterval = setInterval(Utils.bind(this._updateProgressbars, this), PROGRESS_INTERVAL_TIME);
        },

        /**
         * Clears any progress interval update.
         *
         * @private
         */
        _clearProgressInterval: function () {
            clearInterval(this._progressInterval);
            this._progressInterval = null;
        },

        /**
         * Clears any progress interval update.
         *
         * @private
         */
        _clearUpdateCarouselsInterval: function () {
            clearInterval(this._updateCarouselsInterval);
            this._updateCarouselsInterval = null;
        },

        /**
         * Checks carousels with EPG layout that can be updated with new data.
         * i.e. live carousels in home or sports page.
         *
         * @param {string} carouselLayout - The carousel layout.
         * @returns {boolean} - True if the carousel can be updated.
         **/
        _isUpdateableCarousel: function (carouselLayout) {
            switch (carouselLayout) {
                case 'EPG_SPORT':

                    // Falls through.
                case 'EPG_LARGE':
                    return true;
                default:
                    return false;
            }
        },

        /**
         * Updates the carousels found on the update list.
         *
         * @private
         */
        _updateCarousels: function () {
            if (this._updateRowsList && this._updateRowsList.length) {
                this._clearProgressInterval();

                Utils.each(this._updateRowsList, Utils.bind(function (row) {
                    row.updateCarousel();
                    this._updateProgressbars();
                }, this));

                this._updateRowsList.length = 0;
            }
        }
    });

    UIBuilderComponent.WIDGET_TYPES = WIDGET_TYPES;

    return UIBuilderComponent;
});