вторник, 11 сентября 2012 г.

Blogger: replacing "Older" and "Newer" links with pages titles.

Original: this is a translation of one of the posts from this blog.

I've recently faced with a problem of replacing Bloggers "Older" and "Newer" links with their titles. Quick research showed that this is a common problem and that everybody seem to be doing it in a wrong way! Not fair. Below is the only right solution for that. Actually, this will work with any kind of links, not just Blogger, but the initial task was to make it for Blogger, so all example below are for this platform. There are several possible solutions you can think of:


Based on information from feed

Locate links, take information from the feed. To make it simple we can use jQuery or similar library (I prefer mootools). Somebody already did this: http://www.tipsntech.net/2012/06/replace-older-and-newer-post-link-with.html. I haven't tried, but I don't see a reason why it can't be working. My other widgets are using something similar and working fine, after all (related posts widget and top posts widget). Advantages: it's simple and obvious (of course if you don't have to trudge through the objects returned by the feeds). Disadvantages: you need feed (there is for Blogger, no issues). No feed no data. What does it mean? You, probably, couldn't get titles of the external pages that easily.


Loading "in place"

"Older" and "Newer" pages belong to the same blog, so the content of that pages can be easily loaded without facing same-origin policy restrictions (it will not work with external links). Examples of the solution? Lots of them. Usually using jQuery. Advantages: even more simple to implement. Disadvantages: as mentioned, it will not work with external links; it also forces browser to load 3 pages instead of 1. And what if there's more links you want to load? Users won't like it. Neither of the solutions is good enough, so I decided to write my own and do it right at this time.


Doing it right

For the cases when on a website A you need to get information from any other sites (when you need to "break" the same-origin restrictions) people invented JSONP. Long story short, you make a request to external service and that service calls your JS function on your page (with necessary parameters when applicable) and that functions does the stuff. For our task I've written a script and deployed it to several free webhostings. What it does? It gets URL, name of a callback function and id of a DOM element (you'll need that ID in many cases, trust me); then it calls that callback function passing page data into it (example). It caches results, so there are no calls to your pages every time you make a request. Callback function example you can find in graddit-extras.js. How it works:
Function gradditReplacePreviousNextBloggerLinks() takes names of the classes of the links separated by comma; these links will be replaced with corresponding pages titles. If nothing is passed then the default Blogger classes will be used ("blog-pager-newer-link" and "blog-pager-newer-link"). All the links that have given classes are passed to the script that calls our callback function gradditReplaceLinkTitleCallback for each of them.
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 searches links and replaces their innerHTML with the titles.
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;
        }
    }
}
There's one more function called gradditGetInfoServersList. It returns list of servers where the script is available. To avoid overloads server is picked randomly every time. Nothing really interesting. You'd need to call gradditReplacePreviousNextBloggerLinks at the very end of your page right before </body> tag, like this:
<b:if cond="data:blog.pageType == &quot;item&quot;">
  
</b:if>
or like this (with your own links classes):
<b:if cond="data:blog.pageType == &quot;item&quot">
  
</b:if>

Each link should have an id or name attribute (Blogger links have id, no worries; if that's not Blogger you're using you shouldn't forget about it). Disadvantages: you rely on the external scripts deployed to free hostings. I'll be adding new servers (and some of them will be my own) eventually, so it wouldn't be that much of headache. Advantages: it's flexible, you can work with any kind of links: internal and external; no feeds required; none of the heavy frameworks, like jQuery, is necessary. Of course, you can combine it with jQuery to initiate the process on page load like this:
$(document).ready(gradditReplacePreviousNextBloggerLinks);

Now lets make result look nicer: add arrows and limit the width to not allow links to take too much space. I've found corresponding styles (you might need to tick "Expand Widget Templates" checkbox) and replaced them with the following:
.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";
}

Result you can see here: http://fruitfulbookmarks-en.blogspot.com/2011/11/rating-widgets-for-websites-blogs-and.html (scroll to the bottom of the page). That's it! If you have questions, write a comment; found mistakes - use errors report widget - just select wrong text, press Ctrl + Enter and give proper one.

Комментариев нет:

Отправить комментарий