JavaScript (Legacy) Cookbook

Getting Started

To get started with the JavaScript driver, please visit the Gitana JavaScript (Legacy) Driver Page.

Connecting to Gitana

To connect, supply your API Keys as the argument to the connect() method.

Gitana.connect({
    "clientKey": "{clientKey}",
    "clientSecret": "{clientSecret}",
    "username": "{username}",
    "password": "{password}",
    "baseURL": "https://api.cloudcms.com"
}, function(err) {
  
    var platform = this;
    
});

If a problem was encountered during the authentication, the err variable will be populated. If not, the this variable will the Platform instance you can use to start doing interesting things!

If you supply an application argument in your API Keys, you'll get back a helper object that you can use to access all of the resources on your application's associated Stack.

Gitana.connect({
    "clientKey": "{clientKey}",
    "clientSecret": "{clientSecret}",
    "username": "{username}",
    "password": "{password}",
    "baseURL": "https://api.cloudcms.com"
}, function(err) {
  
    var helper = this;
    
    var repository = helper.datastore("content");    
    
});

Asynchronous Chaining

The Gitana JavaScript driver is unique and unusual in that it offers a Chaining based solution to making asynchronous series of API calls. It's similar to Promises (but pre-dates them!) and makes it possible to make a series of API calls without having to write as much code.

For example, with the Gitana JavaScript driver, you can write the following:

platform.readRepository("1234567890abcdef1234").readBranch("master").then(function() {
    this.createNode({"title": "Article #1"});
    this.createNode({"title": "Article #2"});
    this.createNode({"title": "Article #3"});
});

This might look similar to Promises but it has a few differences. One important difference is that the then() method modifies the this to be the result. This allows you to chain calls as shown above, or even like this:

platform.readRepository("1234567890abcdef1234").readBranch("master").createNode({"title": "Article #1"});

Which is effectively the same, in concept, as a callback approach where you might do something like:

readRepository(platform, function(err, repository) {

    if (err) {
        return handleError(err);
    }
    
    readBranch(repository, "master", function(err, branch) {
    
        if (err) {
            return handleError(err);
        }
    
        createNode(branch, {"title": "Article #1"}, function(err, node) {
        
            if (err) {
                return handleError(err);
            }
            
            // yay I made it!
        
        });
    })'
});

Not so with our JavaScript driver. You can keep your code lean and clean. You just need to be mindful of the changes in scope (the this variable). Which is unique. But there it is.

Code Samples

Here are some code samples to help you get started.

In these examples, we'll assume that you're connecting to Gitana using API keys that either provide the application value or they do not. The examples below show an essential skeleton for a JS application that uses the driver for both cases.

In all of the examples that follow, assume that your code gets inserted into the TODO section.

In Cloud CMS, you can have as many repositories as you'd like. And each repository can multiple branches (similar to Git). The first thing you should do is connect and get the branch that you want to work on.

For cases where the repository and branch must be explicitly referenced, we'll assume that the repository ID is 1234567890abcdef1234 and the branch ID is master.

API Keys with application

Gitana.connect({
    "clientKey": "{clientKey}",
    "clientSecret": "{clientSecret}",
    "username": "{username}",
    "password": "{password}",
    "baseURL": "https://api.cloudcms.com",
    "application": "{yourApplicationID}"
}, function(err) {

    if (err) {
        return console.log("Failed to connect", err);
    }

    var helper = this;
    
    var repository = helper.datastore("content");
        
    repository.readBranch("master").then(function() {
        var branch = this;
        
        // TODO: add your custom code here
    });    
    
});

Standard API Keys (without application)

Use the readRepository method to fetch a specific repository. The user you've logged in as must have READ permissions to the repository. If they do not have read rights, a null response is expected.

Use the readBranch method to fetch a specific branch. The user you've logged in as must have READ permissions to the branch. If they do not have read rights, a null response is expected.

Gitana.connect({
    "clientKey": "{clientKey}",
    "clientSecret": "{clientSecret}",
    "username": "{username}",
    "password": "{password}",
    "baseURL": "https://api.cloudcms.com"
}, function(err) {

    if (err) {
        return console.log("Failed to connect", err);
    }

    var platform = this;
        
    platform.readRepository("1234567890abcdef1234").then(function() {
        var repository = this;
        
        this.readBranch("master").then(function() {
            var branch = this;
            
            // TODO: add your custom code here
        });    
    });
    
});

Node Creation

Create a Node

Create a basic node of type n:node (the default type).

var branch = this;

branch.createNode({
    "title": "My First Node"
}).then(function() {

    // NOTE: this = node
    
    // add your custom code here
});            

Create a Node for a Custom Content Type

Suppose we have a custom content type called custom:article that looks like this:

{
    "title": "Custom Article",
    "_qname": "custom:article",
    "type": "object",
    "properties": {
        "title": {
            "type": "string"
        },
        "body": {
            "type": "string"
        },
        "categories": {
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "rating": {
            "type": "number"
        }
    }
}

Create a content instance of this type like this:

var branch = this;

branch.createNode({
    "_type": "custom:article",
    "title": "Article #1",
    "body": "A still more glorious dawn awaits",
    "categories": ["category1", "category2", "category3"],
    "rating": 5            
}).then(function() {

    // NOTE: this = node
    
    // add your custom code here
});            

Create a Folder

In Gitana, folders are just nodes with the f:container feature on them.

var branch = this;

branch.createNode({
    "title": "My Folder",
    "_features": {
        "f:container": {}
    }
}).then(function() {

    // NOTE: this = node
    
    // add your custom code here
});            

If you want your folder to appear in the general folder hierarchy, you must link it to a parent folder that is already a member of the folder hierarchy. The main folder hierarchy descends from the branch root node.

Here is the long way of doing this:

var branch = this;

branch.rootNode().then(function() {

    var rootNode = this;

    Chain(branch).createNode({
       "title": "My Folder",
       "_features": {
           "f:container": {}
       }
    }).then(function() {
        var folder = this;
        
        Chain(rootNode).associate(folder, {
            "_type": "a:child"
        }).then(function() {
            // done!
        });
    });
    
});   

An easier way to do this is to use the following special properties when the node is created:

  • _parentFolderPath - the path to the parent folder where the created node should be linked. The folder must exist. If it does not exist, the operation will fail.
  • _fileName - the f:filename to be assigned to the node once it is created.

Alternatively, you can use:

  • _filePath - an absolute path where the file should be created. This allows you to specify the parent folder path and the file name at the same time,.

You can also adjust the association type (between the discovered parent folder and your newly created item).

  • _associationTypeString - the QName for the newly created association. The default is a:child.

Thus, you could do:

var branch = this;

branch.createNode({
   "title": "My Folder",
   "_features": {
       "f:container": {}
   },
   "_parentFolderPath": "/",
   "_fileName": "my_folder"
}).then(function() {
    var folder = this;        
});

Or you could do:

var branch = this;

branch.createNode({
   "title": "My Folder",
   "_features": {
       "f:container": {}
   },
   "_parentFolderPath": "/",
   "_filePath": "/my_folder"
}).then(function() {
    var folder = this;        
});

Read a Node

You can read a previously created node if you have its ID:

var branch = this;

branch.readNode("myNodeId").then(function() {
    var node = this;
});

You can also read a node by its relative path. To do this, you must pass a node ID and a path offset. The node ID can be "root" if you want to start from the top level directory. Assume we have a node with path /myFolder/myNode. To read it:

var branch = this;

branch.readNode("root", "/myFolder/myNode").then(function() {
    var pathNode = this;
});

If you have the ID of a multilingual node, you can read localizations of the node by passing a "locale parameter". For example, to read a node's Spanish translation, you could do something like this:

var branch = this;

branch.readNode("myNodeId", null, { locale: "es_ES" }).then(function() {
    var spanishNode = this;
});

Associations

In Gitana, any two nodes can be connected via an association. You can use an out-of-the-box association type, such as a:linked or a:owned or you can create your own.

Let's imagine that we have two articles that look like this:

  • Article #1
{
    "_doc": "1234567890abcdef1111",
    "_type": "custom:article",
    "title": "Article #1",
    "body": "a mote of dust suspended in a sunbeam",
    "categories": ["category1", "category2"],
    "rating": 1
}
  • Article #2
{
    "_doc": "1234567890abcdef2222",
    "_doc": "",
    "_type": "custom:article",
    "title": "Article #2",
    "body": "harvesting star light",
    "categories": ["category2", "category3"],
    "rating": 2
}

Let's create an a:linked association that points from the first article (1234567890abcdef1111) to the second article (1234567890abcdef2222), like this:

var branch = this;

branch.associate("1234567890abcdef1111", "1234567890abcdef2222", "a:linked').then(function() {
    var association = this;
});

Or we can do this using the nodes themselves:

var branch = this;

branch.readNode("1234567890abcdef1111").then(function() {
    var article1 = this;
    
    Chain(branch).readNode("1234567890abcdef2222").then(function() {
        var article2 = this;
        
        Chain(article1).associate(article2, "a:linked").then(function() {        
            var association = this;
        });    
    });
});   

You can also find all associations around article1. This will include associations that are INCOMING, OUTGOING and MUTUAL.

article1.associations().each(function() {
    console.log("Found association: " + this._doc);
});

Or find only the INCOMING associations:

article1.associations({
    "direction": "incoming"
}).each(function() {
    console.log("Found incoming association: " + this._doc);
});

Or find only the OUTGOING associations of type a:linked:

article1.associations({
    "direction": "outgoing",
    "type": "a:linked"
}).each(function() {
    console.log("Found outgoing linked association: " + this._doc);
});

Query for Nodes

Let's now look at querying for content. This makes use of MongoDB's query language to express some pretty powerful queries. We recommend further reading on Query Strings.

Let's assume that we have the following:

  • Article #1
{
    "_type": "custom:article",
    "title": "Article #1",
    "body": "a mote of dust suspended in a sunbeam",
    "categories": ["category1", "category2"],
    "rating": 1
}
  • Article #2
{
    "_type": "custom:article",
    "title": "Article #2",
    "body": "harvesting star light",
    "categories": ["category2", "category3"],
    "rating": 2
}
  • Article 3
{
    "_type": "custom:article",
    "title": "Article #3",
    "body": "we are star stuff",
    "categories": ["category3", "category1"],
    "rating": 3
}

Basic Querying

Find all of the articles that have a category with the value category1.

var branch = this;

branch.queryNodes({
    "_type": "custom:article",
    "category": "category1"
}).count(function(c) {
    console.log("The size was: " + c);
}).each(function() {
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
}).then(function() {
    // done!
});

Pagination

If you have a lot of potential query hits, you'll want to paginate. Here is an example where we return results starting at index 2 and hand back at most 5 results.

var branch = this;

branch.queryNodes({
    "_type": "custom:article",
    "category": "category1"
}, {
    "skip": 2,
    "limit": 5    
}).each(function() {
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
}).then(function() {
    console.log("The size was: " + this.size()); // 1
    console.log("The offset was: " + this.offset()); // 2
    console.log("The totalRows was: " + this.totalRows()); // 3
});

Sorting

Use the pagination option to sort the results as you see fit. Here we sort descending on rating.

var branch = this;

branch.queryNodes({
    "_type": "custom:article",
    "category": "category1"
}, {
    "sort": {
        "rating": -1
    }   
}).each(function() {
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
});

The results will look like:

Found a result, title: Article #3, rating: 3
Found a result, title: Article #2, rating: 2
Found a result, title: Article #1, rating: 1

Query for values in a range

Find all of the articles where the rating is greater than or equal to 2.
This demonstrates the power of MongoDB's query language.

var branch = this;

branch.queryNodes({
    "_type": "custom:article",
    "rating": {
        "$gte": 2
    }
}).each(function() {
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
});

Search for Nodes

Let's now look at searching for content. This makes use of Elastic Search's DSL to express some very powerful full text and structured searches. We recommend further reading on Search within Cloud CMS.

We also suggest reading up on Query Strings to understand how you can write searches textually (in addition to structuring them as JSON objects):

Let's assume that we have the following:

  • Article #1
{
    "_type": "custom:article",
    "title": "Article #1",
    "body": "a mote of dust suspended in a sunbeam",
    "categories": ["category1", "category2"],
    "rating": 1
}
  • Article #2
{
    "_type": "custom:article",
    "title": "Article #2",
    "body": "harvesting star light",
    "categories": ["category2", "category3"],
    "rating": 2
}
  • Article 3
{
    "_type": "custom:article",
    "title": "Article #3",
    "body": "we are star stuff",
    "categories": ["category3", "category1"],
    "rating": 3
}

Full Text Search

Find all of the nodes with the word star. Simple enough!

var branch = this;

branch.searchNodes("star").each(function() {    
    console.log("Found a result, title: " + this.title + ", rating: " + this.rating);
});

The results will be:

Found a result, title: Article #2, body: harvesting star light
Found a result, title: Article #3, body: we are star stuff

Node Attachments

You can store binary files onto Nodes as attachments.

Note: If you're working with the JavaScript driver in the browser, there are some limitations in terms of the kinds of payloads you can send to the Cloud CMS API. This is because the browser does not let you stream files from local disk. You are limited to the kinds of content that can be expressed within JavaScript itself (usually text, JSON and similar formats).

Attachments have the following methods:

  • getId() returns the ID of the attachment
  • getLength() returns the length of the attachment in bytes
  • getContentType() returns the mimetype of the attachment
  • getFilename() returns the optional filename of the attachment
  • getUri() returns a datastore-relative path to the attachment
  • getDownloadUri() returns a driver baseURI-relative path to the attachment
  • del() deletes the attachment
  • download() downloads the attachment and hands it back to you. The exact format depends on the mimetype. For text files, this will be a string. For JSON documents, this will be a JavaScript object literal. Otherwise, it will be a byte array.

Note that support for Streams is only available when using the JavaScript driver in Node.js. For information on that, please see the Node.js Driver Cookbook.

Listing Attachments of a Node

To list the attachments of a Node, do the following:

var node = ...;

node.listAttachments().each(function() {
   console.log("The node: " + node._doc + " has an attachment: " + this.getId() + " (mimetype = " + this.getContentType() + ", length: " + this.getLength() + ")");
});

Uploading an Attachment to a Node

To upload an attachment, do the following:

var node = ...;

node.attach("default", "text/plain", "Hello World", "helloworld.txt");

Downloading an Attachment of a Node

To download an attachment:

var node = ...;

node.attachment("default").download(function(data) {
   console.log("Downloaded: " + data);
});

Stream an Attachment of a Node

If you have a larger attachment or an attachment which contains binary data, you might wish to stream the attachment:

var node = ...;

node.attachment("default").then(function() {
    this.stream(function(err, stream) {

        var outStream = fs.createWriteStream("test.png");
        stream.pipe(outStream);
    });
});

GraphQL

You can make queries for specific fields or content in related items by using GraphQL. Here we do a simple query for just the title and body of our article type:

let query = `query {
    n_nodes {
        title
    }
}`;

branch.graphqlQuery(query).then(function(response) {
    console.log(`Query Results: ${response}`);
});