define('application/components/guide', [
'rofl/widgets/component',
'rofl/widgets/container',
'rofl/widgets/label',
'rofl/widgets/button',
'rofl/widgets/image',
'rofl/lib/l10n',
'rofl/widgets/verticallist',
'application/widgets/clock',
'antie/runtimecontext',
'application/managers/epg',
'application/models/configuration',
'rofl/lib/utils',
'rofl/events/keyevent',
'application/widgets/guide/pointerhorizontalcontainer',
'application/utils',
'application/widgets/infoblock',
'application/widgets/guide/verticalcarousel',
'application/managers/channel',
'rofl/analytics/web/google',
'antie/widgets/horizontallist',
'application/widgets/guide/directdial',
'application/managers/genre',
'application/widgets/pointerfocusablebutton',
'application/widgets/filters/button',
'application/widgets/guide/filters/day',
'application/formatters/guide',
'application/managers/halo'
], function (
Component,
Container,
Label,
Button,
Image,
L10N,
VerticalList,
Clock,
RuntimeContext,
EpgManager,
KPNConfiguration,
Utils,
KeyEvent,
HorizontalCarousel,
AppUtils,
InfoBlock,
VerticalCarousel,
ChannelManager,
GoogleAnalytics,
HorizontalList,
DirectDial,
GenreManager,
PointerFocusableButton,
FilterButton,
DayFilterButton,
GuideFormatter,
HaloManager
) {
'use strict';
var Guide,
l10n = L10N.getInstance(),
application = RuntimeContext.getCurrentApplication(),
config = application.getConfiguration(),
epgManager = EpgManager.getInstance(),
channelManager = ChannelManager.getInstance(),
carouselsDimensions = application.getLayout().guide,
GA = GoogleAnalytics.getInstance(),
infoblockConfig = {
id: 'back-on-top-block',
text: l10n.get('infoblock'),
classname: ['icon', 'icon-back-v2'],
position: 'right'
},
DEFAULT_FILTER_BACK_DAYS = Utils.getNested(config, 'guide', 'backDays') || 6,
DEFAULT_FILTER_FORWARD_DAYS = Utils.getNested(config, 'guide', 'forwardDays') || 4,
guideFormatter = new GuideFormatter();
Guide = Component.extend({
/**
* Initialises the component.
*/
init: function init () {
init.base.call(this, 'guide');
this._filterTime = this._getCurrentTimeframeStart();
this._activeVerticalIndex = null;
this._build();
this._onKeyDownBound = Utils.bind(this._onKeyDown, this);
this._onSelectBound = Utils.bind(this._onSelect, this);
this._onFocusBound = Utils.bind(this._onFocus, this);
this._onSelectedItemChangeBound = Utils.bind(this._onSelectedItemChange, this);
this._unlockAssetsBound = Utils.bind(this._unlockAssets, this);
this._lockAssetsBound = Utils.bind(this._lockAssets, this);
this._onVisibilityChangeBound = Utils.bind(this._onVisibilityChange, this);
},
/**
* Builds the component.
*
* @private
*/
_build: function () {
this._buildPageContainer();
this._buildHeader();
this._buildGradient();
this._buildTitle();
this._buildSubtitle();
this._buildClock();
this._buildFiltersContainer();
this._buildDayFilters();
this._buildTimeFilters();
this._buildNowFilter();
this._buildGenreFilters();
this._buildVerticalCarousel();
this._buildInfoBlock();
this._buildDirectDial();
this._buildNoResultsMessage();
},
/**
* 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(),
branding = this._branding = new Container();
header.addClass('header');
branding.addClass('page-branding');
header.appendChildWidget(branding);
this._pageContainer.appendChildWidget(header);
},
/**
* Builds the gradient.
*
* @private
*/
_buildGradient: function () {
var gradient = this._gradient = new Container();
gradient.addClass('header-gradient');
this._header.appendChildWidget(gradient);
},
/**
* Builds the header.
*
* @private
*/
_buildFiltersContainer: function () {
var filtersContainer = this._filterscontainer = new HorizontalList();
filtersContainer.addClass('filters-container');
filtersContainer.addClass('filters');
this._header.appendChildWidget(filtersContainer);
},
/**
* Builds the title.
*
* @private
*/
_buildTitle: function () {
var title = this._title = new Label({ text: l10n.get('guide.title'), classNames: ['top-title'] });
this._branding.appendChildWidget(title);
},
/**
* Builds the subtitle.
*
* @private
*/
_buildSubtitle: function () {
var subtitle = new Label({ text: l10n.get('guide.subtitle'), classNames: ['main-subtitle'] });
this._header.appendChildWidget(subtitle);
},
/**
* Builds the clock.
*
* @private
*/
_buildClock: function () {
var clock = new Clock();
clock.addClass('header-clock');
this._header.appendChildWidget(clock);
},
/**
* Builds now filter.
*
* @private
*/
_buildNowFilter: function () {
var nowFilter = new PointerFocusableButton(),
label = new Label({ text: l10n.get('guide.now'), classNames: ['v-align-target'] }),
helper = new Label({ text: '', classNames: ['v-align-helper'] });
nowFilter.addClass(['filter', 'now-filter', 'icon-button']);
nowFilter.appendChildWidget(helper);
nowFilter.appendChildWidget(label);
this._filterscontainer.appendChildWidget(nowFilter);
},
/**
* Builds day filters.
*
* @private
*/
_buildDayFilters: function () {
var dateString = [
l10n.get('asset.today')
].join(' '),
dayFilters = this._dayfilters = new DayFilterButton({
text: dateString,
id: 'day-filter-btn',
isDropDown: true,
classList: 'icon-button'
});
this._filterscontainer.appendChildWidget(dayFilters);
},
/**
* Builds day filters.
*
* @private
*/
_buildTimeFilters: function () {
var timeFilters = this._timefiltersButton = new FilterButton({
text: this._filterTime.getHours() + ':00',
id: 'filter-time-btn',
isDropDown: true,
classList: 'icon-button'
});
this._filterscontainer.appendChildWidget(timeFilters);
},
/**
* Builds genre filters.
*
* @private
*/
_buildGenreFilters: function () {
var genreFilters = this._genrefiltersButton = new FilterButton({
text: l10n.get('genre.all'),
id: 'filter-genre-btn',
isDropDown: true,
classList: 'icon-button'
});
this._filterscontainer.appendChildWidget(genreFilters);
},
/**
* Builds channel logo and number.
*
* @param {Array} items - Array of items.
* @returns {Object} Container - A new Container with channelinfo.
*
* @private
*/
_buildChannelLogoAndNumber: function (items) {
var channlInfoContainer = new Container(),
channellogo = new Image(),
channelNumber = channelManager.getChannelNumberForId(items[0].getChannelId()),
labelNum = new Label({ text: channelNumber }),
logoSrc;
logoSrc = items[0].getChannelLogo();
channellogo.setSrc(logoSrc);
channlInfoContainer.addClass('channel-info');
channlInfoContainer.appendChildWidget(labelNum);
channlInfoContainer.appendChildWidget(channellogo);
return channlInfoContainer;
},
/**
* Builds the infoblock.
*
* @private
*/
_buildInfoBlock: function () {
var infoblock = this._infoblock = new InfoBlock(infoblockConfig);
infoblock.addClass('info-block');
this.appendChildWidget(infoblock);
},
/**
* Builds the vertical carousel.
*
* @private
*/
_buildVerticalCarousel: function () {
var verticalCarousel = this._verticalCarousel = new VerticalCarousel();
this._pageContainer.appendChildWidget(verticalCarousel);
},
/**
* Builds the direct dial.
*
* @private
*/
_buildDirectDial: function () {
var directDial = this._directDial = new DirectDial();
this.appendChildWidget(directDial);
},
/**
* Builds the no results message.
*
* @private
*/
_buildNoResultsMessage: function () {
var noResultsLabel = this._noResultsLabel = new Label({ classNames: ['noresults'] });
this.appendChildWidget(noResultsLabel);
},
/**
* Builds the vertical carousel.
*
* @param {Object} currentDate - Current date.
* @param {Array} datesArray - Array of dates.
* @returns {number} Index.
* @private
*/
_getCurrentDateIndex: function (currentDate, datesArray) {
var i, j, index;
for (i = 0, j = datesArray.length; i < j; i++) {
if (currentDate.getDate() === datesArray[i].getDate()) {
index = i;
break;
}
}
return index;
},
/**
* Builds the carousel.
*
* @param {Object} items - Items.
* @returns {Object} Carousel - A new HorizontalCarousel.
* @private
*/
_buildCarousel: function (items) {
var datesArray = this._generateDateArray(),
dateStringsArray = this._getFilterDays(),
dayIndex = this._getCurrentDateIndex(this._filterDate, datesArray),
horizontalCarousel = new HorizontalCarousel(items, dayIndex, this._filterDate, dateStringsArray);
horizontalCarousel.addClass('carousel-row');
return horizontalCarousel;
},
/**
* BeforeRender event.
*/
onBeforeRender: function () {
var now = application.getDate(),
time = now.getHours(),
day;
time = time % 2 === 0 ? time : time - 1;
time = this._convertToTime(time) + ':00';
day = [
l10n.get('asset.today')
].join(' ');
this._setFilter('time', time);
this._setFilter('day', day);
this._setFilter('genre', l10n.get('genre.all'));
this._loadingTime = application.getDate();
},
/**
* OnBeforeShow event handler.
*/
onBeforeShow: function () {
var dayStart = application.getDate(),
value = ((application.getDate() - this._loadingTime) / 1000).toFixed(2);
this._activeTimeframe = '_nowIndex';
this._dayfilters.focus();
dayStart.setHours(2, 0, 0, 0);
GA.onPageView('guide');
this._filterGenre = null;
this._startGuidePage = application.getDate();
this._infoblock.hide({skipAnim: true});
this._directDial.hide({skipAnim: true});
this._setEventListeners();
this._reset();
this._loadData(dayStart, null, '_nowIndex');
GA.onEvent('page', 'load', {
eventLabel: 'guide',
eventValue: value
});
this._updateProgressbars();
HaloManager.getInstance()
.getServiceMessage()
.then(function (r) {
if (r) {
application.route('service');
}
});
},
/**
* OnBeforeHide event handler.
*
*/
onBeforeHide: function () {
var activeFilter = this._dayfilters.getActiveChildWidget(),
duration = ((application.getDate() - this._startGuidePage) / 60000).toFixed(2),
now = application.getDate();
if (activeFilter) {
activeFilter.removeClass('selected');
}
this._genrefiltersButton.removeClass('selected');
this._filterTime = now;
clearInterval(this._progressInterval);
this._progressInterval = null;
this._removeEventListeners();
GA.onEvent('page', 'time', {
eventLabel: 'search',
eventValue: duration
});
},
/**
* Sets the event listeners.
*
* @private
*/
_setEventListeners: function () {
this.addEventListener('selecteditemchange', this._onSelectedItemChangeBound);
this.addEventListener('mousefocus', this._onFocusBound);
application.addEventListener('$unlocked', this._unlockAssetsBound);
application.addEventListener('$locked', this._lockAssetsBound);
document.addEventListener('visibilitychange', Utils.bind(this._onVisibilityChangeBound, this));
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('selecteditemchange', this._onSelectedItemChangeBound);
this.removeEventListener('mousefocus', this._onFocusBound);
application.removeEventListener('$unlocked', this._unlockAssetsBound);
application.removeEventListener('$locked', this._lockAssetsBound);
this._removeSelectListener();
},
/**
* Returns actual timeframe.
*
* @returns {Date} Actual date object.
* @private
*/
_getCurrentTimeframeStart: function () {
var date = application.getDate(),
hours;
hours = date.getHours() % 2 ? date.getHours() - 1 : date.getHours();
date.setHours(hours, 0, 0, 0);
return date;
},
/**
* KeyDown event.
*
* @param {Object} e - The event data.
* @private
*/
_onKeyDown: function (e) {
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:
e.stopPropagation();
e.preventDefault();
this._onBack();
break;
case KeyEvent.VK_CHANNEL_UP:
if (this._verticalCarousel.getChildWidgetCount()) {
this._moveGuide(1);
}
break;
case KeyEvent.VK_CHANNEL_DOWN:
if (this._verticalCarousel.getChildWidgetCount()) {
this._moveGuide(0);
}
break;
case KeyEvent.VK_0:
case KeyEvent.VK_1:
case KeyEvent.VK_2:
case KeyEvent.VK_3:
case KeyEvent.VK_4:
case KeyEvent.VK_5:
case KeyEvent.VK_6:
case KeyEvent.VK_7:
case KeyEvent.VK_8:
case KeyEvent.VK_9:
this._onDirectDialInput(e.keyChar);
break;
}
},
/**
* Focus event.
*
* @private
*/
_onFocus: function () {
var verticalCarousel = this._verticalCarousel,
lastIndex = verticalCarousel._aligner._lastAlignIndex,
activeCarouselRow = verticalCarousel.getActiveChildWidget(),
index = verticalCarousel.getActiveChildIndex();
if (activeCarouselRow.isFocussed() && (lastIndex !== index)) {
activeCarouselRow.determinePointers(index);
this._activeVerticalIndex = index;
verticalCarousel.alignToIndex(index);
this._activeVerticalIndex = null;
}
},
/**
* OnSelectedItemChange event.
*
* @param {Object} e - The event data.
* @private
*/
_onSelectedItemChange: function (e) {
var target = e.target,
item = e.item,
index = e.index,
activeRowNumber = index + 1,
count = this._verticalCarousel.getChildWidgetCount() - 1,
loadmoreRows = config.loading.guide.loadMoreAfterRows,
channelMap,
direction,
channelId,
startIndex,
itemChannelIndex;
if (target.hasClass('time-filters')) {
this.focus();
}
// Target is the vertical carousel.
if (target.hasClass('big-carousel')) {
channelMap = channelManager.getChannelMap();
direction = target.parentWidget.parentWidget.getAligner().getDirection();
channelId = this._getChannelIdByCarouselDirection(direction);
startIndex = channelMap.indexOf(channelId);
itemChannelIndex = channelMap.indexOf(item.getChannelId());
// Going down
if (direction === 0 && (count - index) === loadmoreRows) {
if (channelMap.length - 1 === startIndex) {
return;
}
if (!this._filterGenre && !this._loadingMoreData) {
this._loadingMoreData = true;
this._loadMore(this._filterDate, startIndex + 1, startIndex + 20)
.then(Utils.bind(function (data) {
this._setLoadmoreData(data, direction);
this._loadingMoreData = false;
}, this));
}
}
// Going up
if (direction === 1 && ((index === loadmoreRows) || (itemChannelIndex - startIndex === loadmoreRows - 1))) {
if (startIndex === 0) {
return;
}
if (!this._filterGenre && !this._loadingMoreData) {
this._loadingMoreData = true;
this._loadMore(this._filterDate, startIndex - 21, startIndex)
.then(Utils.bind(function (data) {
this._setLoadmoreData(data, direction);
this._loadingMoreData = false;
}, this));
}
}
if (activeRowNumber !== 1) {
this._infoblock.show();
} else {
this._infoblock.hide();
}
if (index === 1) {
this._gradient.addClass('active-gradient');
} else if (index === 0) {
this._gradient.removeClass('active-gradient');
}
}
if (target.parentWidget.parentWidget.parentWidget.parentWidget === this) {
this._updateFilter(item.getMask().getWidgetStrip().getActiveChildWidget(), true);
}
if (item.hasClass('focus')) {
if (item.hasClass('asset')) {
this._updateFilter(item);
} else if (item.hasClass('next-day-button')) {
this._startAlignPositionTimeout(1);
} else if (item.hasClass('prev-day-button')) {
this._startAlignPositionTimeout(0);
}
}
},
/**
* Get the channel ID based on the scroll direction of the Carousel.
*
* @param {number} direction - The direction Aligner.directions.FORWARD or Aligner.directions.BACKWARD.
* @returns {number} - The channel ID.
* @private
*/
_getChannelIdByCarouselDirection: function (direction) {
var lastIndex = this._verticalCarousel.getChildWidgets().length - 1;
// Down
if (direction === 0) {
return this._verticalCarousel.getChildWidgetByIndex(lastIndex).getChannelId();
}
// Up
if (direction === 1) {
return this._verticalCarousel.getChildWidgetByIndex(0).getChannelId();
}
},
/**
* Updates the filter for the given item.
*
* @param {Object} item - The item.
* @param {boolean} [direct] - Should the aligning happen direct.
* @private
*/
_updateFilter: function (item, direct) {
var now = application.getDate(),
itemDate,
filter,
dataItem = item.getDataItem();
if (!item) {
return;
}
if (dataItem) {
itemDate = dataItem.endTime;
}
clearTimeout(this._timer);
if (!itemDate) {
if (direct) {
this.timeAlignItem(this._activeTimeframe);
} else {
if (item.hasClass('next-day-button')) {
filter = '_lastIndex';
} else if (item.hasClass('prev-day-button')) {
filter = '_firstIndex';
}
this._setTimer(filter);
}
return;
}
clearTimeout(this._timer);
this._setFilter('time', this._filterTimeString(itemDate.getHours()));
filter = itemDate.getTime();
if (item.startTime < now && itemDate > now) {
filter = '_nowIndex';
}
if (direct) {
this.timeAlignItem(this._activeTimeframe);
} else {
this._setTimer(filter);
}
},
/**
* Load content of present time.
*
* @private
*/
_nowFilterSelectionSetting: function () {
var today = application.getDate(),
startDate = application.getDate(),
hours = today.getHours(),
dateString;
if (this._genrefiltersButton.hasClass('selected')) {
this._genrefiltersButton.removeClass('selected');
}
clearTimeout(this._timer);
startDate.setHours(2, 0, 0, 0);
this._activeVerticalIndex = this._verticalCarousel.getActiveIndex();
this._filterGenre = null;
this._loadData(startDate, null, '_nowIndex');
dateString = [
l10n.get('asset.today')
].join(' ');
hours = this._filterTimeString(hours);
this._setFilter('day', dateString);
this._setFilter('time', hours);
this._setFilter('genre', l10n.get('genre.all'));
this._filterTime = today;
},
/**
* Popup a modal to filter a time.
*
* @private
*/
_timeFilterSelectionSetting: function () {
var timeArray = this._generateHours(2);
clearTimeout(this._timer);
application.route('dropdown', {
dataOptions: this._getFilterHours(2),
dataValues: timeArray,
prevValue: this._timefiltersButton.getText(),
filter: 'time',
callback: Utils.bind(this._onDropdownCallback, this),
showGoBackButton: true
});
},
/**
* Popup a modal to filter a genre.
*
* @private
*/
_genreFilterSelectionSetting: function () {
var genreArray = [],
genreValues = [],
genres = GenreManager.GENRES,
all = l10n.get('genre.all');
genreArray.push(all);
genreValues.push({
name: all,
values: null
});
Utils.each(genres, function (genre) {
genreArray.push(genre.name);
genreValues.push(genre);
});
clearTimeout(this._timer);
application.route('dropdown', {
dataOptions: genreArray,
dataValues: genreValues,
prevValue: this._genrefiltersButton.getText(),
filter: 'genre',
callback: Utils.bind(this._onDropdownCallback, this),
showGoBackButton: true
});
},
/**
* Popup a modal to filter a day.
*
* @private
*/
_dayFilterSelectionSetting: function () {
clearTimeout(this._timer);
application.route('dropdown', {
dataOptions: this._getFilterDays(),
dataValues: this._generateDateArray(),
prevValue: this._dayfilters.getText(),
filter: 'day',
callback: Utils.bind(this._onDropdownCallback, this),
showGoBackButton: true
});
},
/**
* Function fires after dropdown item is selected.
*
* @param {Object} event - Selected item.
* @private
*/
_onDropdownCallback: function (event) {
var target,
filterName,
valueObject,
date,
time;
event = event || {};
target = event.target || {};
filterName = target._filterName;
valueObject = target._itemValueObject;
switch (filterName) {
case 'time':
date = new Date(this._filterDate);
date.setHours(
valueObject.getHours(),
valueObject.getMinutes(),
valueObject.getSeconds(),
0
);
time = this._convertToTime(date.getHours()) + ':00';
this.timeAlignVisible(date.getTime());
this._setFilter('time', time);
this._filterTime = date;
this._activeTimeframe = date.getTime();
break;
case 'day':
date = new Date(valueObject);
valueObject.setHours(
this._filterTime.getHours(),
this._filterTime.getMinutes(),
this._filterTime.getSeconds(),
0);
this._filterDate = valueObject;
this._filterTime = valueObject;
date.setHours(2, 0, 0, 0);
this._activeVerticalIndex = this._verticalCarousel.getActiveIndex();
this._setFilter('day', target._labelText);
this._loadData(date, null, valueObject.getTime());
break;
case 'genre':
this._filterGenre = valueObject;
this._setFilter('genre', valueObject.name);
if (!valueObject.values) {
this._filterGenre = null;
this._activeVerticalIndex = 0;
this._genrefiltersButton.removeClass('selected');
} else {
if (!this._genrefiltersButton.hasClass('selected')) {
this._genrefiltersButton.addClass('selected');
}
}
GA.onEvent('guide', 'select-genre', {
eventLabel: valueObject.name
});
this._filterDate.setHours(2, 0, 0, 0);
this._loadData(this._filterDate, null, this._filterDate.getTime());
break;
}
},
/**
* Get a generated array of days in string form.
*
* @param {Integer} [num] - Number of generated days.
* @returns {Array} Days in string.
* @private
*/
_getFilterDays: function (num) {
var daysStringArray = [],
i,
dateArray,
dateString,
currentDay = Math.floor(application.getDate().getTime() / 1000 / 60 / 60 / 24);
num = num || DEFAULT_FILTER_FORWARD_DAYS + DEFAULT_FILTER_BACK_DAYS + 1;
dateArray = this._generateDateArray();
for (i = 0; i < num; i++) {
switch (Math.floor(dateArray[i].getTime() / 1000 / 60 / 60 / 24)) {
case currentDay:
dateString = [
l10n.get('asset.today')
].join(' ');
break;
case (currentDay + 1):
dateString = [
l10n.get('asset.tomorrow')
].join(' ');
break;
case (currentDay - 1):
dateString = [
l10n.get('asset.yesterday')
].join(' ');
break;
default:
dateString = [
l10n.get('asset.days.' + dateArray[i].getDay()),
dateArray[i].getDate(),
l10n.get('asset.monthsLong.' + dateArray[i].getMonth())
].join(' ');
}
daysStringArray.push(dateString);
}
return daysStringArray;
},
/**
* Generate array of dates.
*
* @param {number} back - Number of back days.
* @param {number} forward - Number of forward days.
* @returns {Array} Days.
* @private
*/
_generateDateArray: function (back, forward) {
var i,
date,
backDays = back || DEFAULT_FILTER_BACK_DAYS,
forwardDays = forward || DEFAULT_FILTER_FORWARD_DAYS,
days = [];
for (i = -backDays; i <= -1; i++) { // Generates back days.
date = this._generateDate(i);
days.push(date);
}
days.push(this._generateDate(0)); // Generates today.
for (i = 1; i <= forwardDays; i++) { // Generates forward days.
date = this._generateDate(i);
days.push(date);
}
return days;
},
/**
* Generate a day.
*
* @param {Integer} i - Position due to this day.
* @returns {Date} Generated date.
* @private
*/
_generateDate: function (i) {
var day = application.getDate(),
today = application.getDate();
day.setDate(today.getDate() + i);
return day;
},
/**
* Generates an array of hours in string.
*
* @param {Integer} step - Step for another hour.
* @returns {Array} Array of hours in string.
* @private
*/
_getFilterHours: function (step) {
var i = 2,
stringHoursArray = [];
for (i; i < 24; i += step) {
if (i < 10) {
stringHoursArray.push('0' + i + ':00');
} else {
stringHoursArray.push(i + ':00');
}
}
stringHoursArray.push('00:00');
return stringHoursArray;
},
/**
* Generates an hour array.
*
* @param {Integer} step - Step for another hour.
* @returns {Array} Generated integer hours array.
* @private
*/
_generateHours: function (step) {
var hours = [],
time = 2,
day;
while (time < 24) {
day = new Date(this._filterTime);
day.setHours(time, 0, 0, 0);
hours.push(day);
time += step;
}
day = application.getDate();
day.setHours(0, 0, 0, 0);
hours.push(day);
return hours;
},
/**
* Upload text filter value.
*
* @param {string} filter - Filter name.
* @param {string} value - Value which should be set.
* @private
*/
_setFilter: function (filter, value) {
switch (filter) {
case 'now':
break;
case 'day':
this._dayfilters.setText(value);
break;
case 'time':
this._timefiltersButton.setText(value);
break;
case 'genre':
this._genrefiltersButton.setText(value);
break;
}
},
/**
* Select event.
*
* @param {Object} e - The event data.
* @private
*/
_onSelect: function (e) {
var target = e.target,
date = new Date(this._filterDate);
if (target.id === 'back-on-top-block') {
this._onBack();
return;
}
if (target instanceof FilterButton) {
this._onFilterSelect(target);
return;
}
if (target.hasClass('now-filter')) {
this._nowFilterSelectionSetting(target);
} else if (target.hasClass('next-day-button')) {
this._onSelectNextDay(date, target.getText());
} else if (target.hasClass('prev-day-button')) {
this._onSelectPreviousDay(date, target.getText());
} else if (target.hasClass('asset')) {
this._onSelectAsset(target);
}
},
/**
* Gets executed when the filter is selected.
*
* @param {Object} target - The target.
* @private
*/
_onFilterSelect: function (target) {
switch (target.id) {
case 'filter-genre-btn':
this._genreFilterSelectionSetting(target);
break;
case 'day-filter-btn':
this._dayFilterSelectionSetting(target);
break;
case 'filter-time-btn':
this._timeFilterSelectionSetting(target);
break;
}
},
/**
* Gets called when the next day is selected.
*
* @param {Date} date - The date.
* @param {string} dayString - Date in string form.
* @private
*/
_onSelectNextDay: function (date, dayString) {
date.setDate(this._filterDate.getDate() + 1);
date.setHours(2, 0, 0, 0);
this._activeVerticalIndex = this._verticalCarousel.getActiveIndex();
this._filterDate = date;
this._activeTimeframe = date.getTime();
this._setFilter('day', dayString);
this._loadData(date, null, date.getTime())
.then(Utils.bind(function () {
this._setFilter('time', '02:00');
}, this));
},
/**
* Gets called when the previous day is selected.
*
* @param {Date} date - The date.
* @param {string} dayString - Date in string form.
* @private
*/
_onSelectPreviousDay: function (date, dayString) {
date.setDate(this._filterDate.getDate() - 1);
date.setHours(2, 0, 0, 0);
this._activeVerticalIndex = this._verticalCarousel.getActiveIndex();
this._filterDate = date;
this._setFilter('day', dayString);
this._activeTimeframe = '_lastIndex';
this._loadData(date, null, '_lastIndex')
.then(Utils.bind(function () {
this._setFilter('time', '00:00');
}, this));
},
/**
* Unlock the assets.
*
* @private
*/
_unlockAssets: function () {
var verticalCarousel = this._verticalCarousel,
mask = verticalCarousel.getMask(),
rows = mask.getChildWidgetByIndex(0).getChildWidgets(),
assets;
Utils.each(rows, function (row) {
assets = row.getCarousel().getMask().getChildWidgetByIndex(0).getChildWidgets();
Utils.each(assets, function (asset) {
if (!asset.hasClass('prev-day-button') &&
!asset.hasClass('next-day-button')) {
asset.unlock();
}
});
});
},
/**
* Locks the assets.
*
* @private
*/
_lockAssets: function () {
var verticalCarousel = this._verticalCarousel,
mask = verticalCarousel.getMask(),
rows = mask.getChildWidgetByIndex(0).getChildWidgets(),
assets;
Utils.each(rows, function (row) {
assets = row.getCarousel().getMask().getChildWidgetByIndex(0).getChildWidgets();
Utils.each(assets, function (asset) {
if (!asset.hasClass('prev-day-button') &&
!asset.hasClass('next-day-button')) {
asset.lock();
}
});
});
},
/**
* Starts the progressbars update interval.
*/
_updateProgressbars: function () {
var gridAssets,
currentProgramFinished,
now = application.getDate(),
dataItem,
hasProgressbar,
updateNextProgram,
assetParentWidget,
currentAssetParentWidget;
if (this._progressInterval) {
clearInterval(this._progressInterval);
gridAssets = this._verticalCarousel.getCarouselItems();
Utils.each(gridAssets, Utils.bind(function (gridAsset) {
hasProgressbar = gridAsset.updateProgressBar();
dataItem = gridAsset.getDataItem();
currentProgramFinished = now >= dataItem.endTime;
currentAssetParentWidget = gridAsset.parentWidget;
// Will update nex program if previous asset was updated and belongs to the same strip/channel lane.
if (updateNextProgram && assetParentWidget === currentAssetParentWidget) {
guideFormatter.format(dataItem.item, gridAsset);
updateNextProgram = false;
}
// Will update the current program (to be set to past).
if (currentProgramFinished && hasProgressbar) {
guideFormatter.format(dataItem.item, gridAsset);
updateNextProgram = true;
assetParentWidget = gridAsset.parentWidget;
}
}, this));
}
this._progressInterval = setInterval(Utils.bind(this._updateProgressbars, this), 5000);
},
/**
* Opens details modal.
*
* @param {Object} asset - Asset.
* @private
*/
_showAssetDetail: function (asset) {
application.route('epgdetail', {
data: asset,
callback: Utils.bind(this.focus, this),
callingPage: 'guide'
});
},
/**
* Gets called when the asset is selected.
*
* @param {Object} target - The target.
* @private
*/
_onSelectAsset: function (target) {
var dataItem = target.getDataItem().item,
carousels = this._verticalCarousel.getMask().getWidgetStrip().getChildWidgets();
clearTimeout(this._timer);
Utils.each(carousels, function (carousel) {
carousel.completeAlignment();
});
if (target.isLocked()) {
this._removeSelectListener();
application.route('parentalpin', {
successCallback:
Utils.bind(function () {
this._showAssetDetail(dataItem);
this._unlockAssets();
this._setSelectListener();
}, this),
errorCallback: Utils.bind(this._setSelectListener, this),
escapeCallback: Utils.bind(this._setSelectListener, this)
});
return;
}
this._showAssetDetail(dataItem);
},
/**
* Attempts to load the data for a given date.
*
* @param {Date} date - The date to load data for.
* @param {number|null} [duration] - Duration to the end of the day.
* @param {string|number} [alignIndex] - The alignindex.
* @returns {Promise} - Promise resolving with data.
* @private
*/
_loadData: function (date, duration, alignIndex) {
var durationTime = duration || (24 * 60 * 60) - 1,
channelMap = channelManager.getChannelMap(),
channelId = this._directDialChannel || this._getCurrentChannelId() ||
channelManager.getLastWatchedChannel() || channelMap[0],
maxChannels = config.loading.guide.maxChannels,
endIndex = maxChannels,
index = channelMap.indexOf(channelId),
maxIndex = channelMap.length - 1,
channelNumber = channelManager.getChannelById(channelId).getNumber(),
startIndex;
this._directDialChannel = null;
this._infoblock.hide({skipAnim: true});
this._filterDate = date;
application.showLoader(true);
if (this._filterGenre) {
return this._loadGenreData(this._filterGenre.name, alignIndex, channelId);
}
startIndex = index - (Math.floor(endIndex / 2));
endIndex = index + (Math.ceil(endIndex / 2));
if (startIndex < 0) {
endIndex = endIndex + Math.abs(startIndex);
startIndex = 0;
} else if (endIndex > maxIndex) {
startIndex = maxIndex - maxChannels;
endIndex = maxIndex + 1;
}
return epgManager.getItems(
channelManager.getChannelMap(startIndex, endIndex),
Math.floor(date / 1000),
durationTime)
.then(Utils.bind(this._setData, this, date, alignIndex, channelNumber))
['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 date, star and end index.
*
* @param {Date} date - The date to load data for.
* @param {number} startIndex - Start index.
* @param {number} endIndex - End index.
* @returns {Promise} - Promise resolving with data.
*
* @private
*/
_loadMore: function (date, startIndex, endIndex) {
var durationTime = (24 * 60 * 60) - 1;
if (startIndex < 0) {
startIndex = 0;
}
return epgManager.getItems(
channelManager.getChannelMap(startIndex, endIndex),
Math.floor(date / 1000),
durationTime)
['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 genre data for the given key.
*
* @param {string} genreKey - The genre key.
* @param {string|number} [alignIndex] - The align index.
* @param {string|number} [channelId] - Channel Id we want to select.
* @returns {Promise} - Promise resolving with the genre data set.
* @private
*/
_loadGenreData: function (genreKey, alignIndex, channelId) {
var channelNumber = null;
return GenreManager.getInstance().getData(genreKey, this._filterDate)
.then(Utils.bind(function (data) {
if (data[channelId]) {
channelNumber = channelManager.getChannelById(channelId).getNumber();
}
this._genreFilteredData = data;
this._setData(this._filterDate, alignIndex, channelNumber, data);
}, this));
},
/**
* Applies timer to synch align carousels in guide.
*
* @param {Object} timeframe - Timeframe of focused asset.
* @private
*/
_setTimer: function (timeframe) {
var self = this;
clearTimeout(this._timer);
this._timer = setTimeout(function () {
self.timeAlignVisible(timeframe);
self._activeTimeframe = timeframe;
}, 2000);
},
/**
* Start the timeout that aligns the guide to a certain position.
*
* @param {number} position - Position. 0 for the start, 1 for the end.
* @private
*/
_startAlignPositionTimeout: function (position) {
var self = this,
timeframe = position === 1 ? '_lastIndex' : '_firstIndex';
clearTimeout(this._timer);
this._timer = setTimeout(function () {
self.timeAlignVisible(timeframe);
self._activeTimeframe = timeframe;
}, 2000);
},
/**
* Append data to carousel.
*
* @param {Array} data - The data.
* @param {number} direction - The direction Aligner.directions.FORWARD or Aligner.directions.BACKWARD.
* @private
*/
_setLoadmoreData: function (data, direction) {
var verticalCarousel = this._verticalCarousel,
widgetHeight = carouselsDimensions.carousels.widgetHeight,
activeWidget = this._verticalCarousel.getActiveChildWidget(),
fromLeftPosition = activeWidget.getFromLeftIndex(),
carousel,
alignIndex,
channelLogoAndNumber,
keys,
i;
data = this._formatEPGData(data);
keys = Utils.keys(data);
if (direction === 1) {
keys = keys.reverse();
}
for (i = 0; i < keys.length; i++) {
if (!data[keys[i]].length) {
continue;
}
carousel = this._buildCarousel(data[keys[i]]);
channelLogoAndNumber = this._buildChannelLogoAndNumber(data[keys[i]]);
carousel.insertChildWidget(0, channelLogoAndNumber);
// Down
if (direction === 0) {
verticalCarousel.append(carousel, widgetHeight);
}
// Up
if (direction === 1) {
verticalCarousel.insert(0, carousel, widgetHeight);
}
if (!this._activeTimeframe) {
this._activeTimeframe = '_nowIndex';
fromLeftPosition = 1;
}
carousel.alignToTimeOfDay(this._activeTimeframe, {skipAnim: true}, fromLeftPosition);
}
alignIndex = verticalCarousel.getIndexOfChildWidget(verticalCarousel.getActiveChildWidget());
this._activeVerticalIndex = alignIndex;
verticalCarousel.alignToIndex(alignIndex, {
skipAnim: true
});
this._activeVerticalIndex = null;
},
/**
* Append data to carousel.
*
* @param {Object} date - The date object.
* @param {string} [alignIndex] - The align index.
* @param {number} channelNumber - The channelnumber.
* @param {Array} data - The data.
* @private
*/
_setData: function (date, alignIndex, channelNumber, data) {
var verticalCarousel = this._verticalCarousel,
noresultslabel,
carousel,
channelLogoAndNumber,
position = 0;
alignIndex = alignIndex || '_nowIndex';
this._verticalCarousel.reset();
if (Utils.isEmpty(data)) {
application.hideLoader();
this._filterscontainer.focus();
if (this._filterGenre) {
noresultslabel = l10n.get('guide.noresults', {
genre: this._filterGenre.name
});
} else {
noresultslabel = l10n.get('guide.noresultsfull');
}
this._noResultsLabel.setText(noresultslabel);
this._noResultsLabel.show();
return;
}
this._noResultsLabel.hide({skipAnim: true});
data = this._formatEPGData(data);
if (Utils.keys(data).length && channelNumber) {
this._activeVerticalIndex = Utils.keys(data).indexOf(channelNumber.toString());
}
if (this._activeVerticalIndex !== 0) {
this._gradient.addClass('active-gradient');
}
Utils.each(data, function (items) {
carousel = this._buildCarousel(items);
channelLogoAndNumber = this._buildChannelLogoAndNumber(items);
carousel.insertChildWidget(0, channelLogoAndNumber);
this._verticalCarousel.append(carousel, carouselsDimensions.carousels.widgetHeight);
}, this);
if (verticalCarousel.getChildWidgets().length > 0
&& this._activeVerticalIndex !== null) {
if ((verticalCarousel.getChildWidgetCount() - 1 < this._activeVerticalIndex) || this._activeVerticalIndex === -1) {
this._activeVerticalIndex = verticalCarousel.getChildWidgetCount() - 1;
}
verticalCarousel.alignToIndex(this._activeVerticalIndex, {skipAnim: true});
} else {
verticalCarousel.alignToIndex(0, {skipAnim: true});
}
if (alignIndex === '_nowIndex') {
position = 1;
}
this._isGoingBack = true;
this.timeAlignVisible(alignIndex, {
skipAnim: true
}, position);
this._activeVerticalIndex = null;
application.hideLoader();
},
/**
* Formats the epg data.
*
* @param {Object} data - The data.
* @returns {Object} - The formatted EPG data.
* @private
*/
_formatEPGData: function (data) {
var formatted = {};
/*
* EPG Manager gives data back according to channel Id.
* We want to display it according to channel number instead.
*/
Utils.each(data, function (items) {
if (items && items.length) {
formatted[channelManager.getChannelNumberForId(items[0].getChannelId())] = items;
}
});
return formatted;
},
/**
* Aligns assets in visible carousels depending on time they start.
*
* @param {string|number} timeframe - Timeframe of focused asset.
* @param {Object} [options] - Animation options.
* @param {number} [position] - The align position.
*/
timeAlignVisible: function (timeframe, options, position) {
var activeWidget = this._verticalCarousel.getActiveChildWidget(),
fromLeftPosition = activeWidget.getFromLeftIndex(),
activeIndex = this._verticalCarousel.getActiveChildIndex(),
isGoingBack = this._isGoingBack || false,
i,
carousel;
options = options || {
duration: 300,
fps: 60,
easing: 'easeInOut',
skipAnim: false
};
if (position) {
fromLeftPosition = position;
}
for (i = activeIndex - 3; i < activeIndex + 3; i++) {
carousel = this._verticalCarousel.getChildWidgetByIndex(i);
if (carousel && (!carousel.hasClass('focus') || isGoingBack)) {
if (timeframe === '_lastIndex') {
carousel.activateLast(options);
} else if (timeframe === '_firstIndex') {
carousel.alignToPosition(0, options);
} else {
carousel.alignToTimeOfDay(timeframe, options, fromLeftPosition);
}
}
}
this._isGoingBack = null;
},
/**
* Time aligns the items.
*
* @param {string|number} timeframe - The timeframe.
* @param {Object} [options] - The alignment options.
*/
timeAlignItem: function (timeframe, options) {
var verticalCarousel = this._verticalCarousel,
activeWidget = verticalCarousel.getActiveChildWidget(),
fromLeftPosition = activeWidget.getFromLeftIndex(),
activeIndex = verticalCarousel.getActiveChildIndex(),
item;
options = options || {
skipAnim: true
};
item = verticalCarousel.getChildWidgetByIndex(activeIndex - 2);
if (item) {
if (timeframe === '_lastIndex') {
item.activateLast(options);
} else if (timeframe === '_firstIndex') {
item.alignToPosition(0);
} else {
item.alignToTimeOfDay(timeframe, options, fromLeftPosition);
}
}
item = verticalCarousel.getChildWidgetByIndex(activeIndex + 2);
if (item) {
if (timeframe === '_lastIndex') {
item.activateLast(options);
} else if (timeframe === '_firstIndex') {
item.alignToPosition(0, options);
} else {
item.alignToTimeOfDay(timeframe, options, fromLeftPosition);
}
}
},
/**
* Aligns all assets in carousel to with the last item.
*
* @param {Object} [options] - Animation options.
*/
timeAlignLastItem: function (options) {
var activeIndex = this._verticalCarousel.getActiveChildIndex(),
i,
carousel;
options = options || {
duration: 300,
fps: 60,
easing: 'easeInOut',
skipAnim: false
};
for (i = activeIndex - 3; i < activeIndex + 3; i++) {
carousel = this._verticalCarousel.getChildWidgetByIndex(i);
if (carousel && !carousel.hasClass('focus')) {
carousel.activateLast(options);
}
}
},
/**
* Sets "0" before number, if that is single number.
*
* @param {number} number - Number which should be corrected.
* @returns {string} String number wich "0" on first position if needed.
* @private
*/
_convertToTime: function (number) {
number = number.toString();
return number.length === 1 ? '0' + number : number;
},
/**
* Makes time form of a number.
*
* @param {number} filterTime - Input number.
* @returns {string} String form of time.
* @private
*/
_filterTimeString: function (filterTime) {
filterTime = filterTime % 2 === 0 ? filterTime : filterTime - 1;
filterTime = filterTime.toString();
filterTime = filterTime.length === 1 ? '0' + filterTime + ':00' : filterTime + ':00';
return filterTime;
},
/**
* Resets the guide.
*
* @private
*/
_reset: function () {
this._infoblock.hide({skipAnim: true});
this._timefiltersButton.setActiveChildIndex(0);
this._verticalCarousel.reset();
},
/**
* Moves the guide.
*
* @param {number} direction - The direction. 1 for upwards, 0 for downwards.
* @private
*/
_moveGuide: function (direction) {
var carousel = this._verticalCarousel,
current = carousel.getActiveIndex(),
max = carousel.getChildWidgetCount() - 1,
min = 0,
index = 0,
animOption = {
skipAnim: !!this._movingGuide,
onComplete: Utils.bind(function () {
this._movingGuide = false;
}, this)
},
loadmoreRows = config.loading.guide.loadMoreAfterRows,
channelMap = channelManager.getChannelMap(),
channelId = this._getChannelIdByCarouselDirection(direction),
startIndex = channelMap.indexOf(channelId);
if (direction) {
// Move up.
index = current - 3;
} else {
// Move down.
index = current + 3;
}
if (index > max) {
index = max;
} else if (index < min) {
index = min;
}
// Going down
if (direction === 0 && ((max - current) <= loadmoreRows) &&
(channelMap.length - 1 !== startIndex) &&
!this._loadingMoreData) {
if (!this._filterGenre) {
this._loadingMoreData = true;
this._loadMore(this._filterDate, startIndex + 1, startIndex + 20)
.then(Utils.bind(function (data) {
this._setLoadmoreData(data, direction);
this._loadingMoreData = false;
}, this));
}
}
// Going up
if (direction === 1 && current <= loadmoreRows && startIndex !== 0 &&
!this._loadingMoreData) {
if (!this._filterGenre) {
this._loadingMoreData = true;
this._loadMore(this._filterDate, startIndex - 21, startIndex)
.then(Utils.bind(function (data) {
this._setLoadmoreData(data, direction);
this._loadingMoreData = false;
}, this));
}
}
// Prevent carousel aligning if focus is on the first/last row and no more data is available.
if ((current !== max && !direction) || (current !== min && direction)) {
this._activeVerticalIndex = index;
// Prevents guide list jumping behaviour.
this._movingGuide = !(current === 0 && current === index);
carousel.alignToIndex(index, animOption);
this._activeVerticalIndex = null;
}
},
/**
* OnDirectDialInput.
*
* @param {string} char - The input character.
* @private
*/
_onDirectDialInput: function (char) {
var directDial = this._directDial,
timeoutValue = 3000;
clearTimeout(this._timer);
directDial.setKeyInput(char);
if (!directDial.isVisible()) {
directDial.show({duration: 200});
}
this._clearDirectDialTimeout();
this._directDialTimeout = setTimeout(Utils.bind(this._onDirectDial, this), timeoutValue);
},
/**
* Returns closest channel id for specific channel number from genre filtered data.
*
* @param {number} number - Channel number.
* @returns {number} Channel id.
* @private
*/
_getGenreFilteredChannelId: function (number) {
return Utils.map(Utils.keys(this._genreFilteredData), function (channelId) {
return channelManager.getChannelById(channelId);
}).sort(function (a, b) {
return Math.abs(a.getNumber() - number) - Math.abs(b.getNumber() - number);
}).shift().getId();
},
/**
* Gets executed when the direct dial finalises.
*
* @private
*/
_onDirectDial: function () {
var number = parseInt(this._directDial.getCurrentInput()),
self = this,
channelId;
if (!isNaN(number)) {
if (this._filterGenre) {
channelId = this._getGenreFilteredChannelId(number);
} else {
channelId = channelManager.getChannelByNumber(number).getId();
}
this._directDialChannel = channelId;
application.showLoader();
this._loadData(this._filterDate, null, this._activeTimeframe)
.then(function () {
self.focus();
application.hideLoader();
});
this._directDial.hide({duration: 200});
this._directDial.reset();
}
},
/**
* Clears the direct dial timeout.
*
* @private
*/
_clearDirectDialTimeout: function () {
clearTimeout(this._directDialTimeout);
},
/**
* Gets executed when the user should go back.
*
* @private
*/
_onBack: function () {
var activeBigCarouselIndex = this._verticalCarousel.getActiveChildIndex(),
filterHours = this._timefiltersButton.getText(),
self = this,
channelId,
date;
if (activeBigCarouselIndex !== 0) {
channelId = channelManager.getChannelMap()[0];
this._directDialChannel = channelId;
this._isGoingBack = true;
application.showLoader();
date = new Date(this._filterDate);
date.setHours(
parseInt(filterHours.slice(0, 2)),
parseInt(filterHours.slice(3, 5)),
0,
0
);
this._loadData(this._filterDate, null, date.getTime())
.then(function () {
self.focus();
application.hideLoader();
});
} else {
application.focusMenu('main');
}
},
/**
* Returns the currently active channel.
*
* @returns {number|null} - The currently active channel id or null.
* @private
*/
_getCurrentChannelId: function () {
var activeWidget = this._verticalCarousel.getActiveChildWidget();
if (activeWidget) {
return activeWidget.getChannelId();
}
return null;
},
/**
* Visibility change event.
*
* @param {Object} e - The event data.
* @private
*/
_onVisibilityChange: function (e) {
if (this.isFocussed() && (document.visibilityState === 'visible' || e === 'visible')) {
this._updateProgressbars();
} else {
this._clearProgressInterval();
}
},
/**
* Clears any progress interval update.
*
* @private
*/
_clearProgressInterval: function () {
clearInterval(this._progressInterval);
this._progressInterval = null;
}
});
return Guide;
});