I've seen plenty of examples and questions on how to do this, but the issue is that they all rely on a massive database on the server-side. I figured I could try to move that to the client-side. Here's what I have:

(function() {
var data, pos, read, write, fcnt, tcnt, i, forums, threads, l, fid, id, t, allunread, post;
data = {forum:{},thread:{}};
if( localStorage[USERID+'.forumhistory']) {
         data = JSON.parse(localStorage[USERID+'.forumhistory']);
switch(FORUM_READ.mode) {
     // in the initialisation, FORUM_READ is an object with:
     //   mode: "index" for the list of forums, "forum" for an individual forum
     //         "thread" for an individual thread.
case "forum":
    threads = document.querySelectorAll('.f_thread');
              // each thread element has a "data-id" attribute indicating the thread's ID
              // and a "data-ts" attribute indicating the timestamp of the most recent post
    l = threads.length;
    fid = FORUM_READ.id;
    for( i=0; i<l; i++) {
        id = threads[i].getAttribute("data-id");
        data.thread[fid] = data.thread[fid] || {};
        data.thread[fid][id] = data.thread[fid][id] || data.forum[fid] || DATE;
        if( threads[i].getAttribute("data-ts")*1000 > data.thread[fid][id]) {
            threads[i].cells[0].children[0].src = "/img/misc/f_unread.gif";
        else delete data.thread[fid][id];
    data.forum[fid] = DATE;
    allunread = true;
    for( i in data.thread[fid]) {
        allunread = false;
    if( allunread) {
        delete data.thread[fid];
    // Fall through to deal with subforums
case "index":
    forums = document.querySelectorAll('.f_forum');
              // similarly to threads, forums have "data-id" and "data-ts" attributes
    l = forums.length;
    for( i=0; i<l; i++) {
        id = forums[i].getAttribute("data-id");
        data.forum[id] = data.forum[id] || DATE;
        if( forums[i].getAttribute("data-ts")*1000 > data.forum[id])
                           forums[i].cells[0].children[0].src = "/img/misc/f_unread.gif";
case "thread":
              // the last post, #f_post, has a "data-ts" attribute giving its timestamp
    post = document.getElementById('f_post').getAttribute("data-ts")*1000;
    fid = FORUM_READ.forum;
    id = FORUM_READ.id;
    if( data.thread[fid] && data.thread[fid][id] && data.thread[fid][id] < post) {
        data.thread[fid][id] = post;
localStorage[USERID+'.forumhistory'] = JSON.stringify(data);

Now, as far as I can tell this works. However, I haven't really been able to test much due to not having anyone else who can test for me (sometimes working alone sucks...). So I'm hoping that someone here will be able to spot any errors in my logic.

  • \$\begingroup\$ Please explain what you're code is doing before posting it. The title alone is not necessarily sufficient information for any random person on this site to know what you're attempting. \$\endgroup\$ – David Jun 23 '12 at 22:11
  • \$\begingroup\$ Basically the idea is that there is an image on each row that lists a thread or forum, defaulting to "f_read.png", which should be changed to "f_unread.gif" if there is an unread entry. \$\endgroup\$ – Niet the Dark Absol Jun 23 '12 at 23:19
  • \$\begingroup\$ What happens when your users delete their client side storage? Everything shows up as unread again? There's a reason for the "massive database on the server side" approach. \$\endgroup\$ – Corbin Jun 23 '12 at 23:55
  • 1
    \$\begingroup\$ @st-boost That's definitely one way to see it, however, if I were a user, I would not expect that behavior. Also, what about a different browser or different computer? Deleting isn't the only way. \$\endgroup\$ – Corbin Jun 24 '12 at 0:01
  • 1
    \$\begingroup\$ @Corbin you're right, this can break in completely normal scenarios - between that and the delay in rendering, I'd say a server-side solution is definitely preferable. But I still think of this as an unimportant feature, and therefore an acceptable compromise if server resources are limited. \$\endgroup\$ – delete me Jun 24 '12 at 0:05

No big issues that I can see, but here are a few tips.

When using for-in loops, add a hasOwnProperty check, like this:

for (var i in obj) {
    if (obj.hasOwnProperty(i) {
        // do stuff

Otherwise the loop will also iterate over properties of Object.prototype.

data can more simply and efficiently be declared like this:

data = localStorage[USERID + '.forumhistory'] ?
    JSON.parse(localStorage[USERID+'.forumhistory']) :

The switch statement might be more legibly written as a series of if else-if statements (with nesting), but that's mostly a stylistic choice.

data is, actually, the worst variable name there is. It is impossible to create a less specific name. I would suggest renaming it.

In loops where you iterate over an array, like this:

var i, l = forums.length;
for (i = 0; i < l; i++) {
    // use `forums[i]`

If you know every element in forums is truthy (not 0, null, undefined, false, or ""), you can use this loop, which is shorter and more efficient, and in my opinion more legible.

var i, forum;
for (i = 0; forum = forums[i]; i++) {
    // use `forum`
  • \$\begingroup\$ Okay, thank you for the tips. If I know for a fact that Object.prototype is never extended by any of my site's scripts, it is still worth adding that hasOwnProperty check? \$\endgroup\$ – Niet the Dark Absol Jun 24 '12 at 0:20
  • \$\begingroup\$ If you're certain Object.prototype will never change, then no. But are you really certain? What if you include a third-party script, like an ad? \$\endgroup\$ – delete me Jun 24 '12 at 0:24
  • \$\begingroup\$ I'm not using any frameworks (I hate the things), and any ads will be served in an iframe from a different domain (I have a centralised ad setup for all my projects) so it can't affect the parent window's stuff. \$\endgroup\$ – Niet the Dark Absol Jun 24 '12 at 0:26
  • 1
    \$\begingroup\$ @Kolink What about browser add-ons? What if the project gets passed off to somebody else, who doesn't know your code assumes Object.prototype is unmodified? In the end it's probably not going to matter, but I always feel uncertain knowing my code assumes anything about its environment. Also, I'm so happy I found somebody else who won't touch frameworks :) \$\endgroup\$ – delete me Jun 24 '12 at 0:31
  • \$\begingroup\$ Okay, understood. I'll make sure to add in the check. And yeah, it's always good to find a non-framework-user! Personally, I like to write my own code so that, when things go wrong, I know for a fact that it's my fault. \$\endgroup\$ – Niet the Dark Absol Jun 24 '12 at 0:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.