В своем первом посте я описал почему выбрал именно вариант изучения JavaScript с выполнением поставленной себе задачи написать браузерное расширение. С того времени ни разу не посетила мысль о том, что все напрасно. По мере написания скрипта никуда не делись обязательные пункты обычного изучения — тематические статьи и обучающие материалы. Однако в данном случае был отличный катализатор — собственное воображение.
Так как я постоянно читаю Хабр, то какие-то, на мой скромный взгляд, шероховатости в юзабилити тут же находили место в списке непреодолимых желаний по улучшению.
А почетное место заняла идея отображения обновлений трекера без необходимости дополнительных переходов и, желательно, в одном блоке.
Конечно же вся соль была в том как получать данные, но нужно же где-то их показывать. Поэтому путь от простого к сложному начался с контейнера для получаемых обновлений:
var trackerLink = document.querySelector('.userpanel > .top > a.count');
trackerLink.href = '#tracker_updates';
var updates = document.createElement('ul');
updates.className = 'updates';
updates.style.display = 'none';
userpanel.appendChild(updates);
trackerLink.onclick = function (event) {
event.preventDefault();
updates.style.display = (updates.style.display != 'none' ? 'none' : 'block');
};
И логически перешел к написанию функции парсинга страниц трекера:
function getUpdates(url, getUrl) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.responseType = 'document';
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var tracks = xmlhttp.responseXML.querySelectorAll(url);
for (i = 0; i < tracks.length; i++) {
var post = tracks[i];
post.removeChild(post.firstElementChild);
updates.appendChild(post);
post.outerHTML = post.outerHTML.replace(/td/g, "li");
}
}
}
xmlhttp.open("GET", getUrl, true);
xmlhttp.send();
}
getUpdates('tr.new > td.event_type', '/tracker/subscribers/');
getUpdates('tr.new > td.mention_type', '/tracker/mentions/');
В итоге получался список обновлениями подписчиков и новыми упоминаниями о пользователе. Все верно, главное же — обновления постов. И вот с ними все сложилось несколько иначе, так как требовалось еще отображать счетчик новых комментариев для каждого поста:
(function () {
var xmlhttp = new XMLHttpRequest();
xmlhttp.responseType = 'document';
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var tracks = xmlhttp.responseXML.querySelectorAll('tr.new > td.post_title');
var commentCounts = xmlhttp.responseXML.querySelectorAll('tr.new > td.comment_count');
for (i = 0; i < tracks.length; i++) {
var track = tracks[i];
var commentCount = commentCounts[i].firstChild.nextSibling;
commentCount.className = 'count';
track.appendChild(commentCount);
updates.appendChild(track);
track.outerHTML = track.outerHTML.replace(/td/g, "li");
}
};
xmlhttp.open("GET", '/tracker/', true);
xmlhttp.send();
})();
А обернул запрос в анонимную самовызывающуюся функцию потому, что все описанные ранее части выполнялись в виде одной именованной функции.
Все бы хорошо, да наверняка не всем хочется каждый раз кликать кнопку отображения все скрытых картинок или разворачивать дерево ответов к комментариям. Вдруг нужна только пользовательская панель. И тут решено было наконец добавить управление настройками, чтобы комфорт был по-настоящему таковым. Почти сразу пришел к варианту с хранением настроек в local storage и тут процесс встал, так как правильнее было бы использовать родные для каждого из браузеров методы работы с локальным хранилищем. Начал с изучения Chrome Storage API и……им же и закончил, потому что слишком накладно получается строить луна-парк для каждого вместе счастливого будущего для всех и сразу. Появились некотрые идеи, но ввиду недостаточного опыта и просто ради совета бывалого обратился за помощью к spmbt. Он мне как раз подсказал элегантный вариант в виде трех простых функций:
var setLocStor = function(name, hh){
if(!localStorage) return;
localStorage['custom_'+ name] = JSON.stringify({h: hh});
},
getLocStor = function(name){
return (JSON.parse(localStorage && localStorage['custom_'+ name] ||'{}')).h;
}
,removeLocStor = function(name){localStorage.removeItem('custom_'+ name);}
Насколько я понимаю, в случае использования Storage API Хрома запросы будут выполняться асинхронно (прошу меня поправить, так как могу ошибаться).
Не буду мучать вас портянками с кодом и вкратце упомяну о том, что создавал именованные чекбоксы со значением по-умолчанию disabled, а для отключения фичи значение менялось на enabled. При клике по каждому чекбоксу выполняется запись пары ключ-значение в local storage. При вызове блока настроек читаются записи local storage и чекбоксам присваиваются соответствующие состояния (при значении ключа disabled чекбокс в состоянии false и если enabled — true). Функции, отвечающие за фичу «слушают» значение своего ключа в local storage и выполняются только если значение в состоянии disabled, то есть галочку «отключить» в настройках не установили.
И в завершении длинного рассказа небольшой бонус в виде отображение текущих кармы и рейтинга пользователя по клику на область с информацией о данном пользователе в его комментарии.
Ну вот и все, я убедился в том, что совмещать приятное с полезным в обучении не только можно, но и нужно. Учиться интереснее если есть стимул, который каждый день приходит в виде пары-тройки идей. Даже если результат трудов пригодился только тебе и нескольким приятелям, то нет абсолютно никакого повода думать о сомнительности мероприятия — ты получил не очередной изученный минимум, а новое увлечение.
Теперь развитие скрипта будет зависеть от свободного времени и новых предложений/хотелок, а полученные знания продолжу закреплять на практике и придумывать себе более сложные задачи для дальнейшего изучения JavaScript.
Спасибо всем, кто дочитал и, надеюсь, кого-то убедил повторить «путь самурая».
Буду признателен за конструктивную критику и рекомендации.
Ссылка на Chrome webstore
Ссылка на Github
Автор: Glebcha