Как я учился на своих хотелках

в 10:42, , рубрики: javascript, я учусь, метки: ,

Большинство наверняка начинали свой путь с чтения книжек и выполнения тестовых задачек, иной раз выискивая на профильных форумах варианты решения/улучшения. Повторялись одни и те же демки в разных вариациях, что сводилось к ленивому гуглению и скорейшему прохождению всех тестов ради перехода к изучению следующей главы, статьи и т.д. Я решил действовать как настоящий самурай — усложнить себе задачу придумыванием какой-нибудь хотелки и по ходу дела изучать потребовавшуюся к изучению матчасть.

Вначале искал вдохновения в тестовых задачках изучаемого курса по основам javascript, однако вскоре решил искать его на просторах часто посещаемых ресурсов. Так и был выбран Хабр.
Первой посетила идея прятать все картинки в постах и комментариях, так как зачастую они только мешали. Но кнопка для их отображения все же не помешала бы.
Загляну немного вперед и скажу, что почти сразу я пришел к необходимости разбиения кода на логические блоки, которых я выделил три — подготовка к работе, работа с изображениями, работа с ответами к комментариям. Каждому блоку был присвоен уровень сложности и начать я решил с того, что проще для закрепления ранее изученного материала и постепенного возрастания сложности задач.
Как прятать изображения долго не думал — использовать свойство «display: none» при загрузке страницы и по нажатию на кнопку возвращать свойство «display: block» для выбранных элементов. По DOM-у я проходился в поисках заранее подсмотренных в отладчике элементов, учитывая разные варианты вложенности тега img. Первый блок, который прятал все требуемые элементы, выглядел так:

    function initialHide() {

        function hideReply() {
            var replies = document.querySelectorAll('.reply_comments');
            for (var r = 0; r < replies.length; r++) {
                var reply = replies[r];
                reply.style.display = 'none';
            }
        }
        hideReply();

        function hideImages() {
            var images = document.querySelectorAll(".content > img, .content > div > img, .content > div > div > img, .content > a > img, .content > a > div > img, .content > table > tbody > tr> td> a > img, .content > table > tbody > tr> td> img, .content > ul > li > img, .content > ul > li > table > tbody > tr> td> img, .message > a > img, .message > img, .sidebar_right > .banner_300x500, .sidebar_right > #htmlblock_placeholder");
            for (var m = 0; m < images.length; m++) {
                var image = images[m];
                image.style.display = "none";
            }
        }
        hideImages();

    }

Теперь нужно было создать кнопку и применить к ней стили, ведь хотелось нативного вида как у хабра.
Тут я почитал о разных вариантах и набрел на неоднозначную статью. Вот что получилось:

    function imgPosts() {

        var imgs = document.querySelectorAll(".content > img, .content > div > img, .content > div > div > img, .content > a > img, .content > a > div > img, .content > table > tbody > tr> td> a > img, .content > table > tbody > tr> td> img, .content > ul > li > img, .content > ul > li > table > tbody > tr> td> img, .message > a > img, .message > img");
        var button = document.createElement("input");
        button.type = "button";
        button.className = "habraimage";
        button.value = "◄ Показать изображения";

        document.querySelectorAll(".main_menu")[0].appendChild(button);



        button.onclick = function () {

            for (var x = 0; x < imgs.length; x++) {
                var img = imgs[x];
                img.style.display = (img.style.display != 'none' ? 'none' : 'block');
            }

        };

        function addCSSRule(sheet, selector, rules) {
            if (sheet.insertRule) {
                sheet.insertRule(selector + "{" + rules + "}");
            } else {
                sheet.addRule(selector, rules);
            }

        }


        addCSSRule(document.styleSheets[0], ".habraimage", "position:fixed; right: 6%; z-index: 1; height: 2.45em; -webkit-border-radius: 6px; -moz-border-radius: 6px; border-radius: 6px; background-image: -webkit-linear-gradient(top, #eeeeee, #e1e1e1); background-image: linear-gradient(top, #eeeeee, #e1e1e1); background-image: -moz-linear-gradient(top, #eeeeee, #e1e1e1); background-repeat: repeat-x; border: 1px solid #d9d8d8; border-color: #d9d8d8 #cccbcb #aeaeae; text-shadow: 0 1px 0px rgba(255, 255, 255, 0.8); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1), 0px 1px 7px rgba(177, 180, 199, 1); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1), 0px 1px 7px rgba(177, 180, 199, 1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1), 0px 1px 7px rgba(177, 180, 199, 1);");

        addCSSRule(document.styleSheets[0], ".habraimage:hover", "background-color: #fcfcfc !important; background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fcfcfc), to(#e8e8e8));background-image: -webkit-linear-gradient(top, #fcfcfc, #e8e8e8); background-image: linear-gradient(top, #fcfcfc, #e8e8e8); background-image: -moz-linear-gradient(top, #fcfcfc, #e8e8e8);background-repeat: repeat-x; text-decoration: none;");
    }

Ремарка по поводу метода для работы со стилями — IE в планах был сразу, просто места под виртуальную машину для тестов не осталось на маленьком ssd.

Следующий блок был самым сложным и, одновременно, интересным. Нужно было не только скрывать ответы для каждого комментария, но еще и учитывать отсутствие ответов, чтобы не вводить в ступор пользователя. При создании кнопки учитывалось количество ответов и если оно было равно нулю, то кнопка для бока с таким комментарием не создавалась. Также количество ответов использовалось для отображения в теле кнопки. Но просто число не покажешь, нужно добавить существительное «ответ» и желательно склонять его в зависимости от числительных. Тут я начал было писать велосипед, но вовремя опомнился и подумал, что скорее всего это уже давно решили без меня. Все верно, решение быстро нашлось:

            function declOfNum(number, titles) {
                cases = [2, 0, 1, 1, 1, 2];
                return titles[(number % 100 > 4 && number % 100 < 20) ? 2 : cases[(number % 10 < 5) ? number % 10 : 5]];
            }

Ну а результатом стал третий блок, который отвечал за скрытие/отображения дерева ответов к комментарию с помощью кнопки, на которой отображалось текущее количество ответов для каждого комментария:

    hideReplies();

    function hideReplies() {

        function getChildrenByClassName(el, className) {
            var children = [];
            for (var i = 0; i < el.childNodes.length; i++) {
                if (el.childNodes[i].className == className) {
                    children.push(el.childNodes[i]);
                }
            }
            return children;
        }

        function addBtn() {
            var comments = document.querySelectorAll(".comments_list > .comment_item"),
                comment, combody;

            function declOfNum(number, titles) {
                cases = [2, 0, 1, 1, 1, 2];
                return titles[(number % 100 > 4 && number % 100 < 20) ? 2 : cases[(number % 10 < 5) ? number % 10 : 5]];
            }

            for (var i = 0; i < comments.length; i++) {
                comment = comments[i];
                var replies = comment.querySelectorAll('.reply_comments .comment_body');
                if (replies.length > 0) {
                    combody = getChildrenByClassName(comment, 'comment_body')[0];
                    if (combody) {
                        var btn = document.createElement("input");
                        btn.type = "button";
                        btn.className = "hidereplies";
                        btn.value = replies.length + declOfNum(replies.length, [' ответ', ' ответа', ' ответов']);
                        combody.appendChild(btn);
                    }
                }
            }

            function addCSSRule(sheet, selector, rules) {
                if (sheet.insertRule) {
                    sheet.insertRule(selector + "{" + rules + "}");
                } else {
                    sheet.addRule(selector, rules);
                }

            }

            addCSSRule(document.styleSheets[0], ".hidereplies", "height: 2em; margin-bottom: 2em; border-radius: 6px; background-image: -webkit-linear-gradient(top, #eeeeee, #e1e1e1); background-image: -o-linear-gradient(top, #eeeeee, #e1e1e1); background-image: linear-gradient(top, #eeeeee, #e1e1e1); background-image: -moz-linear-gradient(top, #eeeeee, #e1e1e1); background-repeat: repeat-x; border: 1px solid #d9d8d8; border-color: #d9d8d8 #cccbcb #aeaeae; text-shadow: 0 1px 0px rgba(255, 255, 255, 0.8); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 1);");

            addCSSRule(document.styleSheets[0], ".hidereplies:hover", "background-color: #fcfcfc; background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fcfcfc), to(#e8e8e8));background-image: -webkit-linear-gradient(top, #fcfcfc, #e8e8e8); background-image: linear-gradient(top, #fcfcfc, #e8e8e8); background-image: -moz-linear-gradient(top, #fcfcfc, #e8e8e8);background-repeat: repeat-x; text-decoration: none;");

            addCSSRule(document.styleSheets[0], ".hidereplies:active", "-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); background: #e1e1e1 !important; border: 1px solid #a4a7ac; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border-color: #a4a7ac #d2d3d4 #e1e1e1; text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);");

        }
        addBtn();


        function hideR() {
            var buttons = document.querySelectorAll(".hidereplies");

            for (a = 0; a < buttons.length; a++) {
                var btn = buttons[a];
                btn.onclick = function (event) {
                    var btn = event.currentTarget;
                    var comments = btn.parentNode.parentNode.querySelectorAll(".reply_comments");
                    for (var y = 0; y < comments.length; y++) {
                        var reply = comments[y];
                        reply.style.display = (reply.style.display != 'none' ? 'none' : 'block');
                    }
                };
            }
        }
        hideR();


    }

А обернул я все блоки в анонимную самовызывающуюся функцию.

Результат выложил на гитхаб.

Под катом можно посмотреть как Хабр выглядит после установки плагина

image

image

image

image

С созданием браузерных расширений в тонкости не вникал и сделал их после чтения официальной документации для разработчиков для Chrome и Safari. Чтобы установить в Chrome, придется скачать расширение и затем перетащить в открытый пункт настроек Расширения.

Всем, собравшимся с последними силами и дочитавшим до конца — спасибо. Прошу извинить за ляпы, портянки кода и некий сумбур. Надеюсь получилось поделиться опытом, главный урок которого — с интересными задачами учиться продуктивнее и веселее. Я только начинаю и буду очень рад конструктивной критике. Также с удовольствием скооперируюсь с кем-нибудь для продолжения работы над этим скриптом и парой остальных в ближайших планах — буду рад как опытному наставнику, так и такому же начинающему как я :)

Автор: Glebcha

Источник

Поделиться

* - обязательные к заполнению поля