Skip to end of metadata
Go to start of metadata

In my previous post, I used a very simple Create Blog Post example to show how to save a document using the Carbon LDP JavaScript SDK. In this post, I build upon that example by showing how to save a tree of multiple documents instead of just one.

When you create a document with the Carbon JS SDK, it can be the parent of another. So, in LDP terms, you could say that we're creating a hierarchy of LDP containers. Instead of saving blog posts into the application root as shown in the previous post, the goal is to now save posts in dated containers. A structure similar to the following is the goal:

  • Blog
    • 2016
      • 09
        • 05
          • blog-post-document/
        • 04
        • 03

When we create a new blog post, we want to create the container hierarchy for blog, year, month, and day. The blog post content will become a child of the day container. Of course, any of these container documents might already exist, so we'll use thedocuments.exists() function to test for them before creating them. For this, I created a general function to reuse called getOrCreateAndGetChildDocument(). The function gets a document if it exists or creates and gets the document if it does not exist. There is also a helper function called getSlug(), which gets the last part (the most identifying part) of a given URI.

Here's the new script for the Create Blog Post page. Instead of creating a new document in the application root, it now creates it within a dated folder hierarchy. The HTML for the input form hasn't changed since the previos post, so I've left it out for the sake of brevity.

(function() {

	var APP_SLUG = 'test-app/';
	var carbon = new Carbon();
	carbon.setSetting( "domain", "localhost:8083" );
	carbon.setSetting( "http.ssl", false );

	// Returns the slug (document identifying portion) of the given URI.
	function getSlug(uri) {
		// Snip off the trailing slash, if the uri has one...
		if(uri.endsWith('/')) {
			uri = uri.substring(0,uri.length-1);
		// Return all characters after the last remaining slash if one 
		// exists, otherwise return the given string
		if ( uri.indexOf('/') != -1 ) {
			var lastSlashNdx = uri.lastIndexOf('/');
			return uri.substring(lastSlashNdx + 1, uri.length);
		} else {
			return uri;
	function getOrCreateAndGetChildDocument(myAppContext,parentDocId,docId,doc) {
		console.log(">> getOrCreateAndGetDocument()");
		var slug = getSlug(docId);
		return myAppContext.documents.exists( docId ).then(
    		function( promiseResult ){
				if( promiseResult[0] ) {
					console.log("Document exists; fetching it...");
					return myAppContext.documents.get( docId );
				} else {
					console.log("Document does not exist; creating it...");
					return myAppContext.documents.createChildAndRetrieve( parentDocId, doc, slug );
	function submitContent() {
		// Get all the values from the UI Form...
		// (No form validation going on here yet)
	    var contentTitle = document.getElementById("contentTitle").value;
	    var contentBody = CKEDITOR.instances.editor1.getData();
	    var pubDate = moment().format();
	    var pubYear = moment().format('YYYY');
	    var pubMonth = moment().format('MM');
	    var pubDay = moment().format('DD');
	    var contentSlug = document.getElementById("contentSlug").value;
	    var userName = document.getElementById("userName").value;
	    var password = document.getElementById("password").value;

	    var myAppContext,
	    return carbon.auth.authenticate( userName, password ).then(
		    	function( token ) {
		    		//console.log( "Authenticated Agent: %o",carbon.auth.authenticatedAgent ); // Yourself!
		    		return carbon.apps.getContext( APP_SLUG );
		    	function( appContext ) {
		    		myAppContext = appContext; // retrieving your app context
		    		// First, lets create the blog container
		    		// ID (URI) of the Carbon document you want to retrieve
		    		// Could be fully qualified...
					// var containerId = "http://localhost:8083/apps/" + APP_SLUG + "blog/";
		    		// Or just relative...
		    		var containerId = "blog/";
		    		blogContainer = {
		    			title: "Blog"
		    		// Create the Blog container if it doesn't exist...
					return getOrCreateAndGetChildDocument(myAppContext,"/",containerId,blogContainer);
				function( result ) {
					blogContainer = result[0];
					console.log("blogContainer: %o",blogContainer);

					yearContainer = {
		    			title: pubYear

					return getOrCreateAndGetChildDocument(myAppContext,, + pubYear + "/",yearContainer);
				function( result ) {
					yearContainer = result[0];
					console.log("yearContainer: %o",yearContainer);

					monthContainer = {
			    			title: pubMonth
					// Create the month container if it doesn't exist
					return getOrCreateAndGetChildDocument(myAppContext,, + pubMonth + "/",monthContainer);
				function( result ) {
					monthContainer = result[0];
					console.log("monthContainer: %o",monthContainer);
					dayContainer = {
			    			title: pubDay

					// Create the day container if it doesn't exist
					return getOrCreateAndGetChildDocument(myAppContext,, + pubDay + "/",dayContainer);
				function( result ) {													
					dayContainer = result[0];
					console.log("dayContainer: %o",dayContainer);

					content = {
		    			title: contentTitle,
		    			body: contentBody,
		    			publishedDate: pubDate
					return getOrCreateAndGetChildDocument(myAppContext,, + contentSlug + "/",content);
				function( result ) {												
					content = result[0];
					console.log("Content: %o", content);
					function( result ) {
						console.log("ERROR: %o", result);


	var el = document.getElementById('submitButton');
	el.addEventListener("click", submitContent, false);



Here we can begin to see the usefulness of JavaScript Promises. Since we cannot create a child of a document or container unless we're sure it already exists, then we can chain.then() functions to be sure the previously called asynchronous operation always finishes before the next.

So, reading down through the code, we're doing this:

  • Authenticate with Carbon, then...
  • Get the application (application context), then...
  • Create the blog/ document (if it does not already exist), then...
  • Create the YYYY/ document (if it does nor already exist), then...
  • Create the MM/ document (if it does nor already exist), then...
  • Create the DD/ document (if it does nor already exist), then...
  • Create the slug/ blog post content (if it does nor already exist).

After submitting the form to create a new blog post, I can then navigate to the Carbon Workbench and find the following in the app...