Created
August 14, 2012 19:28
-
-
Save jodaka/3352000 to your computer and use it in GitHub Desktop.
fav
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*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(' '); // 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