fy47zw
Last Updated: February 25, 2016
·
4.299K
· msaspence
Image

Sharing HTML Fragment Caches Between Users With CSS

About this time last year 37signals wrote a great blog post about How Basecamp Next got to be so damn fast without using much client-side UI which has been a great resource for me over the last month as I've been working on a project with a huge amount of data on each page that is impossibly slow with out the "Russian Doll" style of caching they talk about in that post.

In order to increase the cache hit rate and decrease the cache size I've also followed their tip of sharing cache fragments between users. Their method uses JavaScript to retrieve the user id from a cookie and adjusts the dom accordingly. I'm not sure this is the best method, it relies on JavaScript for a start and depending on the size of you application means writing lots of JavaScript to manage the various things that people should and shouldn't see.

Instead I've opted to use css to adjust which content should and should not be shown to a user. As noted in the original post any html fragment that is shared between users must contain all the content (including links to actions that can be performed) that any individual user (including an anonymous user) needs to see.

To build on 37signals example here is a some html that represents a post that can have other users invited to comment on a post and other actions that only a specific user or admins can perform.

<div id='post-1' class='post'>
  <h1 class='title'>My Blog Post</h1>
  <div class='content'><p>Lorem ipsum dolar.</p></div>
  <form class='invite' action='/posts/1/invite'>
    <h4>Invite to comment</h4>
    <ul>
        <li><input name='invite[1]' type='checkbox'/>User 1</li>
        <li><input name='invite[2]' type='checkbox'/>User 2</li>
        <li><input name='invite[3]' type='checkbox'/>User 3</li>
    </ul>
    <input type='submit' value='Invite' />
  </form>
  <a href='/posts/1/edit'>Edit</a>
  <a href='/posts/1/spam'>Mark as spam</a>
  <a href='/signin'>Sign in to make changes</a>
</div>

In this example we want anonymous to see "sign in to make changes", admins to see the "edit" and "mark as spam", and the post author to see the "edit" action and "Invite to comment" but not themselves on the invite list. And of cause we want everyone to be able to see the title and content.

Assuming that the head and body tags of your document aren't cached we can place the classes and css that power this method.

For an anonymous user the body tag looks like this:

<body class='anonymous'>

For the author of the post (username: user1) the top of his document looks like this:

<head>
  <style>
    .for-user1 { display: block; }
    .not-for-user1 { display: block; }
  </style>
</head>
<body class='user1 signed-in'>

For an admin the top of the document looks like this:

<head>
  <style>
    .for-user2 { display: block; }
    .not-for-user2 { display: none; }
  </style>
</head>
<body class='user2 admin signed-in'>

We then need to add a few classes to our html:

<div id='post-1' class='post'>
  <h1 class='title'>My Blog Post</h1>
  <div class='content'><p>Lorem ipsum dolar.</p></div>
  <form class='invite not-for-most for-user1' action='/posts/1/invite'>
    <h4>Invite to comment</h4>
    <ul>
        <li class='not-for-user1'><input name='invite[1]' type='checkbox'/>User 1</li>
        <li><input name='invite[2]' type='checkbox'/>User 2</li>
        <li><input name='invite[3]' type='checkbox'/>User 3</li>
    </ul>
    <input type='submit' value='Invite' />
  </form>
  <a href='/posts/1/edit' class='not-for-most for-user1 for-admin'>Edit</a>
  <a href='/posts/1/spam' class='not-for-most for-admin'>Mark as spam</a>
  <a href='/signin' class='for-anonymous'>Sign in to make changes</a>
</div>

And finally add the CSS to power it all, this can be added to any stylesheet but should be added before your style tag that defines .for-user1 { display: block; }:

.not-for-most { display: none; }
body.anonymous .for-signed-in { display: none; }
body.signed-in .for-anonymous { display: none; }
.for-admin { display: none; }
body.admin .for-signed-in { display: block; }

Its also possible to include avatars for you current user. Let's say that we have a comment form that we want the current user's avatar next to (like Facebook has):

<form action='posts/1/comment' class='for-signed-in'>
  <img src='/avatars/user1.png' class='avatar' />
  <textarea name='comment'></textarea>
  <input type='submit' value='Comment' />
</form>

If we change the html to:

<form action='posts/1/comment' class='for-signed-in'>
  <div class='avatar current-user'></div>
  <textarea name='comment'></textarea>
  <input type='submit' value='Comment' />
</form>

And change our head for signed in users:

<head>
  <style>
    .for-user1 { display: block; }
    .not-for-user1 { display: none; }
    .avatar.current-user { background: url(/avatars/user1.png); }
  </style>
</head>
<body class='user1 signed-in'>

And in our stylesheet:

.comment.avatar {
  height: 30px;
  width: 30px;
  background-size: 30px;
}

Of couse if the image doesn't match the dimensions of the avatar and the browser doesn't support background-size (Opera Mini and IE < 9) the avatar is going to look a little funny so I think in this case JavaScript is the better option. Using our original comment form html (with img tag but with a default avatar url in the src) add the follow javascript to a script tag at the end of your body:

$('img.avatar.current_user').attr('src','/avatars/user1.png');
Say Thanks
Respond