JsRender as a Templating Solution
Cristian Merighi ()

Purely client-side implementation for a twitter feed-reader using JsRender along with "some" other javascript frameworks.
This article is obsolete. Some functionalities might not work anymore. Comments are disabled.
I want to conform myself to the confusing maelstrom ruling the world of javascript components and try to offer my personal two-cents solution about putting together (I mean PUTTING TOGETHER)
a purely client-side Twitter parser.
Let me introduce the guys who'll help me to get my job done. They're MANY, some of them are issue-prones, some of them are wonderful at some aspects but miss
(or gave up!) some others... (Fairly pissed off by the actual js world snapshot, does it show?)
- Microsoft Ajax 4 (String.format, Date.parseInvariant, Array.insert...)
- jQuery (Async calls, DOM manipulation, Utilities...)
- JsRender (Templating, Data formatting...)
- Other stuff (jQuery-relevant) of restricted utility...
Goal: using the Twitter REST Api to retrieve and display tweets in a fully comprehensive fashion (w/ avatars, hashtags, mentions and links),
using just DHTML technologies (sorry for having resusciated this 20th century term).
Dissecting the code, let's start with the declarative JsRender template:
JsRender Template
- <ul id="tweets" class="Tweets">
- <li><div class="Loader"></div></li>
- </ul>
- <script id="twitTmpl" type="text/x-jsrender">
- <li><img src="{{:user.profile_image_url_https}}" />{{:~renderTweet(#view)}}<span class="Date">{{:~timeago(created_at)}}</span></li>
- </script>
I've highlighted the bindings to the tweet entity properties: user along with created_at are respectively a complex and a simple/plain "property" for the json tweet representation,
on the other side #view wraps the whole entity object (looks like a DataItem
coming from the ASP.Net WebForms environment).
JsRender syntax: {{:[_property_].[_subproperty_]}} (please notice there's no spaces between braces and colons, nor between colon and text!).
{{:~foofunc([_propertypath_or_view_])}} to adjust the output before the very rendering (foofunc is a "view helper method").
Where do these helper methods go (in the following code snippet they're named renderTweet e timeago)?
View Helper Methods
- // "sort-of" converters
- $.views.helpers({
- // accepts the whole (wrapped) dataItem (aka "#view")
- renderTweet: function (view) {
- var tweet = view.data;
- var txt = tweet.text;
- var rettxt = '';
- var entities = tweet.entities;
- var trunks = [];
- if (entities) {
- // sorting the various entity trunks
- $.each(entities.hashtags, function (i, hash) {
- pushTrunk(trunks, { start: hash.indices[0], end: hash.indices[1], text: hash.text, type: 'hash' });
- });
- $.each(entities.urls, function (i, url) {
- pushTrunk(trunks, { start: url.indices[0], end: url.indices[1], text: url.url, type: 'url' });
- });
- $.each(entities.user_mentions, function (i, usr) {
- pushTrunk(trunks, { start: usr.indices[0], end: usr.indices[1], text: usr.screen_name, type: 'user' });
- });
- // assembling the final html string (rettxt)
- var lastIndex = 0;
- $.each(trunks, function (i, trunk) {
- switch (trunk.type) {
- case "hash":
- rettxt += txt.substring(lastIndex, trunk.start) + "<a target=\"_blank\" href=\"https://twitter.com/search/%23" + trunk.text + "\">#" + trunk.text + "</a>";
- break;
- case "url":
- rettxt += txt.substring(lastIndex, trunk.start) + "<a target=\"_blank\" href=\"" + trunk.text + "\">" + trunk.text + "</a>";
- break;
- case "user":
- rettxt += txt.substring(lastIndex, trunk.start) + "<a target=\"_blank\" href=\"https://twitter.com/" + trunk.text + "\">@" + trunk.text + "</a>";
- break;
- }
- lastIndex = trunk.end;
- });
- rettxt += txt.substring(lastIndex);
- } else rettxt = txt;
- return rettxt ;
- },
- timeago: function (createdAt) {
- // using timeago plugin (issues with UTC date and L10N)
- // Date.parseInvariant is exposed by Ajax 4
- return $.timeago(Date.parseInvariant(createdAt, 'ddd MMM dd HH:mm:ss +0000 yyyy'));
- }
- });
Please notice the specific framework lures in the code comments.
As a glue for bridging the declarative-template and the helping methods is the following javascript code-block
(please pay attention to the highlighted lines of code and to the respective - sometimes subtle - meaning reported in each preceding line of comment):
Glue Code
- function loadTweets() {
- // obey to cross-site-scripting issues
- $.support.cors = true;
- // format url using Ajax 4 String.format
- var rest_url = String.format("https://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=true&screen_name={0}&count={1}", 'cmerighi', 3);
- // jQuery ajax (fluent version)
- $.ajax({
- // be sure to specify dataType jsonp
- dataType: "jsonp",
- url: rest_url
- }).success(function (tweets) {
- // build up
- // FIRST jsrender template...
- $.templates({ twit: '#twitTmpl' });
- // ...THEN the DOM
- $('#tweets').empty().append($.render.twit(tweets));
- }).error(function (e) {
- alert(e.statusText);
- }).always(function(){ /* runs anyway */ });
- }
A fully-functioning DEMO is provided in order to allow to lurk the code
I've omitted here ;)
Take care. Bye.