Skip to content

Instantly share code, notes, and snippets.

@jodaka
Created August 14, 2012 19:28
Show Gist options
  • Save jodaka/3352000 to your computer and use it in GitHub Desktop.
Save jodaka/3352000 to your computer and use it in GitHub Desktop.
fav
/*global moment, Handlebars, i18n, Ya, BEM, Lego */
(function(y, $){
"use strict";
/**
* Экспортирует только один метод: init, который получает на вход данные
* и отрисовывает список эпизодов
*/
y.fav = (function(){
var f = {};
var $document = $(document);
f.loader = '<img class="b-loader" src="//yandex.st/lego/_/q4t1zoXCZziVOOmLCkFJjzLDFR8.gif" style="width:10px; padding: 1px;"/>';
// preloading 'loader' image
new Image().src = '//yandex.st/lego/_/q4t1zoXCZziVOOmLCkFJjzLDFR8.gif';
f.holder = $('.b-layer-up .l-width-gap');
f.printHolder = $('.js-print-version');
/**
* Возвращает теледень (Дату. Не полную, а порядковый номер дня в месяце. With leading zero, да).
* @param {Moment} date дата
* @return {Number} теледень
*/
f.getTVDay = function(date) {
return (date.hours() < 5)
? date.subtract('days', 1).format("DD")
: date.format("DD")
}
/**
* Форматирует дату в из формата MM/DD/YYYY
* в представление "[Сегодня] 30 мая".
* @param {String} date дата
* @return {String}
*/
f.formatDate = function(date) {
var _headerMonth = date.substr(0,2);
var _headerDate = date.substr(3,2);
var _now = moment.utc(Ya.tv.fromLocalTz(y.timestamp));
var today = f.getTVDay(_now);
return '' +
((today === _headerDate) ? i18n.tv['js-common'].today()+', ' : '')
+ _headerDate + ' '
+ i18n.tv['js-common'].monthsLong().split('_')[_headerMonth-1]
}
/**
* Вешаем бинды на всё и вся
*/
f.addBindings = function() {
// кнопка удаления передачи из списка "Мои передачи"
$document.on('click.delMy', '.b-delete', f.removeFromFavorites);
// кнопки "Добавить" (в старых версиях "Напомнить")
$document.on('click.remindButton', '.b-prog-feed .b-form-tags input[type=checkbox]', f.remindButtonClick);
// звездочка в рекомендательном блоке
$document.on("TV:recommendationStarClick", f.recommendationStarClick);
// кнопка печати
$document.on("click.Print", ".b-print a", f.print);
// слушаем событие от calendar.js о том, что пришли данные от календаря
$document.on('TV:calendarEventsReady', f.renderRemindButtons);
}
/**
* Версия для печати
* @return {[type]} [description]
*/
f.print = function(evt) {
evt.preventDefault();
window.print();
$document.trigger('TV:statface', [18, 71216, 'print']);
}
/**
* Клик по кнопке "Добавить"
* Отправляет событие в calendar.js
*/
f.remindButtonClick = function(evt) {
evt.preventDefault();
$document.trigger('TV:remindButtonClick', evt);
}
/**
* Пробегаем по всей нашей хитрой структуре данных,
* находим эпизоды, у которых programID = удаляемому programID и помечаем
* их, как удалённые (флаг deleted)
*
* Метод render такие эпизоды отрисовывать не будет
*
* @param {String} programId
*/
f.markAsDeleted = function(programId) {
for (var day in f.data.daysLookup) {
if (f.data.daysLookup.hasOwnProperty(day)) {
for (var idx = 0, idxMax = f.data.daysLookup[day].length; idx < idxMax; idx++) {
// эпизод
var ep = f.data.episodes[f.data.daysLookup[day][idx]];
var epProgramId = f.data.episodesLookup[ep.id].programId;
if (epProgramId === programId) {
f.data.episodesLookup[ep.id].deleted = 1;
}
}
}
}
// пометим передачу, как удалённую (даже если у неё нет сеансов)
for (var p = 0, pMax = f.data.programs.length; p < pMax; p++) {
if (f.data.programs[p].id === programId) {
f.data.programs[p].deleted = 1;
break;
}
}
};
/**
* Удаление передачи из списка
* @param {Event} evt [description]
*/
f.removeFromFavorites = function(evt) {
var element = $(evt.currentTarget);
var programId = element.data('id').toString();
evt.preventDefault();
// если уже удаляется какой-то элемент,
// отваливаемся
if (f.removingInProgress) {
return false;
}
f.removingInProgress = true;
element.after(f.loader);
/**
* Ищет в календаре эпизоды заданной программы и удаляет их
* @param {String} pid - program id
*/
var cleanCalendarEvents = function(pid) {
var calendarEvents = {};
// преобразуем данные от календаря к нужному виду
if (Ya.tv.calendar && Ya.tv.calendar.events) {
for (var e in Ya.tv.calendar.events) {
if (Ya.tv.calendar.events.hasOwnProperty(e)) {
var eventId = e.replace(/^event/, '');
var progId = Ya.tv.calendar.events[e];
if (!calendarEvents.hasOwnProperty(progId)) {
calendarEvents[progId] = [];
}
calendarEvents[progId].push(eventId);
}
}
}
if (calendarEvents.hasOwnProperty(pid)) {
var deleteEvents = calendarEvents[pid];
for (var i = 0, iMax = deleteEvents.length; i < iMax; i++) {
Ya.tv.calendar.remove(deleteEvents[i], pid);
}
}
};
/**
* функция, которая выстрелит после ответа метода Ya.tv.favorites.remove
* Помечает удалённые передачи специальным маркером в нашей структуре данных.
* @param {String} result
*/
var parseResponse = function (result) {
if (result === 'success') {
// пометим эпизоды, как удалённые в нашей структуре данных
f.markAsDeleted(programId);
// отрисуем измененную структуру данных
f.render();
// уберём звёздочку из программы в списке рекомендаций
$(".b-star[data-id=" + programId + "]").removeClass('b-star_state_checked');
// уберём из календаря эпизоды этой программы
cleanCalendarEvents(programId);
}
f.removingInProgress = false;
};
y.favorites.remove(programId, parseResponse);
}
/**
* Отрисовываем кнопки с напоминаниями.
* поскольку данные для кнопок приходят асинхронно в calendar.js, мы не можем отрисовать
* их сразу.
*
* TODO зарефакторить calendar.js и вмержить его функциональность сюда
*
* @param {Object} data - данные об эпизодах от календаря
*/
f.renderRemindButtons = function(evt, data) {
var eventsFeed = $('.b-prog-feed');
data = data || Ya.tv.calendar.events;
eventsFeed
.find('.b-prog-feed__remind')
.each(function () {
var $this = $(this);
var checkbox = $this.find('.b-form-tags__checkbox');
if (data['event' + $this.data('eid')]) {
checkbox
.attr('checked','checked')
.change()
.parent()
.addClass('b-form-tags__tag_checked_yes')
.find('.b-form-tags__tag_label')
.text(i18n.tv['js-common'].reminderAdded());
} else {
checkbox
.parent()
.removeClass('b-form-tags__tag_checked_yes')
.find('.b-form-tags__tag_label')
.text(i18n.tv['js-common'].reminderAdd());
}
});
eventsFeed.end()
.find('.b-prog-feed__remind')
.removeClass('i-hidden');
}
/**
* Рисовалка
* @return {[type]} [description]
*/
f.render = function(){
var parentHolder = f.holder.parent();
var activePrograms = 0;
var now = moment.utc(Ya.tv.fromLocalTz(y.timestamp));
f.holder.detach();
for (var p = f.data.programs.length - 1; p >= 0; p--) {
if (!f.data.programs[p].hasOwnProperty('deleted')) {
activePrograms++;
}
}
// случай, если ни одной передачи не добавлено в "мои". Показываем промо
if (activePrograms === 0) {
f.holder.html(
Handlebars.templates.myFavoritesPromo({
notAuthorized : !Ya.tv.login,
staticHost : Ya.tv.staticHost,
textMyPrograms : i18n.tv['js-common'].myPrograms(),
reminderPromoText1: i18n.tv['js-common'].reminderPromoText1(),
reminderPromoText2: i18n.tv['js-common'].reminderPromoText2(),
reminderPromoText3: i18n.tv['js-common'].reminderPromoText3()
})
);
parentHolder.html(f.holder);
return true;
}
// случай, если в ближайшее время нет сеансов любимых передач
var _headerDate = '';
var _todayTitle = '';
var calendarToday = parseInt(f.getTVDay(now), 10);
// заголовки только для случаев, когда есть сеансы
if (f.data.days.length > 0) {
_headerDate = parseInt(f.data.days[0].substr(3,2), 10);
// TV-2744 мы должны проверить, что на "сегодня" есть не удалённые эпизоды
if (_headerDate === now.date()) {
var iMax = f.data.daysLookup[f.data.days[0]].length;
var ffound = false;
for (var iToday = 0; iToday < iMax; iToday++) {
var ep = f.data.episodes[f.data.daysLookup[f.data.days[0]][iToday]];
// ищем хоть один не удалённый эпизод
if (!f.data.episodesLookup[ep.id].hasOwnProperty('deleted')) {
ffound = true;
}
}
// если не нашли эпизодов за сегодня: обнуляем заголовко
if (!ffound) {
_headerDate = '';
_todayTitle = '';
}
}
}
// очищаем версию для печати
f.printHolder.html('<table cellspacing="0" class="b-p-my__layout"></table>');
var printHTML = '';
var episodesHTML = '';
var fffoundEpisodes = 0;
var printDaysCounter = 0;
// начинаем отрисовку дней
for (var d = 0, dmax = f.data.days.length; d < dmax; d++) {
var episodes = [];
var gotTitle = false;
// подготовим данные для эпизодов
for (var e = 0, emax = f.data.daysLookup[f.data.days[d]].length; e < emax; e++) {
var episode = f.data.episodes[f.data.daysLookup[f.data.days[d]][e]];
// пропускаем удалённые программы
if (f.data.episodesLookup[episode.id].hasOwnProperty('deleted')) {
continue;
}
if (_todayTitle === '') {
var epStart = moment.utc(Ya.tv.fromLocalTz(episode.start));
var tvDay = f.getTVDay(epStart);
_todayTitle = epStart.format("MM/")+tvDay+epStart.format("/YYYY");
gotTitle = true;
_todayTitle = f.formatDate(_todayTitle);
}
fffoundEpisodes++;
episodes.push({
id : episode.eventId,
start : moment.utc(Ya.tv.fromLocalTz(episode.start)).format('H:mm'),
programId : f.data.episodesLookup[episode.id].programId,
timeStart : episode.start,
timeFinish : episode.finish,
title : f.data.episodesLookup[episode.id].title,
desc : f.data.episodesLookup[episode.id].desc,
channelTitle: f.data.episodesLookup[episode.id].channelTitle,
logo : f.data.episodesLookup[episode.id].logo,
channelLink: f.selfURI + y.urls.get({
'page' : 'channel',
'channel': f.data.episodesLookup[episode.id].channelId
}),
episodeLink: f.selfURI + y.urls.get({
'page' : 'program',
'program': f.data.episodesLookup[episode.id].programId,
'event' : episode.eventId
}),
titleAdd: i18n.tv['js-common'].reminderAdd()
});
}
// отрисуем эпизоды
if (episodes.length > 0) {
printHTML += Handlebars.templates.myFavoritesPrint({
dayOdd: (printDaysCounter % 2 === 0) ? 'true' : '',
titleDate: f.formatDate(f.data.days[d]),
episodes: episodes
});
printDaysCounter++;
episodesHTML += Handlebars.templates.myFavoritesDay({
titleDate: (!gotTitle) ? f.formatDate(f.data.days[d]) : '',
episodes: episodes
});
}
}
f.holder.find('.b-print').toggleClass('i-hidden', fffoundEpisodes === 0);
// отрисуем заголовок
f.holder.html(
Handlebars.templates.myFavoritesMain({
textMyPrograms : i18n.tv['js-common'].myPrograms(),
textPrintVersion: i18n.tv['js-common'].printVersion(),
textCalendar : i18n.tv['js-common'].calendar(),
// заголовок (сегодня)
calendarToday : calendarToday,
todayDate : _headerDate,
todayDateTitle: _todayTitle,
programs: f.data.programs
})
);
var episodesHolder = f.holder.find('.js-episodes').append(episodesHTML);
f.printHolder.find('.b-p-my__layout').append(printHTML);
// заглушка на случай, когда нет эпизодов
if (fffoundEpisodes === 0) {
// заглушка
episodesHolder.append(
Handlebars.templates.myFavoritesNoEpisodes({
noEpisodesText: i18n.tv['js-common'].noEpisodesText()
})
);
// уберём ненужный заголовок и календарь
var layoutLeft = f.holder.find('.l-page-layout__left');
layoutLeft
.find('.b-title').remove().end()
.find('.b-prog-calendar').remove().end()
.append('&nbsp;'); // Opera при отсутствии пробела ломает вёрстку
f.holder.find('.b-print').addClass('i-hidden');
}
parentHolder.html(f.holder);
// обновим статус только если у нас уже загружен календарь
if (y.calendar && typeof y.calendar.settings === 'object' ) {
$document.trigger('TV:updateReminderStatus');
f.renderRemindButtons();
}
};
/**
* Пересчитываем данные от ручки в нужный нам формат
*
* Эпизоды:
* 1) Создаём плоский массив эпизодов
* 2) Сортируем этот массив по времени
* 3) Группируем его по дням
*
* @param {Object} data.channels хеш таблица с описанием каналов
* @param {Array} data.programs программы с эпизодами
*/
f.prepareData = function(data) {
f.data = {};
f.data.episodesLookup = {};
f.data.episodes = [];
f.data.programs = [];
f.data.programsLookup = [];
var _programs = data.programs || [];
// счётчики
var p, pmax, e, emax;
// ссылка на эпизод
var _episode;
for (p = 0, pmax = _programs.length; p < pmax; p++) {
var prog = _programs[p];
// эпизоды могут отсутствовать;
var _episodes = prog.episodes && prog.episodes.episodes || []
// сгенерируем список программ
if ($.inArray(prog.id, f.data.programsLookup) === -1) {
f.data.programsLookup.push(prog.id);
f.data.programs.push({
id: prog.id,
title: prog.title,
link: f.selfURI + y.urls.get({
'page' : 'program',
'program': prog.id
}),
inactive: _episodes.length === 0 ? 'yes' : ''
});
}
// список эпизодов
for (e = 0, emax = _episodes.length; e < emax; e++) {
_episode = _episodes[e];
var _title = (prog.title === _episode.title)
? prog.title
: prog.title+'. '+_episode.title;
// 1) Создаём lookup таблицу с эпизодами
f.data.episodesLookup[_episode.id] = {
desc : _episode.description,
title : _title,
channelId : _episode.channelId,
channelTitle: data.channels[_episode.channelId].title,
programId : prog.id,
logo : data.images[p].media.img
};
// создадим массив с временем начала эпизодов
// чтобы потом по нему делать сортировку
f.data.episodes.push({
start : _episode.start,
finish : _episode.finish,
id : _episode.id,
eventId : _episode.eventId
});
}
}
// отсортируем массив по времени начала эпизода
f.data.episodes.sort(function(a,b){
return a.start - b.start
});
f.data.days = [];
f.data.daysLookup = {};
// сгруппируем по дням
// в массиве дней f.data.days будут ссылки на эпизоды из f.data.daysLookup
for (e = 0, emax = f.data.episodes.length; e < emax; e++) {
var d = moment.utc(Ya.tv.fromLocalTz(f.data.episodes[e].start))
var date = (d.hours() < 5)
? d.subtract('days', 1).format('L') // предыдущий теледень
: d.format('L');
var _index = $.inArray(date, f.data.days);
if (_index === -1) {
f.data.days.push(date);
f.data.daysLookup[date] = [];
_index = f.data.days.length -1;
}
f.data.daysLookup[date].push(e);
}
}
/**
* Обработка клика по звёздочке в рекомендательном блоке
* @param {DOM} el звёздочка, по которой произошел клик
*/
f.recommendationStarClick = function(evt, el) {
var button = $(el);
var checked = button.hasClass('b-star_state_checked');
var programId = button.data('id').toString();
evt.preventDefault();
// к этому элементу нужно будет скроллиться
f.scrollTo = button;
/**
* Функция отрисует кастомный alert, который позволит
* пользователю обновить список "моих" эпизодов
*/
var showSmartAlert = function() {
// если уже открыт попап, то не будем рисовать ещё один
if (f.smartPopupActive) {
return false;
}
var text = i18n.tv['js-common'].myListUpdated()+
' <a href="javascript:void(0)" class="updateDateLink">'+
i18n.tv['js-common'].myListUpdatedReload()+
'</a>';
$document.trigger('TV:alertShow', {
text: text,
autoHide: false,
positionAbsolute: true
});
var $elem = $('.b-alert');
// повесим бинд на ссылку обновления данных
$elem
.find('.updateDateLink')
.on('click.updateData', function(){
f.render();
$document.trigger('TV:alertHide');
f.smartPopupActive = false;
$document.trigger('TV:updateCalendar');
});
};
var parseResponse;
if (checked) {
// удаляем из моих
parseResponse = function(status) {
// удаляем передачу из избранного
button.removeClass('b-star_state_checked').attr({title: i18n.tv['js-common'].addToFavTitle()});
// помечаем эпизоды, как удалённые в нашей структуре данных
f.markAsDeleted(programId);
// покажем наш кастомный алерт
showSmartAlert();
};
y.favorites.remove(programId, parseResponse);
} else {
// добавляем передачу в мои
parseResponse = function(status) {
if (status === 'success') {
// дергаем ручку с новыми данными
$.ajax({
url : '/actions/my-favorites.xml?',
data : {region: Ya.tv.region},
dataType: 'json',
cache : false,
type : 'GET',
retry : 3,
timeout : 5000,
success: function(data) {
// поставим звёздочку
button.addClass('b-star_state_checked').attr({title: i18n.tv['js-common'].addedToFavTitle()});
// покажем наш кастомный алерт
showSmartAlert();
// распарсим новые данные от сервера
f.prepareData(data);
},
error: function(res) {
// TODO сделать обработку ошибок
$document.trigger('TV:alertShow');
}
});
}
};
y.favorites.add(programId, parseResponse);
}
}
return {
/**
* Инициализация мои программ
* @param {Object} data данные
*/
init: function(data){
// TODO подумать над этим
f.selfURI = window.location.protocol+'//'+window.location.hostname;
f.body = $('body');
// обработаем серверный json в удобоваримый вид
f.prepareData(data);
// отрисуем
f.render();
// повесим бинды
f.addBindings();
// кидаем событие в calendar.js
$document.trigger('TV:initCalendar');
// заставим ссылку на домик показывать домик
BEM.DOM.decl({name: 'b-link', modName: 'domik', modVal: 'true'}, {
onSetMod : {
'js' : function () {
this.__base.apply(this, arguments);
this.on('click', function () {
Lego.block['b-domik_type_popup'].login();
});
}
}
});
}
}
}());
// TODO убрать ajax запрос и сразу отдавать json в теле страницы
$.ajax({
url : '/actions/my-favorites.xml',
data : {region: Ya.tv.region},
cache : false,
timeout : 5000,
retry : 3,
dataType : 'json',
success: function(data) {
y.fav.init(data);
},
error: function() {
$(document).trigger('TV:alertShow');
}
});
}(Ya.tv, jQuery));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment