views:

52

answers:

2

I wonder if there are anything like a Javascript template system that wraps HTML, so that we don't have to deal with HTML directly (yeah, i know it's a bad idea, but just out of curiosity).

So instead of writing HTML:

<body>
  <div id="title">Great work!</div>
  <span>My name is Peter</span>
</body>

We write in Json:

body: [
  {div: [
    {id: "title"},
    {body: "Great work!"}
  ]
  {span: [
    {body: "My name is Peter"}
  ]
]

I know it looks kinda weird, but I really love the thing that everything is an object :)

Is there a such implementation for any language? (Im using Ruby myself).

EDIT: Found something interesting:

http://jsonml.org/

Look at their examples! Brilliant!

A: 

You should also check out haml. Its quite cool! Its not JSON but its rationale is the same. You don't have to deal with HTML.

naikus
It's not the same thing. It's just like writing HTML but you don't have to be as explicit (no ending tag etc). But I mean just to use json for it instead of xml.
never_had_a_name
+1  A: 

I've just wrote a little example of the parser, similar to mentioned by you, using plain old JavaScript. My code is a bit dirty (as mentioned by Casey Hope, you shouldn't extend Object.prototype) , perhaps, but it works and very easy to understand, I hope.

The function itself:

Object.prototype.toHtml = function(options)
{
    //Iterates over elements
    var processElements = function(obj, handler)
    {
        //Stores found elements
        var elements = [];

        for (var elem in obj)
        {
            //Skips all 'derived' properties
            if (!obj.hasOwnProperty(elem)) continue;

            //Attribute
            if (elem.indexOf("_") == 0)
            {
                elements.push({type: "attribute", name : /^_([a-z][0-9a-z]+)$/i(elem)[1], value : obj[elem]});
            }
            //Internal contents
            else if (elem == "contents")
            {
                elements.push({type: "contents", value : obj[elem]});
            }
            //Text node
            else if (elem == "text")
            {
                elements.push({type: "text", value : obj[elem]});
            }
            //Ordinary element
            else
            {
                elements.push({type: "element", name : elem,  value : obj[elem]});
            }
        }

        //Returns parsed elements
        return elements;
    }

    //Internal function to deal with elements
    var toHtmlInternal = function(name, elements)
    {
        //Creates a new element by name using DOM
        var element = document.createElement(name);

        //Element children and attributes
        var children = processElements(elements);

        for (var i = 0; i < children.length; i++)
        {
            switch (children[i]["type"])
            {
                case "element":
                    element.appendChild(toHtmlInternal(children[i]["name"], children[i]["value"]));
                    break;
                case "attribute":
                    element.setAttribute(children[i]["name"], children[i]["value"]);
                    break;
                case "text":
                    element.appendChild(document.createTextNode(children[i]["value"]));
                    break;
                case "contents":
                    for (var j = 0; j < children[i]["value"].length; j++)
                    {
                        var content = children[i]["value"][j];
                        if (typeof content == "string")
                        {
                            element.appendChild(document.createTextNode(content));
                        }
                        else if (typeof content == "object")
                        {
                            element.appendChild(content.toHtml().firstChild);
                        }
                    }
                    break;
            }
        }

        //Returns it
        return element;
    }

    //Initial element checkment
    var initial = processElements(this);
    //Generic wrapper
    var wrapper = document.createElement("div");

    for (var i = 0; i < initial.length; i++)
    {
        if (initial[i]["type"] == "element")
        {
            wrapper.appendChild(toHtmlInternal(initial[i]["name"], initial[i]["value"]));
        }
    }

    //Returns wrapper
    return wrapper;
};

How to use:

//A simple testing template
var html = ({
    //Element name is just a plain name here
    body: {

      //Nested element
      div : {
        //All attributes are prepended with underscore
        _id : "title",
        //Content of the element is represented by such construction
        text : "Great work!"
      },

      span : {
        text : "My name is Peter"
      },

      h1 : {
        _class : "common",
        //Elements can be defined using 'contents' notation also, so we could include text nodes
        contents : ["This is my ", {a : {text: "beautiful"}} , " header"]
      }

    }
}).toHtml();

alert(html.innerHTML); 
floatless
Extending `Object.prototype` is a really bad idea because it breaks `for-in` loops.
Casey Hope
I know. The code is just an expression of the general idea (how such parser might look like). This code can be easily rewritten to avoid future errors.
floatless
Is it hosted on GitHub?
never_had_a_name
No, I wrote this code several hours ago for this question. If you wish, you may host it somewhere else.
floatless