2005-11-20

Blogger previous and next date links

It's time for my much sought after post on setting up a Blogger blog with Previous and Next date links. This is one way of doing it, it is not exactly the way my blog does it. (This code is much cleaner, and only does this particular thing. Hence you won't need huge racks of code that probably just break browsers I have not done enough testing with.)

Caveats

Before we begin, note that the present state of the Blogger tags are rather limiting and don't yet make it possible to actually solve this problem all out for post pages, without additional labour on your part (such as manualy maintaining the Previos and Next links in each post's text body -- doable though painful) -- the solution given below gives previous and next date links for your archive pages, not Previous / Next post pages. (They can of course be put on item pages too, but on a busy blogging day with three items posted, both the Previous and Next links will skip over a note or two, jumping straight to the nearest date in either direction.) There, you have been warned, now let's get to it!

Prerequisites

You must make sure your Blogger settings are right, since this method requires you to archive by day. Also, you have to republish the entire blog every time you add a post on a new date, or there will not be any "Next date" links for the days running up to the last post. This is because this solution is based on scanning the Archives secion of your blog for links, comparing the dates between the links available there and the dates of the post visited, to pick out the next and previous dates for the two navigation links. Crude, yet effective. This requires reposting the entire blog, because Blogger would not otherwise add today's archive page link to yesterday's archives section. (And it's either update all posts or just today's. Too bad for us; we'll just have to wait for the extra needless processing of our blog history.) One more thing: by implication, to manage to parse these dates, both your date header and archive date formats must contain all of year, month and date. "Sunday", or "November 20" is not enough.

Anyway, find the Archive settings, and set the Archive Frequency to "Daily":

Settings -> Archive -> Archive frequency: Daily


Then it's time to muck about with the template. Pick the Template tab in the view above, mark all contents, copy them and save them in a file somewhere safe. There is no undo button anywhere in sight, so you want to provide you the option, in case you're not satisfied with the results, or something goes very wrong. (I won't be able to help you, so you had better help yourself. I'm only going to say this once.)

Now seek out the <BlogDateHeader> tag; it should be somewhere in the <body> tag, and you might have to scroll through lots and lots of stylesheets before you find it. Mine looked like this before editing:

<BlogDateHeader>
<div class="DateHeader"><$BlogDateHeaderDate$></div>
</BlogDateHeader>

and we will change it a bit to toss in some additions, marked in bold face below:

<BlogDateHeader>
<div style="float:left; visibility:hidden; padding:0.5em;"
id="prev-date"><a accesskey="P">« Previous Date «</a>
</div>
<div style="float:right; visibility:hidden; padding:0.5em;"
id="next-date"><a accesskey="N">» Next Date »</a>
</div>

<div id="seen-date" class="DateHeader"><$BlogDateHeaderDate$></div>
</BlogDateHeader>

The style attributes and order of these tags isn't important; it's just a sketchy way of making them show up somewhat naturally in the page flow; your own styling should hopefully look a little better, to blend in better with the rest of your blog template. If you so prefer, you may skip the added divs and tuck the id and style attributes on to the <a> tags themselves, instead. The accesskey attributes give nice shortcuts to these links; Alt + P/N in most environments, Shift + Escape followed by P or N in Opera. In other Mac browsers, it's Control + P/N.

This adds the basis for where the navigation will end up. As you see, they will be hidden by default -- the mentioned script code will turn them on, when there is something to be clicked for each link. If you always want both links visible, even when unclickable, delete the "visibility:hidden;" part. Should you want just the key bindings, but not the visible links, substitute with "display:none;" instead.

Next, we must make sure the archive section is adressable by the script. Seek out (or add anew, if there is none) the <BloggerArchives> tag. Mine looked like this:

<BloggerArchives>
<a href="<$BlogArchiveURL$>"><$BlogArchiveName$></a> /
</BloggerArchives>

and after our modifications, it will look like this:

<div id="archive"><BloggerArchives>
<a href="<$BlogArchiveURL$>"><$BlogArchiveName$></a> /
</BloggerArchives></div>

The only bit that matters about it is that there is the <a> tag above somewhere in it and that it is surrounded by a tag with the id="archive" attribute. Below this we will add the big portion of code that does the links. Optionally, you may stow all but the leading makePrevNextLinks() line away somewhere up in your <head> tag, if you prefer. Just make sure that the makePrevNextLinks() line appears in the page flow below the above sections, and things will run just fine.

The final blow is the large chunk of code below. Tune it to use your date format (you may want to open up the Settings / Formatting page in a different browser window to find out what date format settings you use, in case you forgot. The two select boxes below need to use the same format (though they won't tell the same date, unless you are one of the few readers who happen to catch this note today, the same day I wrote it), or the code will not work. When you pick a date format, the first two lines of the code below changes accordingly; should you later feel you want to use some other date format, come back here and readjust them to see what values to put there.

Add the huge <script> block -- if you just want it on the archive pages where it always works reliably, encase it in an <ArchivePage>...</ArchivePage> container, and it won't appear on the main page and item pages. If your choice of Date Language isn't English and either of your date formats includes a month name, also be sure to edit the monthNames variable to read exactly (case matters!) how month names are listed in your language of choice, separated by the vertical bar character. Once you are done, save the template and republish your blog, and voilà -- Previous and Next buttons!

Settings -> Formatting -> Date formats



<script type="text/javascript">
var monthNames = 'January|February|March|April|May|June|July|' +
'August|September|October|November|December';
var dateHeaderParser = getDateHeaderParser( 1 );
var archiveParser = getArchiveParser( 9 );
makePrevNextLinks(); // Don't change anything from here onwards:

function makePrevNextLinks()
{
var prevNode = document.getElementById( 'prev-date' );
var seenNode = document.getElementById( 'seen-date' ), seen;
var nextNode = document.getElementById( 'next-date' );
if( !seenNode ) return alert( 'No tag with id="seen-date" found!' );
if( !(seen = dateHeaderParser( seenNode.innerHTML )) )
return alert( 'Failed to parse date "'+ seenNode.innerHTML +
'"; did you really set the right format?' );
seen = seen.getTime();
var all = getDates(), prev, next;
for( next in all )
if( next == seen )
{
next = null;
continue;
}
else if( next > seen )
break;
else if( next < seen )
{
prev = next;
next = null;
}
if( prev && prevNode )
{
if( prevNode.noveName != 'A' )
prevNode = prevNode.getElementsByTagName( 'a' ).item( 0 );
prevNode.href = all[prev];
prevNode.style.visibility = 'visible';
}
if( next && nextNode )
{
if( nextNode.noveName != 'A' )
nextNode = nextNode.getElementsByTagName( 'a' ).item( 0 );
nextNode.href = all[next];
nextNode.style.visibility = 'visible';
}
}

function getDates()
{
var ar = document.getElementById( 'archive' );
if( ar )
{
var all = ar.getElementsByTagName( 'a' ), dates = {}, i;
for( i=0; i<all.length; i++ )
{
var link = all[i];
var date = archiveParser( link.innerHTML );
if( date ) dates[date.getTime()] = link.href;
}
return dates;
}
}

function getArchiveParser( formatNo )
{
var fmt = { 0:'MonthName Day, Year', 1:'MonthNo/Day/Year',
2:'MonthNo\\.Day\\.Year', 3:'YearMonthNoDay',
4:'Day\\.MonthNo\\.Y2', 5:'Year-MonthNo-Day',
/*6:'MonthName Day',*/ 7:'MonthName Day, Year',
8:'Year/MonthNo/Day', 9:'MonthNo/Day/Y2',
10:'Y2_MonthNo_Day', 11:'Day MonthName Year',
12:'Day MonthName', 13:'Day/MonthNo/Year',
14:'Day/MonthNo/Y2' }[ formatNo ];
return fmt ? getDateParser( fmt ) : alert( 'Archive date format type ' +
(formatNo ? formatNo + ' not supported! (Year needed?)' : ' not given!') );
}

function getDateHeaderParser( formatNo )
{
var fmt = { 1:'MonthName Day, Year', 2:'MonthNo/Day/Year',
3:'MonthNo\\.Day\\.Year', 4:'YearMonthNoDay',
14:'Year/MonthNo/Day', 6:'Year-MonthNo-Day',
5:'Day\\.MonthNo\\.Y2', 7:'MonthNo\\.Day\\.Year',
/*8:'Weekday', 10:'MonthName Day',*/
12:'MonthName Day, Year', 18:'Day MonthName Year',
23:'Day MonthName, Year' }[ formatNo ];

return fmt ? getDateParser( fmt ) : alert( 'Date header format type ' +
(formatNo ? formatNo + ' not supported! (Year needed?)' : ' not given!') );
}

function sortNumeric( a, b )
{
return parseInt( a ) - parseInt( b );
}

function findInArray( array, value )
{
for( var i=0; i<array.length; i++ )
if( array[i] == value ) return i;
return -1;
}

function getDateParser( format )
{
var what = { Year:'\\d{4}', Y2:'\\d{2}', MonthName:monthNames,
MonthNo:'\\d{1,2}', Day:'\\d{1,2}' };
var re = format, monthNo = {}, where = {}, order = [], tmp, i, type;
for( i=0, tmp = what.MonthName.split( '|' ); i<tmp.length; i++ )
monthNo[tmp[i]] = i;
for( type in what ) // for each match type,
if( (tmp = format.indexOf( type )) >= 0 ) // if used in this format,
{
where[tmp] = type; // store away its match position, to find out
order[order.length] = parseInt( tmp ); // which paren matched what
re = re.replace( type, '('+ what[type] +')' ); // and fix the regexp
}
for( i=0, order = order.sort( sortNumeric ); i<order.length; i++ )
order[i] = where[order[i]]; // map back to mnemonics, again
var getYear = function( match )
{
var where = findInArray( order, 'Year' ) + 1;
if( where ) return parseInt( match[where], 10 );
where = findInArray( order, 'Y2' ) + 1;
if( !where ) return (new Date).getFullYear();
var year = parseInt( match[where], 10 ) + 1900;
if( year < 1990 ) year += 100;
return year;
};
var getMonth = function( match )
{
var where = findInArray( order, 'MonthName' ) + 1;
if( where ) return monthNo[ match[where] ];
where = findInArray( order, 'MonthNo' ) + 1;
if( where ) return parseInt( match[where], 10 ) - 1;
return (new Date).getMonth();
};
var getDate = function( match )
{
var where = findInArray( order, 'Day' ) + 1;
if( where ) return parseInt( match[where], 10 );
return (new Date).getDate();
};
re = new RegExp( '\\b'+ re +'\\b', 'i' ); // make it a real RegExp
return function( raw )
{
var match = re.exec( raw );
if( match )
return new Date( getYear(match), getMonth(match), getDate(match) );
};
}
</script>

26 comments:

  1. Very nice!
    How did you make your calendar?

    ReplyDelete
  2. ;) I'm still waiting for the IE version of the script

    ReplyDelete
  3. Oh men! I re-invent the wheel :D
    Take a look this example http://adivina.blogspot.com/2004_03_01_adivina_archive.html

    Just I prefer monthly, not daily. I am working on the explain post, i think is more easy than yours.

    Anyway, great job!
    Mario

    ReplyDelete
  4. I'm trying to do it in my blog: luison-off.blogspot.com But I don't get it! I think I followed every step correctly... But the problem is that if you open the page it will say: "No tag with id='seen date' found!" I don't know what to do... I already having problems with the calendar too... :S it was saying NaN NaN NaN everywhere... Could you please help me??? Thank you very much!!!!

    ReplyDelete
  5. It's me again... jejeje After a long time trying and trying... finally I guess I've fixed some of the problems. Now it doesn't say nothing worng when you open it.. and the calendar it's working again!... But the problem now is that the previous and next links don't work. Really I'm getting crazy with this... :S Could anybody help me please? Thanks a lot.

    ReplyDelete
  6. Your page misses the makePrevNextLinks function definition, which takes up most of this post; you try to call it without having defined it somewhere first. Or, if you have put it in an external script somewhere, you try to call it prior to having included that script in the page, which is the same thing to a web browser.

    When ripping out a script tag to an external file, by the way, lose the leading <-- and trailing //--> -- I'm somewhat surprised to see the calendar work even with them intact. I would not expect all browsers to handle that syntactic error, though.

    ReplyDelete
  7. Ok. Actually it wasn't missing the "makePrevNextLinks" function definition... It was in the bottom of the page... but I fixed it and put it in the head all together. Anyway I still having problems... it says "No tag with id:"seen-date" found!"... I will fix the calendar later... I'm thinking to put it in my own server... Thanks a lot!

    ReplyDelete
  8. Thank you so much! I know nothing about script but your explanation was very easy to follow. I had to fiddle with a few things, but it all worked out and now I have previous and next links. Thanks again!

    ReplyDelete
  9. Thanks dude, this'll really come in handy with my scanlation blog

    ReplyDelete
  10. How to copy the code so I don't have to type all of them ???
    thanks

    ReplyDelete
  11. Thank you so much for figuring this beautiful solution and for sharing it with easy to follow instructions! *appreciative smile*

    I had a bit of a struggle as IE wouldn't behave with the visiblity property. I did a shameless hack of making the default colours the same as my background (in lieu of making the links hidden) and then assigned the appropriate colour property when there was a link present. Not elegant but it works.

    Thanks again!

    ReplyDelete
  12. hi there! if I want the previous next links on the main page, what do I encase the long script in? thanks!

    ReplyDelete
  13. I believe you might be looking for the MainPage or even MainOrArchivePage tag for that, but you will probably also have to take your time to understand and retailor the code to work in that environment. Best of luck with that; I am afraid I won't invest time in doing it myself.

    ReplyDelete
  14. Thanks for all those amazing tools that have made my blogger life a whole lot easier (especially the catogerizer).

    I was wondering, in your script here : "Also, you have to republish the entire blog every time you add a post on a new date, or there will not be any "Next date" links for the days running up to the last post."

    So, my question is - is there a script that would work this way : when I press "publish" for my new post, would first publish my new post and then republish the whole blog? that would take care of the only "downfall" this script has. Maybe this could be embedded into one of your already existing scripts

    ReplyDelete
  15. Oh! My God!
    Your blogger is very nice,especially sidebar calendar process so smart!
    I like your blogger template!
    Thank you!
    I in your blogger,learn Information is very much!
    So sorry...
    I come from Taiwan,english isn't good...
    I wish you understand my type...^^|||

    ReplyDelete
  16. This's blogger cheat is what I've been waiting for so long. But I just don't get on the last 3 paragraphs. Actually, where I've to put the script in?

    I've put it inside the head section but got the error "No tag with id='seen date' found!" and the script doesn't work.

    and where's the ArchivePage tag? My theme doesn't has it. Create it my self? in which section?

    Please help. Thanks..

    ReplyDelete
  17. hi this is something ive been looking for a terribly long time. i have a question though. in this script the archives links all show up. if i wanted them to appear in a drop down list can you tell me what code i should use? thanks in advance.

    ritu

    ReplyDelete
  18. This script does not (and should not) address that need. You may wish to browse around Freshblog and perhaps pay most attention to its wiki of blogger hacks.

    ReplyDelete
  19. thanks johan. thats been a BIG help :)

    ReplyDelete
  20. I love this hack and appreciate folks like yourself that share your gift of mastering these codes with everyone free of charge.

    It works for me wonderfully in firefox, but is invisible and does not work in IE 6 or 7. Any thoughts that I may have missed in the comments above, when you have the time.

    ReplyDelete
  21. Thank you very much :)
    I finally got what I've been looking for :)

    ReplyDelete
  22. I've been looking for this hack for ages! Thankyou! Anyway I'm having serious problems to get it working. Just like ax2m said, I'm not sure if I was supposed to place the big block in the "head", and I keep getting the alert "no tag with id=seen-date" :( The "next" and "previous" words doesnt show up until I remove the visiblity feature, and still they dont link anything. Hope someone can help :/

    ReplyDelete
  23. I get that alert, "no tag with id=seen-date" as well. What does that mean? What am I doing wrong?

    ReplyDelete
  24. This tutorial was written two years ago for a Blogger template system that doesn't match the one in use today. My best suggestion is to seek more recent hotwos elsewhere on how to achieve similar things. Freshblog mentioned above is probably the best starting point.

    ReplyDelete
  25. I'm using the classic template though. Does that matter? Thanks for your help.

    ReplyDelete

Limited HTML (such as <b>, <i>, <a>) is supported. (All comments are moderated by me amd rel=nofollow gets added to links -- to deter and weed out monetized spam.)

I would prefer not to have to do this as much as you do. Comments straying too far off the post topic often lost due to attention dilution.

Note: Only a member of this blog may post a comment.