Self-Replacing Script Blocks for Dynamic Lists

2012-01-17 8:52

On contemporary websites and web applications, it is extremely common task to display a list of items on page. In any reasonable framework and/or templating engine, this can be accomplished rather trivially. Here’s how it could look like in Jinja or Django templates:

  1. <ul>
  2. {% for item in items %}
  3.     <li>{{ item }}</li>
  4. {% endfor %}
  5. </ul>

But it’s usually not too long before our list grows big and it becomes unfeasible to render it all on the server. Or maybe, albeit less likely, we want it to be updated in real-time, without having to reload the whole page. Either case requires to incorporate some JavaScript code, talking to the server and obtaining next portion of data whenever it’s needed.

Obviously, that data has to be rendered as well, and there is one option of doing it on the server side, serving actual HTML directly to JS. An arguably better solution is to respond with JSON or similar representation of our items. This is conceptually simpler, feels less messy and is potentially reusable as a part of website’s external API. There is just one drawback: it forces rendering to be done also in JavaScript.

While it isn’t a hindrance by itself (there are many great tools to help, including Mustache or – sadly discontinued – jQuery Templates), it raises an issue of code duplication. Ideally, we would still want some initial data to be displayed just when the page itself is loaded. And at first, this seems as to require both server-side and client-side rendering functions… I hope I do not really have to explain why it would be a mighty bad idea to create, use and (try to) maintain them.

Client is king

That’s why we would opt for rendering exclusively in the browser. Fortunately, it is not only possible, but also quite easy if we already have the logic for handling AJAX calls. Using it, we can prepare initial items in JSON format and put them directly inside some <script> block:

  1. <script type="text/javascript">
  2.     $(document).ready(function() {
  3.         var items = {{ items|json }};
  4.         renderItems($('#items'), items);
  5.     });
  6. </script>

Here, the |json filter represents the before mentioned logic, while renderItems draws the objects-turned items as children of given element. What it does inside can vary from manipulating HTML strings directly (eww!), creating DOM objects (slightly better) or invoking some JS templating engine (best).

Striving for modularity

A simple solution, outlined above, looks sufficient for many practical applications. It is far from ideal, though. For one, it suffers from an acute disconnection, since the actual place where the data is displayed ($('#items')) can be arbitrarily far from the <script> tag. But more importantly, it scales poorly and lacks modularity: should we need to use our list on more than one site, we may need to carefully tailor several moving parts – such as the '#items' selector.

Incidentally, both of those issues can be resolved at once. Let’s create a self-replacing <script>: quite similar to the one above, but with some interesting twists. Rather than referencing some DOM element via hard-coded selector, it shall render the items “where they stand”: exactly where the very <script> is located. And just as a final touch, the script will even remove itself from the DOM tree when it’s no longer needed.

The solution…

Sounds like a pretty clever trick, but it’s not exactly complicated at all. In its entirety, it can look somewhat like this:

  1. <script type="text/javascript">
  2.     (function() {
  3.         var $script = $('script').last();
  4.         $(document).ready(function() {
  5.             var items = {{ items|json }};
  6.             renderItems($script.parent(), items);
  7.             $script.remove();
  8.         });
  9.     })();
  10. </script>

What makes it work is the fact that var $script = ...; line lies outside of the $document.ready function. Thanks to that, it is executed as soon as the <script> block is parsed – before the rest of document is processed, and way before the $(document).ready callback is invoked.
At that early time, this script is therefore the last <script> element in the DOM tree. In other words, it’s exactly what $('script').last() will return! What we are doing in the first line is therefore nothing else, but obtaining a reference to the enclosing <script>.

How’s that useful, though?… Well, if we have indeed dropped this <script> right where we wanted our dynamic list to display, its parent() would be the element to render our items in. And because we don’t mind cleaning up after ourselves when done, we finish this by remove()-ing the script. Nice and tidy.

…and how to use it

What makes it a pretty powerful technique is easy and flexible integration with modern templating engines. We can almost effortlessly made this snippet into a reusable template part, such as Jinja macro:

  1. {% macro render(items) %}
  2. <script type="text/javascript">
  3.     (function() {
  4.         var $script = $('script').last();
  5.         $(document).ready(function() {
  6.             var items = {{ items|json }};
  7.             renderItems($script.parent(), items);
  8.             $script.remove();
  9.         });
  10.     })();
  11. </script>
  12. {% endmacro %}

The final usage would be then extremely clean and simple:

  1. <div class="items">
  2.     {{ render(items) }}
  3. </div>
Be Sociable, Share!
Be Sociable, Share!
Tags: , , , , , , ,
Author: Xion, posted under Applications, Internet »

Adding comments is disabled.

Comments are disabled.

© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with