Substance as a Blog Engine

Mon Mar 19 2012

A brief introduction on how to use Substance as a blog engine, or integrate documents with your web-page.

Introduction

Substance exposes Documents as data rather than just pre-rendered HTML. Because sections, text nodes and images are stored separately, Substance Documents can be further processed and easily integrated with third-party applications. In this tutorial you'll learn how to use Substance as a blog engine. The Substance Reader (Fig. 1) is such an example, which uses the same concepts that are described in this document. You can have a look at the Source Code for reference.

The Substance API

Unlike traditional RESTful API's, Substance offers a graph interface for exchanging data. There are just two services for reading and writing:

  • http://substance.io/graph/read 
  • http://substance.io/graph/write (not yet available)

In order to retrieve information you need to send a query (serialized as JSON) to the read service. You get returned all objects that match your specified query. We use JSONP in order to allow cross domain requests. Let's query for documents created by the user "substance":

$.ajax("http://beta.substance.io/graph/read", {
  dataType: "jsonp",
  data: {
    "qry": JSON.stringify({"type": "/type/document", "creator": "/user/substance"}),
    "options": "{}"
  },
  success: function(res) {console.log(res); }
});

The result contains all documents that have matched.

{
  "/document/substance/23a227e63c6a67ae7526ed286a4cf9d6": {
    "creator": "/user/substance",
    "name": "substance-internals",
    "title": "Substance Iternals",
    ...
  }
  "/document/substance/85fb22661b470562e3b38755512df5a0": {
    "creator: "/user/substance",
    "name": "substance-internals",
    "title": "Substance Iternals",
    ...
  },
  ....
}

Setup

We'll use Data.js in order to ease interacting with the Substance API. Data.js provides a query interface as well as in-memory data manipulation. We won't create any server side code. All we're going to use is plain old Javascript, HTML, and the implied CSS. 

First of all, we need to connect a local Data.js Graph datastructure to the Substance API Endpoint.

graph = new Data.Graph(schema).connect('ajax', {url: "http://substance.io/graph/"});

Please make sure to include the Substance Data Schema and pass it to the Data.Graph constructur.

Fetching a list of documents

var qry = {"type": "/type/document", "creator": "/user/substance"};
graph.fetch(qry, function(err, documents) {
  documents.each(function(d) {
    console.log(d._id); // The Document's id
    console.log(d.get('title'));
    console.log(d.get('lead')); // Document Lead (Abstract)
    console.log(d.get('children').length); // Number of sections.
  });
});

Based on the specified query we get all documents created by the user "substance". That's enough information for displaying a list of documents on your website. However, we probably want to access the contents of a single document.

Fetch a single document

In order to render the contents of a document, we not only need the document's properties, but also associated data objects (like Sections, Text Nodes, Images, etc.). Let's query them and inspect the results.

var id = "/document/substance/2569faba6cc0583fa8a7b037d8a721b3",
    qry = {
      "_id": id,
      "children": {"_recursive": "true}
    };
graph.fetch(qry, function(err, nodes) {
  var doc = nodes.get(id);
  // Now we can access our document complete with associated content nodes.
  doc.get('children').first().get('children').first().get('content');
});

The described query fetches the document's root node, but also recursively resolves the children associations. What you get back is subgraph of data, containing objects that relate to each other. With Data.js you can traverse this relationships without any additional effort.

Roll your own Renderer

Now that you have a data-structure representing a document, you might want to convert it to HTML. Basically you simply traverse the document starting at the root element (document) and step down to the leaf nodes. Here's a renderer you can use to get started.

var HTMLRenderer = function(root) {
  var renderers = {
    "/type/document": function(node) {
      var content = '';
      node.all('children').each(function(child) {
        content += renderers[child.type._id](child);
      });
      return content;
    },
    "/type/article": function(node) {
      return renderers["/type/document"](node)
    },
    "/type/manual": function(node) {
      return renderers["/type/document"](node);
    },
    "/type/section": function(node) {
      var content = '<h2 id="'+node.html_id+'">' + node.get('name') + '</h2>';
      node.all('children').each(function(child) {
        content += renderers[child.type._id](child);
      });
      return content;
    },
    "/type/text": function(node) {
      return node.get('content');
    },"/type/quote": function(node) {
      return "<quote>"+node.get('content')+"</quote>";
    },"/type/code": function(node) {
      return '<pre class="code">'+node.get('content')+'</pre>';
    },
    "/type/image": function(node) {
      return '<image src="'+node.get('url')+'"/>';
    },
    "/type/resource": function(node) {
      return '<image src="'+node.get('url')+'"/>';
    }
  };
  
  return {
    render: function() {
      // Traverse the document&nbsp;
      return renderers[root.type._id](root);&nbsp;
    }
  };
};

Once your Renderer is in place rendering documents is as simple as:

var doc = graph.get(id);
var html = new HTMLRenderer(doc).render();

You probably want to inject that HTML into a div element on your page.

$('#document').html(new HTMLRenderer(doc).render());

That's it, basically. Here's again the link to the Substance Reader, our reference implementation. It makes use of Backbone.js for abstracting views and uses some EJS-Templating for rendering. Feel free to adjust it to your needs.