суббота, 1 сентября 2012 г.

Blogger: заменяем ссылки "Следующие" и "Предыдущие" на названия статей

Оригинал: это оригинал.

Вообщем-то, я собираюсь рассмотреть более общий случай подмены произвольной ссылки на заголовок страницы, но изначальная задача состояла именно в замене ссылок на предыдущую и следующую страницы в Blogger-е, поэтому в заголовок вынесена именно она. Существует несколько способов подменить ссылки на страницы заголовками этих самых страниц. Несколько навскидку:


На основе фида

Ищем нужные ссылки, выбираем заголовки из фида. Для упрощения решения задачи можно использовать библиотеку jQuery или подобную (я люблю mootools). Пример можно посмотреть здесь: http://www.tipsntech.net/2012/06/replace-older-and-newer-post-link-with.html. Я сам конкретно этот пример реализовать не пробовал, но причин ему не работать не вижу. В конце концов, работает же извлечение информации из фидов в моих виджетах похожих страниц и лучших статей. Достоинства: всё просто и достаточно очевидно, если, конечно, дело не доходит до необходимости пробираться через дебри объектов, возвращаемых фидом. Недостатки: должен быть фид (для Blogger-а, разумеется, есть). Нет фида, нет данных. То есть, например, получить заголовки внешних ссылок может быть уже не так просто.


Загрузка "на месте"

Ссылки "Следующие" и "Предыдущие" ведут на ваш же блог, так что содержимое страниц по этим ссылкам можно без проблем загрузить и прочитать прям тут же, не упираясь в ограничения безопасности (с внешними ссылками так не выйдет). Решение предложено тут: http://bloggerforum.ru/menyaem-ss-lki-pred-dushtie-sleduyushtie-na-nazvanie-statey-t708.html с использованием всё того же jQuery. Достоинства: ещё проще, чем метод #1. Недостатки: не будет работать с внешними ссылками, вместо одной страницы каждый раз будет загружаться три, при чём целиком. А если ссылок больше двух? Это ведёт к увеличению времени загрузки страницы для пользователя и увеличению нагрузки на сервера (да кому до этих серверов дело?). Именно эти обстоятельства побудили меня попробовать приспособить одну свою наработку для решения поставленной задачи.


Делаем это правильно

Специально для таких случаев, когда на сайте A нужно получить информацию с любого сайта (хоть с А, хоть с B), т.е. когда нужно обойти систему безопасности браузера, люди придумали JSONP. Не вдаваясь в подробности: вы делаете вызов внешнему сервису, он в ответ вызывает вашу javascript функцию, передавая в неё все нужные параметры, а вы затем обрабатываете их так, как нужно. Для нашей задачи получения заголовка страниц у меня уже давно есть скрипты, разбросанные по нескольким бесплатным хостингам. Что они делают? Они на вход получают URL страницы, название callback функции и id элемента DOM, с которым идёт работа, а в ответ вызывает callback функцию, передавая в неё данные о странице и id (пример). В скриптах реальизвано кэширование результатов, так что они не запрашивают страницы каждый раз, а берут данные из кэша. Пример callback функции есть в файле graddit-extras.js. Вот как всё работает:
Функция gradditReplacePreviousNextBloggerLinks() принимает в качестве параметров набор названий классов ссылок (через запятую), которые нужно заменить на заголовки. Если классы не заданы, то по умолчанию будут заменены ссылки с классами "blog-pager-newer-link" и "blog-pager-newer-link". Все найденные ссылки по одной передаются сервису, который для каждой из них вызовет callback функцию gradditReplaceLinkTitleCallback (присутствует в этом же файле).
function gradditReplacePreviousNextBloggerLinks() {
    var gradditGetElementsByClassName = function(className) {
        if (typeof document.getElementsByClassName == 'function') {
            return document.getElementsByClassName(className);
        } else {
            var allElements = document.getElementsByTagName('*');
            var foundElements = new Array();
            className = className.toLowerCase();
            for (i=0; i < allElements.length; i++) {
                var elementClasses = new Array();
                if (allElements[i].getAttribute('class')) {
                    elementClasses = allElements[i].getAttribute('class').split(' ');
                } else if (allElements[i].className) {
                    elementClasses = allElements[i].className.split(' ');
                }
                for (j=0; j < elementClasses.length; j++) {
                    if (elementClasses[j].toLowerCase() == className) {
                        foundElements.push(allElements[i]);
                    }
                }
            }
            return foundElements;
        }
    }
    var links = new Array();
    var gradditAggregateLinks = function(elements) {
        for (var i = 0; i < elements.length; i++) {
            if (elements[i].href != null) {
                var link = "";
                if (elements[i].id != null) {
                    link = [elements[i].id, elements[i].href];
                } else if (elements[i].name != null) {
                    link = [elements[i].name, elements[i].href];
                }
                if (link != "") {
                    links.push(link);
                }
            }
        }
    }
    if (arguments.length == 0) {
        gradditAggregateLinks(gradditGetElementsByClassName('blog-pager-newer-link'));
        gradditAggregateLinks(gradditGetElementsByClassName('blog-pager-older-link'));
    } else {
        for (var i = 0; i < arguments.length; i++) {
            gradditAggregateLinks(gradditGetElementsByClassName(arguments[i]));
        }
    }
    var servers = gradditGetInfoServersList();
    for (var i = 0; i < links.length; i++) {
        var server = servers[Math.floor(Math.random() * servers.length)];
        var d = document;
        var s = d.createElement('script');
        s.setAttribute('type','text/javascript');
        var src = server + "?url=" + links[i][1] + "&callback=gradditReplaceLinkTitleCallback&id=" + links[i][0];
        s.setAttribute('src', src);
        var h = d.getElementsByTagName('head').item(0);
        h.insertBefore(s, h.firstChild);
    }
}

Функция gradditReplaceLinkTitleCallback занимается исключительно тем, что ищет указанные ссылки и заменяет их innerHTML (какой бы он ни был) на заголовки страниц.
function gradditReplaceLinkTitleCallback(data, id) {
    var e = document.getElementById(id);
    if (e == null) {
        e = document.getElementsByName(id);
        if (e.length > 0) {
            e = e[0];
        } else {
            e = null;
        }
    }
    if (e != null) {
        if (data["title"] != undefined) {
            data["title"] = data["title"].replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\\\"/g, "\"");
        } else {
            data["title"] = "";
        }
        if (!data["title"].match(/^error 400 \(bad request\)/) && !data["title"].match(/^service unavailable/) && data["title"] != "") {
            var title = data["title"];
            e.innerHTML = title;
        }
    }
}
Есть ещё одна вспомогательная функция gradditGetInfoServersList, которая возвращает список адресов, где размещены скрипты. Чтобы распределить нагрузку, при каждом запросе сервер выбирается произвольно. Ничего особенно интересного. Вызов функции gradditReplacePreviousNextBloggerLinks желательно делать в самом конце страницы, например, перед </body> вот так:

  

или так (с вашими собственными классами, перечисленными через запятую):

  


У каждой ссылки должен быть id или name (в шаблонах Blogger уже есть id, делать ничего не нужно, но если это какие-то ваши собственные ссылки, то имейте в виду). Недостатки такого решения: вы зависите от внешних скриптов, размещенных на бесплатных хостингах. Постепенно я буду размещать скрипты на большем количестве хостингов, некоторые из них будут моими собственными, что должно увеличить надёжность решения. Достоинства подхода в том, что он универсален: вы можете заменить какие угодно ссылки, ведущие на какие угодно сайты, вы не зависите от фидов, а сами скрипты js не зависят ни от какого фрэймворка, вроде jQuery. Разумеется, вы можете ипользвать jQuery совместно с представленным методом, например, для вызова gradditReplacePreviousNextBloggerLinks после загрузки страницы:
$(document).ready(gradditReplacePreviousNextBloggerLinks);

Теперь немного украсим результат: дополним ссылки стрелками и жёстко зададим ширину, чтобы при слишком длинных заголовках ссылки не "уплывали". Я нашёл соответствующие стили в шаблоне (не забудьте включить галочку "Расширить шаблоны виджета") и заменил их на это:
.blog-pager-older-link, .home-link,
.blog-pager-newer-link {
  background-color: $(content.background.color);
  padding: 5px;
  max-width: 200px;
  overflow: hidden;
  display: inline-block;
  text-align: left;
}

.blog-pager-newer-link:before {
  content: "\2190\0020\0020\0020";
}

.blog-pager-older-link:after {
  content: "\0020\0020\0020\2192";
}

Пример конечного результата: http://fruitfulbookmarks-ru.blogspot.com/2011/11/fruitful-bookmarks.html (листайте в самый низ страницы). Вот и всё. Есть вопросы - пишите в комментариях; нашли ошибки - воспользуйтесь менеджером ошибок: просто выделите неверный текст, нажмите Ctrl + Enter и предложите верный вариант.

6 комментариев:

  1. Как ты быстро все сделал, сам взяться не успел, я еще в пути)). В конце недели попробую у себя, отпишусь.
    А насчет скриптов то их можно размещать на гугле, там то серваки хорошие.

    ОтветитьУдалить
    Ответы
    1. Да у меня уже почти всё было готово, надо было только в кучу собрать. Что касается размещения на гугловских серверах: статику (вроде js или css файлов) - без проблем, но есть же ещё и серверная часть, а она написана на PHP. У гугла есть appengine, но там только python и java, насколько я помню. Правда, что-то подобное на python-e у меня есть, но лень возиться с их appengine-ом было, да и квоты там маленькие, а размещать один и тот же скрипт по нескольку раз запрещено правилами. Скоро ещё серверов добавлю, для надёжности.

      Удалить
  2. Да, все отлично работает, лишь бы сервера "отвечали". Fruitfulbookmarks умная голова)) Пожалуй размещу ссылку на эту статью у себя.

    ОтветитьУдалить
  3. Вот только лучше код такой:
    < b:if cond='data:blog.pageType == "item"'>
    < script src='http://www.graddit.com/js/graddit-extras.js' type='text/javascript'/>
    < /b:if>
    Не принимает blogspot твой)

    ЗЫ после "<" пробелы убрать.

    ОтветитьУдалить
    Ответы
    1. Тьфу, кавычки преобразовал)

      Удалить
    2. Вот-вот! Всё было правильно, пока не сработала автозамена в движке блогспота :)
      Поправил.

      Удалить