Let’s Talk JS Markup Templates

So today I wanted to spotlight a neat JS library I found while doing some client work called
Markup JS by Adam Mark:
https://github.com/adammark/Markup.js/

What is Markup JS?

Well to answer that question, it might be better to bring in a real world need first.
Lets say we are building a web application, or even a really simple web service between frontend and backend using XHR (ajax) only.
The first thing that should come to your mind when hearing XHR is what will the server return to us frontend developers?

In real world conditions, you typically run into two flavors of returned data from server side to frontend:

  • Full fledged markup to be dumped on the page.
  • JSON data to then be parsed by frontend code and then dumped on the page.

After years of working in this industry I can tell you here and now, you ALWAYS want serverside to return you data in JSON format, never ever full fledge markup.

In almost 100% of the time, I’ve noticed that when you run into a situations of server returning anything other than JSON in XHR service(s) can ultimately be boiled down to a developer not wanting to take the extra steps. However what most developer fail to realize is that anything other than JSON being sent over XHR is a huge a waste of data transfer (think remote mobile devices YIKES!), and to be honest is just a sloppy delivery. Sloppy because it leaves other developers in spot where they no longer can change the returned markup without editing backend route/file or method… ugh

This is where Markup JS comes into save our lives as frontend developers.
Markup JS is a library that frontend developers the power they need to construct really simple, clean but powerful JSON parsing markup templates.

Let’s take this returned data from server for instance:

{
    "items": [
        {
            "title":"Display Text One",
            "class": "some-class other-class"
        },
        {
            "title":"Display Text Two",
            "class": "some-class other-class"
        },
        {
            "title":"Display Text Three",
            "class": "some-class other-class"
        }
    ]
}

We can then construct a simple Markup JS template:

    {{items}}
  • {{title}}
  • {{/items}}

Next, we can either save this template out a file in your project file system, or continue to maintain templates in JS source file. I recommend saving templates out as individual files to completely keep frontend templates and JS source clean and separated. I also recommend using .txt as your template file type as it is the most commonly used MEME type on all web servers.

For pre-compile environments where you can import files (webpack, gulp etc):

import simpleTemplate from 'raw-loader!./simpleTemplate.txt';
console.log(Mark.up(simpleTemplate, returnedJSONData));

For non pre-compile environments:

var simpleTemplte = "
    {{items}}
  • {{title}}
  • {{/items}}
"; console.log(Mark.up(simpleTemplate, returnedJSONData));

In either approach, we should get the following markup back:

  • Display Text One
  • Display Text Two
  • Display Text Three

Ok so now that we got a good understanding of what markup JS is, lets take a look under the hood of the available API.

Object & Array notation

You can use both object or array notation while constructing your Markup JS template(s).

var data = {
    name: "John Doe",
    addr: {
        street: "1 Maple Street",
        city: "Pleasantville",
        zip: {
            main: "12345",
            ext: "6789"
        }
    }
};

var template = "{{name}} lives at {{addr.street}} in {{addr.city}}.";
var result = Mark.up(template, data);
var data = {
    name: "John Doe",
    colors: ["Red", "Blue", "Green"]
};

var template = "Favorite color: {{colors.0}}";
var result = Mark.up(template, data);

Loops & Loop counters

Of course you can loop data in Markup JS, and even access the counters or lengths of objects and arrays

var context = {
    name: "John Doe",
    brothers: ["Jack", "Joe", "Jim"]
};

var template = "
    {{brothers}}
  • {{.}}
  • {{/brothers}}
"; var result = Mark.up(template, context);
var template = "{{sisters}} {{#}}-{{name.first}} {{/sisters}}";

Pipes

Pipes are really cool and for the most part allows you to condition your Markup JS template(s) and boy are there a ton of pipes:

  • empty (obj): Test for an empty array, empty string, null, undefined, or 0. {{if apples|empty}}
  • notempty (obj): Test for the presence of a value. {{if apples|notempty}} or simply {{if apples}}
  • more (obj, n): Test if a number, iterator, or array is greater than n. {{if articles|more>100}} {{if #|more>10}}
  • less (obj, n): Test if a number, iterator, or array is less than n. {{if age|less>21}}
  • ormore (obj, n): Test if a number, iterator, or array is greater than or equal to n. {{if age|ormore>18}}
  • orless (obj, n): Test if a number, iterator, or array is less than or equal to n. {{if age|orless>55}}
  • between (obj, n1, n2): Test if a number, iterator or array is between n1 and n2, inclusive. {{if age|between>18>35}}
  • equals (obj, str): Test for equality (==). {{if name|equals>Adam}} {{if age|equals>35}}
  • notequals (obj, str): Test for inequality (!=). {{if name|notequals>Adam}}
  • like (str, str): Test for a pattern match (case-insensitive). {{if name|like>Adam}} {{if name|like>a.*}}
  • notlike (str, str): Test for a non-match (case-insensitive). {{if name|notlike>Adam}}
  • blank (str, str): Display a default value for a null or empty string. {{title|blank>Untitled}}
  • upcase (str): Upper-case a string. {{name|upcase}}
  • downcase (str): Lower-case a string. {{name|downcase}}
  • capcase (str): Capitalize the first letter in each word. {{title|capcase}}
  • chop (str, n): Chop a string to n chars followed by “…” if n < string length. {{description|chop>100}}
  • tease (str, n): Chop a string to n words followed by “…” if n < word count. {{summary|tease>15}}
  • trim (str): Trim leading and trailing white space from a string. {{article|trim}}
  • pack (str): Trim and normalize white space in a string. {{article|pack}}
  • round (num): Round a number. {{age|round}}
  • clean (str): Strip HTML/XML tags from a string. {{article|clean}}
  • length (obj): Get the length of an array, string, or iterator. {{apples|length}} {{#|length}}
  • size (obj): Alias of length. {{apples|size}} {{#|size}}
  • reverse (arr): Reverse an array.* {{articles|reverse}} … {{/articles}}
  • join (arr [, str]): Join an array with “,” or with the given token. {{names|join> + }}
  • limit (arr, n1 [, n2]): Limit an array to n1 items beginning at index n2 (or 0). {{contacts|limit>10}} … {{/contacts}}
  • split (str [, str]): Split a string on “,” or by the given token. {{names|split>;}} {{.}} {{/names}}
  • choose (bool, str [, str]): Output one value if truthy, another if falsy. {{user.passed|choose>Pass>Fail}}
  • toggle (obj, str, str [,str]): Switch one string value for another. {{gender|toggle>M,F>Boy,Girl>N/A}}
  • sort (arr [, str]): Sort an array, optionally by object property name.* {{users|sort>firstname}} … {{/users}}
  • fix (num, n): Format a number to n decimal places. {{weight|fix>1}}
  • mod (num, n): Get the remainder of a number or iterator divided by n. {{rows|mod>10}}
  • divisible (num, n): Test if a number or iterator is perfectly divisible by n. {{if #|divisible>3}}
  • even (num): Test if a number or iterator is even. {{if #|even}}
  • odd (num): Test if a number or iterator is odd. {{if #|odd}}
  • number (str): Extract a number from a string (e.g. “$1,234.56” or “30px”). {{price|number}}
  • url (str): URL-encode a string. {{article.link|url}}
  • bool (obj): Cast an object to a boolean value. {{user.geo_pref_flag|bool}}
  • falsy (obj): Test for falseness. {{if expired|falsy}}
  • first (iterator): Test if an iterator is first. {{if #|first}}
  • last (iterator): Test if an iterator is last. {{if #|last}}
  • call (obj, func [, arg1, arg2, …]): Call an object function. (See doc below) {{doggy|call>bark>5}}
  • set (obj, str): Set a variable for later use, outputting nothing. (See doc below) {{user.birthday|set>bday}}
  • log (obj): Log any variable to the console. (See doc below) {{article.title|log}}

Custom Pipes

Finally we can construct our own pipes that Markup JS will pickup and use. This especially useful I found for doing data formatting of returned JSON when actual data from server side is ill-formatted. Here are two I’ve wrote to format both phone numbers to US format, or zip codes to US format (both cases data being XXXXXXXXXX or XXXXXXXXX).

Mark.pipes.phoneNumber = function (phoneNumber) {
    return phoneNumber.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
};

Mark.pipes.zipCode = function(zipCode){
	return zipCode.replace(/(\d{5})(\d{4})/, '$1-$2');
}

and then use these custom pipes like:

{{if phone|notempty}}{{phone|phoneNumber}}{{/if}}

and

{{if zip|notempty}}{{zip|phoneNumber}}{{/if}}

So, next time you need to update markup on a page from a XHR service, always make sure that service is sending you JSON not markup and that you reach for this lib over trying to construct your own JS templates (no matter how small).
It will save you time, give you more organization options and allows frontend to format ill-formatted data quickly.

Enjoy!
Devin R. Olsen

Devin R. Olsen

Devin R. Olsen

Located in Portland Oregon. I like to teach, share and dabble deep into the digital dark arts of web and game development.

More Posts

Follow Me:TwitterFacebookGoogle Plus