Source: components/search.js

define('application/components/search', [
    'rofl/widgets/component',
    'rofl/widgets/label',
    'rofl/widgets/container',
    'rofl/lib/l10n',
    'antie/runtimecontext',
    'application/widgets/search/input',
    'application/widgets/search/decorator',
    'application/managers/search',
    'rofl/lib/utils',
    'rofl/events/keyevent',
    'application/widgets/search/contentList',
    'rofl/widgets/horizontallist',
    'application/widgets/infoblock',
    'application/formatters/vodasset',
    'application/formatters/homeepgasset',
    'rofl/analytics/web/google',
    'application/managers/halo'
], function (
    Component,
    Label,
    Container,
    L10N,
    RuntimeContext,
    Input,
    Decorator,
    SearchManager,
    Utils,
    KeyEvent,
    ContentList,
    HorizontalList,
    InfoBlock,
    VodFormatter,
    EpgFormatter,
    GoogleAnalytics,
    HaloManager
) {
    'use strict';

    var l10n = L10N.getInstance(),
        device = RuntimeContext.getDevice(),
        application = RuntimeContext.getCurrentApplication(),
        searchManager = SearchManager.getInstance(),
        Search,
        GA = GoogleAnalytics.getInstance(),
        layout = application.getLayout(),
        inputConfig = {
            inputField: {
                opts: {
                    placeholder: l10n.get('search.input.placeholder'),
                    validateLength: true
                }
            }
        },
        infoblockConfig = {
            id: 'back-on-top-block',
            text: l10n.get('infoblock'),
            classname: ['icon', 'icon-back-v2'],
            position: 'right'
        },
        EVENT = {
            userSearched: 'UserSearched',
            userClickedOnSearchResult: 'UserClickedOnSearchResult'
        },
        GALabels = {
            liveTV: 'LiveTV'
        },
        CONTENT_TYPES = {
            VOD: 'VOD',
            SERIES: 'GROUP_OF_BUNDLES',
            LIVE: 'PROGRAM'
        };

    Search = Component.extend({

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

            this._onSelectBound = Utils.bind(this._onSelect, this);
            this._onKeyDownBound = Utils.bind(this._onKeyDown, this);
            this._onTextChangeBound = Utils.bind(this._onTextChange, this);
            this._onSelectedItemChangeBound = Utils.bind(this._onSelectedItemChange, this);
            this._lockAssetsBound = Utils.bind(this._lockAssets, this);
        },

        /**
         * Builds the component.
         *
         * @private
         */
        _build: function () {
            this._buildPageHeader();
            this._buildList();
            this._buildInput();
            this._buildDecorator();
            this._buildContentList();
            this._buildInfoBlock();
        },

        /**
         * Handle KeyEvent.
         *
         * @param {KeyEvent} e - KeyEvent instance.
         * @private
         */
        _onKeyDown: function (e) {
            var contentList = this._contentList,
                activeRowNumber = contentList.getActiveChildWidgetIndex() + 1;

            switch (e.keyCode) {
                case KeyEvent.VK_LEFT:
                    e.stopPropagation();

                    application.focusMenu('main');
                    break;
                case KeyEvent.VK_RIGHT:
                    e.stopPropagation();
                    break;
                case KeyEvent.VK_BACK:
                    if (activeRowNumber >= 3 && contentList.isFocussed()) {
                        this._backToTop();
                    } else {
                        this._onClose();
                    }
                    break;

                // no default
            }
        },

        /**
         * Builds the page header.
         *
         * @private
         */
        _buildPageHeader: function () {
            var headerContainer = new Container('top-header'),
                branding = this._branding = new Container(),
                pageTitle = new Label({ text: l10n.get('search.title'), classNames: ['top-title'] });

            branding.addClass('page-branding');
            branding.addClass('title');
            branding.appendChildWidget(pageTitle);
            headerContainer.appendChildWidget(branding);

            this.appendChildWidget(headerContainer);
        },

        /**
         * Builds the search list layout.
         *
         * @private
         */
        _buildList: function () {
            var list = this._list = new HorizontalList('search-layout'),
                inputContent = this._inputContent = new Container('input-content'),
                searchContent = this._searchContent = new Container('search-content');

            list.appendChildWidget(inputContent);
            list.appendChildWidget(searchContent);

            this.appendChildWidget(list);
        },

        /**
         * Builds the input widget.
         *
         * @private
         */
        _buildInput: function () {
            var input = this._input = new Input(inputConfig);

            this._inputContent.appendChildWidget(input);
        },

        /**
         * Builds the decorator widget.
         *
         * @private
         */
        _buildDecorator: function () {
            var decorator = this._decorator = new Decorator();

            this._searchContent.appendChildWidget(decorator);
        },

        /**
         * Builds the grid.
         *
         * @private
         */
        _buildContentList: function () {
            var contentList = this._contentList = new ContentList({
                height: layout.search.contentList.height,
                visibleItems: 3
            });

            this._searchContent.appendChildWidget(contentList);
        },

        /**
         * Builds the infoblock.
         *
         * @private
         */
        _buildInfoBlock: function () {
            var infoblock = this._infoblock = new InfoBlock(infoblockConfig);

            infoblock.addClass('info-block');

            this.appendChildWidget(infoblock);
        },

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

        /**
         * BeforeShow event.
         *
         * @param {Event} e - Event.
         */
        onBeforeShow: function (e) {
            var contentList = this._contentList,
                input = this._input,
                keyboard = input.getKeyboard();

            this._input.getInputField().showCursorBlink();
            this._startSearchPage = application.getDate();
            GA.onPageView('search');
            this._infoblock.hide({skipAnim: true});
            this._showLoader(true);
            this._input.focus();
            keyboard.setStyleTo('display', 'block');
            this._infoblock.hide();
            this._decorator.hide({
                skipAnim: true
            });

            if (!e.fromBack) {

                // Set input and title to their initial positions.
                input.reset();
            }

            contentList.removeChildWidgets();
            searchManager.searchPlaceholders()
                .then(Utils.bind(this._setPlaceHolderCarousels, this));

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

        /**
         * Attempts to set the placeholder results.
         *
         * @param {Array} placeholderData - The placeholder results.
         * @private
         */
        _setPlaceHolderCarousels: function (placeholderData) {
            var trending = placeholderData[0],
                latest = placeholderData[1],
                contentList = this._contentList;

            if (trending.data.length) {
                trending.carouselConfig = {
                    title: l10n.get('search.carousels.yesterday', {count: trending.data.length}),
                    visibleItems: 3,
                    layout: layout.search,
                    width: layout.search.carousel.width,
                    assetWidth: layout.asset.epg.width,
                    assetHeight: layout.asset.epg.height,
                    formatter: EpgFormatter,
                    pointerEnabled: true,
                    assetType: 'live'
                };
            }

            if (latest.data.length) {
                latest.carouselConfig = {
                    title: l10n.get('search.carousels.latest', {count: latest.data.length}),
                    visibleItems: 6,
                    layout: layout.search,
                    width: layout.search.carousel.width,
                    assetWidth: layout.asset.vod.width,
                    assetHeight: layout.asset.vod.height,
                    formatter: VodFormatter,
                    pointerEnabled: true,
                    assetType: 'vod'
                };
            }

            // Filter out data.
            placeholderData = Utils.filter(placeholderData, function (dataObj) {
                return dataObj.data.length !== 0;
            });

            if (placeholderData.length) {
                this.align(null);
                contentList.setContent(placeholderData);
                contentList.alignCarousels();

                contentList.show({
                    duration: 300
                });
            } else {
                this._decorator.show(this._decorator.DECORATOR_SEARCH);
            }

            this._showLoader(false);
        },

        /**
         * AfterShow event.
         */
        onAfterShow: function () {
            var inputField = this._input.getInputField(),
                value = ((application.getDate() - this._loadingTime) / 1000).toFixed(2);

            this._setEventListeners();
            inputField.animateFocusBar();

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

        /**
         * BeforeHide event.
         */
        onBeforeHide: function () {
            var inputField = this._input.getInputField(),
                duration;

            // Stop the current search query.
            this._input.getInputField().hideCursor();
            this._showLoader(false);
            clearTimeout(this._searchTimeout);

            this._removeEventListeners();
            inputField.stopCursorBlink();
            inputField.stopFocusbarAnimation();

            duration = ((application.getDate() - this._startSearchPage) / 60000).toFixed(2);
            GA.onEvent('page', 'time', {
                eventLabel: 'search',
                eventValue: duration
            });
        },

        /**
         * Sets the event listeners.
         *
         * @private
         */
        _setEventListeners: function () {
            this.addEventListener('textchange', this._onTextChangeBound);
            this._contentList.addEventListener('selecteditemchange', this._onSelectedItemChangeBound);
            application.addEventListener('$locked', this._lockAssetsBound);

            this._setSelectListener();
        },

        /**
         * Removes the select listener.
         *
         * @private
         */
        _setSelectListener: function () {

            if (this._listening) {
                return;
            }

            this._listening = true;
            this.addEventListener('select', this._onSelectBound);
            this.addEventListener('keydown', this._onKeyDownBound);
        },

        /**
         * Removes the select listener.
         *
         * @private
         */
        _removeSelectListener: function () {

            if (!this._listening) {
                return;
            }

            this._listening = false;
            this.removeEventListener('select', this._onSelectBound);
            this.removeEventListener('keydown', this._onKeyDownBound);
        },

        /**
         * Removes the event listeners.
         *
         * @private
         */
        _removeEventListeners: function () {
            this.removeEventListener('textchange', this._onTextChangeBound);
            this._contentList.removeEventListener('selecteditemchange', this._onSelectedItemChangeBound);
            application.removeEventListener('$locked', this._lockAssetsBound);

            this._removeSelectListener();
        },

        /**
         * Opens asset detail.
         *
         * @param {Object} data - Asset data.
         * @private
         */
        _openAssetDetail: function (data) {
            var detailRoute;

            switch (data.getContentType()) {
                case CONTENT_TYPES.VOD:
                    detailRoute = 'voddetail';
                    break;
                case CONTENT_TYPES.SERIES:
                    detailRoute = 'seriesdetail';
                    break;
                case CONTENT_TYPES.LIVE:
                    detailRoute = 'epgdetail';
                    break;
            }

            application.route(detailRoute, {
                data: data,
                callback: Utils.bind(this.focus, this),
                callingPage: 'search'
            });
        },

        /**
         * On select function.
         *
         * @param {event} e - Event passed on select.
         */
        _onSelect: function (e) {
            var target = e.target,
                keyboard = this._input.getKeyboard(),
                clearbutton = this._input.getClearButton(),
                activeRowNumber = this._contentList.getActiveChildWidgetIndex() + 1,
                innerKeyboard = this._input.getKeyboard().getKeyboard(),
                dataItem;

            switch (target.id) {
                case 'clearbutton':
                    searchManager.clearLastSearchedItems();
                    this._input.updateLastSearchedList();
                    clearbutton.setStyleTo('display', 'none');
                    clearbutton.setDisabled(true);
                    keyboard.focus();
                    break;
                case 'back-on-top-block':

                    if (activeRowNumber !== 1) {
                        innerKeyboard.setStyleTo('display', 'block');
                        innerKeyboard.focus();
                    } else {
                        this._onClose();
                    }
                    break;
            }

            if (target.hasClass('asset')) {
                dataItem = target.getDataItem().item;

                GA.onEvent('Action', EVENT.userClickedOnSearchResult, {eventLabel: GALabels.liveTV});

                if (target.isLocked()) {
                    this._removeSelectListener();
                    application.route('parentalpin', {
                        successCallback:
                            Utils.bind(function () {
                                this._addLastSearchedItem(dataItem.getTitle());
                                this._openAssetDetail(dataItem);
                                this._unlockAssets();
                                this._setSelectListener();
                            }, this),
                        errorCallback: Utils.bind(this._setSelectListener, this),
                        escapeCallback: Utils.bind(this._setSelectListener, this)
                    });
                } else {
                    this._addLastSearchedItem(dataItem.getTitle());
                    this._openAssetDetail(dataItem);
                }

            } else if (target.hasClass('last-searched')) {
                clearTimeout(this._searchTimeout);

                this._searchQuery = target.getLabel().getText();
                keyboard.setText(this._searchQuery);
            }
        },

        /**
         * Save searchquery in searchmanager.
         *
         * @param {string} title - Title of the asset.
         * @private
         */
        _addLastSearchedItem: function (title) {
            var input = this._input;

            searchManager.addLastSearchedItem(title);
            input.updateLastSearchedList();
        },

        /**
         * Unlock the assets.
         *
         * @private
         */
        _unlockAssets: function () {
            var placeholderCarousels = this._contentList,
                listContent = placeholderCarousels.getCarousels();

            Utils.each(listContent, function (carousel) {
               Utils.each(carousel.getChildWidgets(), function (asset) {
                  asset.unlock();
               });
            });
        },

        /**
         * Lock the assets.
         *
         * @private
         */
        _lockAssets: function () {
            var placeholderCarousels = this._contentList,
                listContent = placeholderCarousels.getCarousels();

            Utils.each(listContent, function (carousel) {
                Utils.each(carousel.getChildWidgets(), function (asset) {
                    asset.lock();
                });
            });
        },

        /**
         * Gets executed when the text changes.
         *
         * @param {Object} e - The current text.
         * @private
         */
        _onTextChange: function (e) {
            var text = e.text,
                textLength = text.length;

            clearTimeout(this._searchTimeout);
            this._searchQuery = text.replace(/&nbsp/g, ' ');

            if (textLength) {
                if (textLength >= 3) {

                    // Execute search.
                    this._showLoader(true);
                    this._decorator.hide();
                    this._searchTimeout = setTimeout(Utils.bind(this._executeSearch, this, this._searchQuery), 2000);
                } else {

                    // Hide contentlist carousels and show search placeholder screen
                    this._contentList.hide({
                        duration: 300,
                        onComplete: Utils.bind(this.removeChildWidgets, this._contentList)
                    });

                    this._decorator.show(this._decorator.DECORATOR_SEARCH);
                }

            } else {

                this._showLoader(true);

                this._contentList.removeChildWidgets();
                searchManager.searchPlaceholders()
                    .then(Utils.bind(this._setPlaceHolderCarousels, this));

                this._decorator.hide();
            }
        },

        /**
         * Executes a search request.
         *
         * @param {string} text - The search query.
         * @private
         */
        _executeSearch: function (text) {
            var now = application.getDate(),
                todayAndLaterStartTime = new Date(now), // +4 days.
                todayAndLaterEndTime = new Date(now + (1000 * 60 * 60 * 24 * 4)), // +4 days.
                catchupStartTime = new Date(now - (1000 * 60 * 60 * 24 * 6)), // -6 days.
                searchParams = {
                    todayAndLater: {},
                    catchup: {
                        endTime: now - (1000 * 60 * 15) // 15 minutes
                    },
                    query: text
                };

            // Set today at 00:00:00.
            todayAndLaterStartTime = this._modifyTime(todayAndLaterStartTime, 0, 0, 0, 0);

            // Set +4 days at 23:59:59.
            todayAndLaterEndTime = this._modifyTime(todayAndLaterEndTime, 23, 59, 59, 0);

            // Set -6 days at 00:00.
            catchupStartTime = this._modifyTime(catchupStartTime, 0, 0, 0, 0);

            searchParams.todayAndLater.startTime = Date.parse(todayAndLaterStartTime);
            searchParams.todayAndLater.endTime = Date.parse(todayAndLaterEndTime);
            searchParams.catchup.startTime = Date.parse(catchupStartTime);

            GA.onEvent('Action', EVENT.userSearched);

            searchManager.searchContent(searchParams)
                .then(Utils.bind(this._setSearchResults, this, text))
                ['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'
                });
            });
        },

        /**
         * Modify time for the given date.
         *
         * @param {Date} time - Date time.
         * @param {number} hh - Hours.
         * @param {number} mm - Minutes.
         * @param {number} ss - Seconds.
         * @param {number} ms - Milliseconds.
         * @returns {Date} - Modified time.
         */
        _modifyTime: function (time, hh, mm, ss, ms) {
            time.setHours(hh);
            time.setMinutes(mm);
            time.setSeconds(ss);
            time.setMilliseconds(ms);

            return time;
        },

        /**
         * Attempts to set the search results.
         *
         * @param {string} text - The search query.
         * @param {Array} result - The search results.
         * @private
         */
        _setSearchResults: function (text, result) {
            var contentList = this._contentList,
                liveAndLaterResult = result[0],
                catchupResult = result[1],
                vodResults = result[2];

            if (text !== this._searchQuery) {
                this._showLoader(false);
                return;
            }

            if (liveAndLaterResult.data.length) {
                liveAndLaterResult.carouselConfig = {
                    title: l10n.get('search.carousels.liveAndLater', {count: liveAndLaterResult.data.length}),
                    visibleItems: 3,
                    layout: layout.search,
                    width: layout.search.carousel.width,
                    assetWidth: layout.asset.epg.width,
                    assetHeight: layout.asset.epg.height,
                    formatter: EpgFormatter,
                    pointerEnabled: true,
                    assetType: 'live'
                };
            }

            if (catchupResult.data.length) {
                catchupResult.carouselConfig = {
                    title: l10n.get('search.carousels.catchup', {count: catchupResult.data.length}),
                    visibleItems: 3,
                    layout: layout.search,
                    width: layout.search.carousel.width,
                    assetWidth: layout.asset.epg.width,
                    assetHeight: layout.asset.epg.height,
                    formatter: EpgFormatter,
                    pointerEnabled: true,
                    assetType: 'live'
                };
            }

            if (vodResults.data.length) {
                vodResults.carouselConfig = {
                    title: l10n.get('search.carousels.vod', {count: vodResults.data.length}),
                    visibleItems: 6,
                    layout: layout.search,
                    width: layout.search.carousel.width,
                    assetWidth: layout.asset.vod.width,
                    assetHeight: layout.asset.vod.height,
                    formatter: VodFormatter,
                    pointerEnabled: true,
                    assetType: 'vod'
                };
            }

            // Filter out data.
            result = Utils.filter(result, function (dataObj) {
                return dataObj.data.length !== 0;
            });

            contentList.removeChildWidgets();
            this.align(null);
            contentList.setContent(result);
            contentList.alignCarousels();

            if (result.length) {
                contentList.show({
                    duration: 300
                });
            } else {
                this._decorator.show(this._decorator.DECORATOR_NO_RESULT, {
                    query: text
                });
            }

            this._showLoader(false);
        },

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

            if (target.hasClass('content-list') && target.getChildWidgetCount() > 2) {
                if (e.index >= 2) {
                    this.align(target.getActiveChildWidget());
                } else {
                    this.align(target.getActiveChildWidget());
                }
            }
        },

        /**
         * Aligns the component based on the given item.
         *
         * @param {Object} item - The item.
         */
        align: function (item) {
            var listElement = this._contentList.outputElement,
                listTop = listElement.getBoundingClientRect().top,
                windowHeight = layout.requiredScreenSize.height,
                scrollingLayout = layout.search.scrolling,
                newTopToAdd = scrollingLayout.newTopToAdd,
                left = scrollingLayout.left,
                activeRowIndex = this._contentList.getActiveChildWidgetIndex(),
                rowsAmount = this._contentList.getChildWidgetCount(),
                dimensions,
                perfectTop,
                difference,
                newTop;

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


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

                if (newTop > 0) {

                    newTop = scrollingLayout.top;
                }

                newTop += newTopToAdd;
            } else {

                // Reset content.
                newTop = 0;
            }

            if (activeRowIndex !== (rowsAmount - 1)) {
                device.moveElementTo({
                    el: listElement,
                    skipAnim: true,
                    to: {
                        top: 0,
                        left: left
                    }
                });
            } else {
                device.moveElementTo({
                    el: listElement,
                    skipAnim: true,
                    to: {
                        top: newTop,
                        left: left
                    }
                });
            }
        },

        /**
         * Aligns the carousels back to the top to the first carousel.
         */
        _backToTop: function () {
            var contentList = this._contentList;

            this.align(null);
            contentList.setActiveChildIndex(0);
            contentList.alignCarousels();
        },

        /**
         * Executes the close functionality.
         */
        _onClose: function () {

            // Go back and focus the player.
            application.focusMenu('main');
        },

        /**
         * Shows or hides loader.
         *
         * @param {boolean} show - True to show loader.
         */
        _showLoader: function (show) {
            var loader = application.getComponent('loader'),
                focusState = !show;

            if (show) {
                loader.setStylesTo({
                    'left': '26%'
                });
                application.showLoader(true);

                if (this._contentList) {
                    this._contentList.setFocusable(focusState);
                }
            } else {
                application.hideLoader(null, {
                    onComplete: function () {
                        loader.setStylesTo({
                            'left': '0'
                        });
                    }
                });
            }
        }
    });

    return Search;
});