Mura X: Mura API

Introduction

Mura content management has moved with the times, evolving from a traditional CMS to one that recognizes the fluid and ever-changing process of delivering content from a variety of sources to an equally diverse set of platforms. Flexibility and maintainability are key ingredients to addressing this requirement, and Mura has matured these tools to allow developers of not only traditional HTML + CSS websites but those building advanced JavaScript-rendered decoupled and/or headless websites via advanced frameworks.

In this section we will examine the Mura API, which consists of several components but chiefly the Mura JS JavaScript framework, which is itself a standalone Node project, as well as Mura ORM and the Mura JSON API. These three components enable a wide variety of options to the modern front-end developer.

Decoupled Websites

With the increasing shift for front-end developers to JavaScript-based frameworks like React, Angular and Vue JS, decoupled websites are becoming increasingly common. A decoupled website is one where the front-end of the website is being rendered and/or delivered independent of the underlying content management system. Often the CMS provides the content via a dedicated, secure API. 

Mura is fully-capable and compatible with this type of website, with an underlying JSON API, as well as the Mura JS framework for delivering content in a variety of formats, from raw data-based JSON to server-rendered, hydrated and/or compartmentalized content blocks ready for consumption and display. To help developers move to this type of development, as well as provide insights into how experienced JavaScript developers might integrate into Mura, we have developed several starter or example projects that should facilitate this need. We have also created a basic, "vanilla" JavaScript-only decoupled example, which we will examine more fully below. Otherwise, feel free to visit the GitHub repository for the framework that best suits your needs.

Mura.js

Mura.js originally began as a lightweight utility to decouple Mura's dependency on jQuery, a popular JavaScript library. Since its inception, Mura.js has also grown into a JavaScript framework for interacting with Mura's JSON API, which allows Mura to be the main content creation hub within an organization, and allows developers to build their applications as pure JavaScript clients.

Familiar Syntax

If you've ever used jQuery, you'll find the syntax quite familiar.

Selecting Elements

The example below illustrates how to use Mura.js to select multiple DOM elements with the same class, and replace the content of each of the selected elements with custom HTML.

<script>
Mura(function(m) {
  m('.target').each(function() {
    m(this).html('Mura found you!');
  });
});
</script>

Ajax Support

Mura.js includes baked-in Ajax (asynchronous JavaScript and XML) support. The example below illustrates how to perform a simple Ajax request.

<script>
Mura.ajax({
  type: 'post',
  url: 'https://domain.com/path/',
  data: { key: value },
  success: function(resp) {
    console.log(resp);
  },
  error: function(resp) {
    console.log(resp);
  }
});
</script>

Promise Support

Mura.js also includes support for JavaScript promises. See the following examples for more details.

Mura.post() With Promises

<script>
Mura.post('https://domain.com/path/', { key: 'value' })
  .then(function(result) {
    // handle success
   })
  .catch(function(err) {
    // handle error
  };
</script>

Mura.get() With Promises

<script>
Mura.get('https://domain.com/path/')
  .then(function(result) { 
    // success
    // do something with the result
    Mura('#target').html(result.data.html);
  })
  .catch(function(err) { 
    // handle error
    console.log(err);
  });
</script>

Chaining Promises

The following example illustrates how you could handle multiple Ajax requests with Mura.js using JavaScript Promises chaining, and avoid the "pyramid of doom". In other words, avoid nesting your Ajax requests.

<script>
var exampleFunc1 = function() {
    return new Promise(function (resolve, reject) {
        Mura.post('https://domain.com/path/', { apikey: 'value' })
            .then(function(result) {
                if ( result.hasOwnProperty('id') ) {
                    resolve(result);
                } else {
                    reject('result does not contain an id');
                }
            })
            .catch(function(err) {
                reject(err);
            });
    });
};
 
var exampleFunc2 = function(id) {
    return new Promise(function (resolve, reject) {
        Mura.get('https://domain.com/path/?id=' + id)
            .then(function(result) {
                resolve(result);
            })
            .catch(function(err) {
                reject(err);
            });
    });
};
 
exampleFunc1()
    .then(function(exampleFunc1Result) {
        return exampleFunc2(exampleFunc1Result.id);
    })
    .then(function(result) {
        console.log(result);
    })
    .catch(function(err) {
        console.log(err);
    });
</script>

DOM Event Handlers

Mura.js allows for registering DOM event handlers, as shown in the example below.

<script>
Mura('#mybutton').on('click', function(e) {
  e.preventDefault();
  console.log(e);
});
</script>

You can also register multiple event handlers using a custom "addEventHandler" method, as shown below.

<script>
Mura('.myclass').addEventHandler({
  click: function(e) {
    e.preventDefault();
    console.log(e);
  }
  , touch: function(e) {
    // do something
    console.log(e);
  }
});
</script>

Mura.DOMSelection Class

The DOM selection methods available via Mura.js is handled by the Mura.DOMSelection class. This wraps your selected targets via the Mura() method.

Mura.js allows you to handle selected DOM elements as a single object, or as a collection.

Single Object Example
Mura('#target')
  .html('Hello world!');
Collection Example
Mura('.target')
  .each(function() {
    Mura(this)
      .html('Hello world!');
  });

Supported DOMSelection methods may be found at https://github.com/blueriver/MuraJS/blob/master/src/core/domselection.js.

Mura ORM With Mura.js

Mura.js enables JavaScript developers to interact with Mura ORM, exposing access to its ORM Feed API, and allows for common CRUD (Create, Read, Update, and Delete) functionality.

Mura.js CRUD Functionality

Outlined below are code examples for performing basic CRUD operations on Mura ORM objects/entities via Mura.js. For developers who are primarily used to working with server-side languages, it may take a little time to adjust to working on the client side, because we need to "wait" until the object/entity is loaded in order to work with it. For this reason, it may be helpful to review how to work with JavaScript Promises.

Loading/Reading Mura ORM Objects/Entities

This example simply illustrates how to load a Mura ORM entity, using Mura.js.

<script>
var personid = 'some-uuid';

Mura.getEntity('person')
    .loadBy('personid', personid)
    .then(function(person) {
        console.log(person);
    })
    .catch(function(err) {
        console.log(err.get('errors'));
    });
</script>

Creating/Updating Mura ORM Objects/Entities

This example drives home how to segregate your Ajax calls using JS Promises, and avoid nesting or stacking your Mura.js methods.

<script>
var getPersonByID = function(personid) {
    return new Promise(function(resolve, reject) {
        Mura.getEntity('person').loadBy('personid', personid)
          .then(function(person) {
            resolve(person);
          })
          .catch(function(err) {
            reject(err);
            console.log(err.get('errors'));
          });
    });
};

var savePerson = function(person) {
    return new Promise(function(resolve, reject) {
        person.save()
            .then(function(result) {
                resolve(result);
            })
            .catch(function(err) {
                reject(err);
            });
    });
}

getPersonByID('some-uuid')
    .then(function(person) {
        person.set('namelast', 'Withington');
        return savePerson(person);
    })
    .then(function(result) {
        console.log(result);
    })
    .catch(function(err) {
        console.log(err);
    });
</script>

Deleting Mura ORM Objects/Entities

This is another example of how to segregate your Ajax calls using JS Promises to avoid nesting your Mura.js methods.

<script>
var getPersonByID = function(personid) {
    return new Promise(function(resolve, reject) {
        Mura.getEntity('person').loadBy('personid', personid)
          .then(function(person) {
            resolve(person);
          })
          .catch(function(err) {
            reject(err);
            console.log(err.get('errors'));
          });
    });
};

var deletePerson = function(person) {
    return new Promise(function(resolve, reject) {
        person.delete()
            .then(function(result) {
                resolve(result);
            })
            .catch(function(err) {
                reject(err);
            });
    });
}

getPersonByID('some-uuid')
    .then(function(person) {
        return deletePerson(person);
    })
    .then(function(result) {
        console.log(result);
    })
    .catch(function(err) {
        console.log(err);
    });
</script>

Mura.js Feed API

The following example illustrates how to obtain a feed of Mura ORM objects/entities, and then loop over the returned recordset. As you'll see, it's quite similar to using Mura's ORM Feed syntax.

var person;

Mura
  .getFeed('person')
  .where() //optional
  .prop('namelast')
  .isEQ('Levine')
  .orProp('namelast')
  .beginsWith('Withing')
  .getQuery()
  .then(function(people) {
    // handle success
    people.each(function(person, idx) {
      result = person.get('namefirst') + ' ' + person.get('namelast');
      console.log(result);
    });
  })
  .catch(function(err) {
    // handle error
    console.log(err);
  });

This example demonstrates how to use the aggregate method for a Mura.js Feed.
The example assumes a custom ORM object named "widget" exists, and has a property of "price".

var widget;

Mura
  .getFeed('widget')
  .aggregate('count', '*')
  .aggregate('sum', 'price')
  .aggregate('min', 'price')
  .aggregate('max', 'price')
  .aggregate('avg', 'price')
  .getQuery()
  .then(function(widgets) {
    // handle success
    console.log(widgets.getAll());
  })
  .catch(function(err) {
    // handle error
    console.log(err);
  });

See the "Key Methods" area of the Feed Bean section, for details on the Mura.js Feed API Methods.

Loading JavaScript and CSS Files With Mura.js

Mura.js allows JavaScript developers to load their JS and CSS files synchronously or asynchronously. This is especially useful for modules rendered via CFML.

Note: This is NOT recommend for modules rendered via JavaScript. See the Anatomy of a Module's Rendering Via JavaScript section for more information.

Mura.loader()

The Mura.loader() method is for JavaScript and CSS parallel loading with dependencies management. Using this method also prevents duplicate JavaScript and/or CSS files from being loaded.

There are two primary methods associated with Mura.loader(), loadjs() and loadcss() described below.

loadjs(url, cb)

Parameter Description
url If the first parameter is an array, files will be loaded asynchronously. If it's a string, the files will be loaded synchronously. If the file is located under your theme, you may use m.themepath to dynamically generate the path to the theme location. For example: loadjs(m.themepath + '/script.js')
cb A callback function to execute when all scripts have been loaded.

loadcss(url, attrs, cb)

Parameter Description
url The URL for the CSS file. If the file is located under your theme, you may use m.themepath to dynamically generate the path to the theme location. For example: loadcss(m.themepath + '/file.css')
attrs You may optionally pass in an object to specify the media type attribute for the CSS file. For example: loadcss(m.themepath + '/file/css', {media: "print"})

Valid options include:
  • all
  • aural
  • braille
  • embossed
  • handheld
  • print
  • projection
  • screen
  • tty
  • tv
cb A callback function which executes immediately.

Examples

This example illustrates the basic syntax for loading a JavaScript file, with a desired callback function.

<script>
Mura(function(m) {
    m.loader()
        .loadjs(m.themepath + '/js/mylibrary.js', function() {/* callback */});
});
</script>

The example below illustrates loading some scripts in parallel with another batch of scripts being loaded in order. In the example below, the first loadjs() will be executed in parallel of the second loadjs(). However, in the second loadjs(), the myDependentLib.js file won't be loaded until myRequiredLib.js has finished loading. Then, when they've finished loading, the callback function will execute.

<script>
Mura(function(m) {
    m.loader()
        .loadjs(m.themepath + '/myLib.js')
        .loadjs(
          m.themepath + '/myRequiredLib.js', 
          m.themepath + '/myDependentLib.js', 
          function() {
            // callback
          });
});
</script>

The example below illustrates loading multiple CSS & JavaScript files, and includes a callback function that runs after the script files have all been loaded.

<script>
Mura(function(m) {
    m.loader()
        .loadcss(m.themepath + '/path/to/all.css', { media: 'all' })
        .loadcss(m.themepath + '/path/to/print.css', { media: 'print' })
        .loadjs(
            m.themepath + '/path/to/script1.js',
            m.themepath + '/path/to/script2.js',
            function() {
                // Now do something with the loaded JS
            });
});
</script>

Modules With Mura.js

As introduced in the Anatomy of a Module section, developers may choose to render their modules using purely JavaScript. When doing so, the module's index.cfm file should only contain the following line of code, which informs Mura it has nothing to render via server-side processing, and all rendering will occur via the "client" or browser.

<cfset objectparams.render="client">

Then, in order to load your script(s), you'll need to use a custom event handler, and register the onRenderStart event to dynamically load your script file(s), as illustrated below.

// ../yourmodule/model/handlers/yourhandler.cfc

component extends='mura.cfobject' {

  function onRenderStart(m) {
    // if script should be included in the <head>
    arguments.m.addToHTMLHeadQueue('<script src="/path/to/script.js"></script>');

    // OR

    // if script should be included before closing </body> tag
    arguments.m.addToHTMLFootQueue('<script src="/path/to/script.js"></script>');
  }

}

By using addToHTMLHeadQueue and/or addToHTMLFootQueue, your scripts will only be loaded once, regardless of how many times your module has been applied to the layout.

The Custom JavaScript File

Assuming you've followed the instructions above to get your custom script file included into either the "head" portion of your theme, or before the closing "body" tag, you will most likely want to control when your script(s) are executed. However, you may also simply allow your scripts to run at all times.

Running Scripts on Every Request

If you merely wish for your script(s) to execute, regardless of whether or not your module has been applied to the layout, then you may simply include any JavaScript you wish. In other words, since your script files are being added on every request via your custom event handler's onRenderStart method, your scripts will automatically execute. This is useful for merely adding a utility script, such as a tracking script, etc.

The content of your script file may look as simple as the following example.

// your-script.js
console.log('Hello from your module!');

Running Scripts Only If Your Module Has Been Applied

Unless your scripts are utilities, such as tracking scripts, developers will most often wish their scripts to execute only when the module has been applied to the layout. In addition, when the module includes a configurator, each instance of the module will have access to its own configuration information.

First, it's important to understand that modules rendered via the client, are automatically registered to Mura's namespace under Mura.Module.YourModuleDirectoryName. Also, Mura will automatically invoke a "renderClient" method via Mura.UI, on each request. By defining your own "renderClient" method, you're able to pretty much do whatever you want.

The example below illustrates a skeleton script. You should substitute "yourModuleDirectoryName" with the actual directory name of your display object.

// your-script.js
Mura.Module.YourModuleDirectoryName = Mura.UI.extend({
  renderClient: function() {
    // Your code goes here ...
    return this;
  }
});

You can also defined a "renderServer" method if defined will be used in universal node js frameworks like NextJS and NuxtJS

// your-script.js
Mura.Module.YourModuleDirectoryName = Mura.UI.extend({
	    renderClient: function() {
	        // Your code goes here ...
	        return this;
	    },
   renderServer: function() {         // Your code goes here ...         return this;     } });

this.context

When using JavaScript to render a Mura "Module" via the client, developers are most interested in two (2) primary pieces of information:

  1. How to target the container element of where the module has been applied to the layout.
  2. How to access "objectparams", or the data collected via a configurator.

You can easily access either of these by using "this.context" in your custom script.

  • this.context.targetEl
    • Use this to target the container element of where the module has been applied to the layout. Keep in mind, each instance of the module in the layout will have its own, unique container element.
  • this.context.{yourObjectParam}
    • Use this to access any "objectparams", or data collected via a configurator. Each instance of the module will have access to its own, unique configuration data.

The example below illustrates how to use the above information, and may be used as a starter file in your own projects.

// your-script.js
Mura.Module.YourModuleDirectoryName = Mura.UI.extend({

  renderClient: function() {
    // reference to the container element
    this.container = Mura(this.context.targetEl);
    
    // assuming you have an objectparam named 'mytext'
    var txt = this.context.mytext || 'mytext is empty';

    // Having fun by replacing the content of the container
    // Remember, using Mura.js, we can do jQuery-esque DOM manipulation
    this.container.html('<h3>Hello from yourDisplayObject</h3>');
    this.container.append('<h4>' + txt + '</h4>');

    // The rest of your code goes here ...
    return this;
  }

});

If you're interested in seeing what else is available via Mura.UI, feel free to review the code found at https://github.com/blueriver/MuraJS/blob/master/src/core/ui.js.

To see an example of how to use Mura.js along with Mura ORM, Grunt, and Handlebars, visit https://github.com/stevewithington/muracontacts/tree/js.

Forms & Mura.js

Mura forms are loaded asynchronously. So, if you wish to run some scripts, you need to use a special method to "reopen" the form, and then add your scripts. This can be done by leveraging a special Mura.js method: Mura.DisplayObject.Form.reopen({});.

Once the form has been "reopened," simply leverage one or more of the predefined events from the code example below to inject any custom logic.

NOTE: If not using deferred Mura.js then you do not need to reopen the display object classes within a Mura.preInit() method. The option to defer the loading of Mura.js is controlled by the this.deferMuraJS theme value in the contentRenderer.cfc file.

Mura.preInit(function(m) {

	Mura.DisplayObject.Form.reopen({
		// triggered after the form has rendered on the browser
		onAfterRender: function() {
			var form_container = this.context.targetEl
				, the_form = m(form_container).find('form');

			console.log('afterRender triggered');

			// This is where you register your custom form listeners, 
                        // for example ...
			m('button.form-submit').on('click', function(event) {
				console.log('Form button clicked!')
				console.log(event.target.form);
			});
		}

		// triggered when the form has been submitted, 
                // before processing/validation begins
		, onSubmit: function () {
			var the_button = event.target
				, the_form = the_button.form;

			console.log('onSubmit triggered');
			console.log(the_button);
			console.log(the_form);

			// you could run a script here (obviously) ... then,
			// return true if you want to continue, 
                        // or false if you wish to stop processing
			return true;
		}

		// triggered after submit, and form has errors
		, onAfterErrorRender: function() {
			var resp = JSON.parse(event.currentTarget.response)
				, errors = resp.data.errors;

			console.log('afterErrorRender triggered');
			console.log(errors);
		}

		// triggered after successful form submit (no errors)
		, onAfterResponseRender: function () {
			var response_container = this.context.targetEl;

			console.log('afterResponseRender triggered');
			console.log(response_container);
		}
	});

});

JSON API

Introduction

The JSON API allows Mura CMS to be the main content creation hub within an organization, and allows developers to build their front-ends as pure JavaScript clients.

Base URL

The base URL for the API endpoints is:

https://yourDomain.com/{context}/index.cfm/_api/json/v1/

API Endpoint Reference

Method Endpoint Usage Returns
GET /{siteid}/{entityname}/{id} FindOne data
GET /{siteid}/entityname}/{ids} FindMany data
GET /{siteid}/{entityname}/new FindNew data
GET /{siteid}/{entityname}/? FindQuery data
GET /{siteid}/{entityname}/{id}/{releatedentity}/? FindRelatedEntity data
GET,POST /?method=findCalenderItems&siteid={siteid}&calendarid={calendarid}&start={start}&end={end}&format={format} FindCalendarItems data | events
POST /?method=generateCSRFTokens&siteid={siteid}&context={entityid} GenerateCSRFTokens data
POST /{siteid}/{entityname}/?csrf_token={csrf_token}&csrf_token_expires={csrf_token_expires} Save data
DELETE /{siteid}/{entityname}/{id}/?csrf_token={csrf_token}&csrf_token_expires={csrf_token_expires} Delete data
GET,POST /{siteid}/login/?username={username}&password={password} Login data
GET,POST /{siteid}/logout Logout data

Response Format

On success, the HTTP status code in the response header is 200 OK and the response body contains a data object in JSON format. On error, the header status code is an error code and the response body contains an error object.

Example Response

{
	"data": {
		"key": "value"
	}
}

Response Status Codes

The JSON API uses the following response status codes:

Status Code Description
200 OK - The request has succeeded. The client can read the result of the request in the body and the headers of the response.
400 Bad Request - The request could not be understood by the server due to malformed syntax. The message body will contain more information; see Error Handling, below.
401 Unauthorized - The request requires user authentication or, if the request included authoriation credentials, authorization has been refused for those credentials. Also, the JSON API feature may not be enabled for the site; see How To Enable, above.
403 Forbidden - The server understood the request, but is refusing to fulfill it. For example, requestor does not have permission to a requested method.
404 Not Found - The requested resource could not be found. This error can be due to a temporary or permanent condition.

Error Handling

When an error occurs, the response data attribute will not exist. The response will instead contan an error attribute.

Example Error Response

{
	"error": {
		"message": "Insufficient Account Permissions"
	}
}

Example Response Handler

$.getJSON('/index.cfm/json/v1/v10/content/').then(
	function( resp ) {
		if ( 'error' in resp ) {
			//handle error
		} else {
			//do stuff
		}
	}
});

FindOne

Find an entity.

Endpoint

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/{siteid}/{entityname}/{id}

OR

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=findone&site={siteid}&entityname={entityName}&id={id}

Request & Query Parameters

Parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of the site being searched.
entityname The entityname being searched. For example: "content", "user", "feed", etc.
id The object ID of the entity being searched for.

Example Response

The data object will contain the entity's keys and corresponding values.

{
  "data": {
    "targetparams":"",
    "path":"00000000000000000000000000000000001,2C91A3B0-E375-9383-0B05636E6926868D",
    "menutitle":"Testing",
    "releaseDate":"",
    "fileid":"",
    "responsesendto":"",
    "id":"2C91A3B0-E375-9383-0B05636E6926868D",
    "type":"Page",
    "lastupdatebyid":"AD771D5C-BE0A-D43D-47F4E201B378BD7A",
    "forceSSL":0,
    "responsemessage":"",
    "subtype":"Default",
    "remoteurl":"",
    "contenttype":"",
    "childtemplate":"",
    "keypoints":"",
    "moduleid":"00000000000000000000000000000000000",
    "inheritobjects":"Inherit",
    "searchExclude":0,
    "featureStop":"",
    "newfile":"",
    "remotesourceurl":"",
    "displayStop":"",
    "remotesource":"",
    "remotePubDate":"",
    "tags":"",
    "majorVersion":0,
    "extendautocomplete":true,
    "contentsubtype":"",
    "url":"/index.cfm/testing/",
    "sortby":"orderno",
    "displayTitle":0,
    "approvalstatus":"",
    "credits":"",
    "oldfilename":"",
    "featureStart":"",
    "siteid":"default",
    "doCache":1,
    "imagesize":"small",
    "imagewidth":"AUTO",
    "target":"_self",
    "minorVersion":0,
    "approvalgroupid":"",
    "assocfilename":"",
    "lastUpdate":"2015-01-29T15:45:51",
    "summary":"",
    "images":{},
    "template":"",
    "moduleassign":"",
    "displayStart":"",
    "isFeature":0,
    "filesize":"",
    "approvingchainrequest":false,
    "categoryid":"",
    "tcontent_id":"0",
    "isLocked":0,
    "isNav":1,
    "newstags":"",
    "orderno":1,
    "htmltitle":"Testing",
    "urltitle":"testing",
    "approvalchainoverride":false,
    "body":"",
    "requestid":"",
    "restrictgroups":"",
    "active":1,
    "metadesc":"",
    "audience":"",
    "links":{
      "site":"http://docs.getmura.com:8080/index.cfm/_api/json/v1/default?method=findOne&entityName=site&siteid=default",
      "categoryassignments":"http://cf11:8080/index.cfm/_api/json/v1/default?method=findQuery&siteid=default&entityName=contentCategoryAssign&contenthistid=2C92F953-E042-CBF6-42F66BD74A4BEC0B",
      "parent":"http://docs.getmura.com:8080/index.cfm/_api/json/v1/default?method=findOne&siteid=default&entityName=content&id=00000000000000000000000000000000001",
      "stats":"http://docs.getmura.com:8080/index.cfm/_api/json/v1/default?method=findOne&siteid=default&entityName=stats&id=2C91A3B0-E375-9383-0B05636E6926868D",
      "crumbs":"http://docs.getmura.com:8080/index.cfm/_api/json/v1/default?method=findCrumbArray&siteid=default&entityName=content&id=2C91A3B0-E375-9383-0B05636E6926868D",
      "comments":"http://docs.getmura.com:8080/index.cfm/_api/json/v1/default?method=findQuery&siteid=default&entityName=comment&contentid=2C91A3B0-E375-9383-0B05636E6926868D",
      "relatedcontent":"http://docs.getmura.com:8080/index.cfm/_api/json/v1/default?method=findRelatedContent&siteid=default&id=2C91A3B0-E375-9383-0B05636E6926868D",
      "renderered":"http://docs.getmura.com:8080/index.cfm/_api/json/v1/v10/_path/testing",
      "kids":"http://cf11:8080/index.cfm/_api/json/v1/default?method=findQuery&siteid=default&entityName=content&parentid=2C91A3B0-E375-9383-0B05636E6926868D"
    },
    "restricted":0,
    "metakeywords":"",
    "nextN":10,
    "fileext":"",
    "notes":"",
    "display":1,
    "created":"2015-01-29T15:45:51",
    "contenthistid":"2C92F953-E042-CBF6-42F66BD74A4BEC0B",
    "responsedisplayfields":"",
    "expires":"",
    "imageheight":"AUTO",
    "filename":"testing",
    "relatedcontentsetdata":"",
    "sortdirection":"asc",
    "contentid":"2C91A3B0-E375-9383-0B05636E6926868D",
    "changesetid":"",
    "displayinterval":"Daily",
    "sourceiterator":"",
    "mobileExclude":0,
    "extendsetid":"",
    "remoteid":"",
    "parentid":"00000000000000000000000000000000001",
    "preserveid":"2C92F953-E042-CBF6-42F66BD74A4BEC0B",
    "title":"Testing",
    "lastupdateby":"Steve Withington",
    "approved":1,
    "extenddata":"",
    "blogtags":"",
    "responseChart":0
  }
}

FindMany

Get multiple entities.

Endpoint

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/{siteid}/{entityname}/{ids}

OR

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=findmany&siteid={siteid}&entityname={entityname}&ids={ids}

Request & Query Parameters

Parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of where the entity will be stored.
entityname The entity's name.
ids A comma-delimited list of entity IDs.

Example Response

If multiple entities are found, an items array will be present in the data object.

{
	"data": {
		"items": [
			{
				"id": "2C91A3B0-E375-9383-0B05636E6926868D",
				"name": "Example",
				"links": {
					"relatedentitylink1": "http://...",
					"relatedentitylink2": "http://..."
				}
			},
			{
				"id": "6C92F953-E042-CBF6-42F66BD74A4BEC0B",
				"name": "Another Example",
				"links": {
					"relatedentitylink1": "http://...",
					"relatedentitylink2": "http://..."
				}
			},

		]
	}
}

If only one entity is found, the data object will contain the entity's keys and corresponding values.

{
	"data": {
		"id": "2C91A3B0-E375-9383-0B05636E6926868D",
		"name": "Example",
		"links": {
			"relatedentitylink1": "http://...",
			"relatedentitylink2": "http://..."
		}
	}
}

 

FindNew

Gets a new entity.

Endpoint

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/{siteid}/{entityname}/new

OR

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=findnew&siteid={siteid}&entityname={entityname}

Request & Query Parameters

Parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of where the entity will be stored.
entityname The entity's name.

Example Response

{
	"data": {
		"id": "2C91A3B0-E375-9383-0B05636E6926868D",
		"name": "Example",
		"links": {
			"relatedentitylink1": "http://...",
			"relatedentitylink2": "http://..."
		}
	}
}

FindQuery

Get an array of entity items.

Endpoint

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/{siteid}/{entityname}/?

OR

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=findquery&siteid={siteid}&entityname={entityname}/?

Request & Query Parameters

Parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of where the entity will be stored.
entityname The entity's name. For example: ?entityname=content
expand Optional. A comma-separated list of entity relationshipds that you would like expanded in the result. You can use '*' to have all entity relationships expanded: ?expand=kids,crumbs
expandDepth Optional. The amount of levels that when a match is found in the expand parameter it will be expanded: ?expand=kids,crumbs&expandDepth=2
fields Optional. A comma-separated listed of fields to return. For example: ?fields=title,summary,contentid
maxitems Optional. Limit the number of records to return. For example: ?maxitems=10
itemsperpage Optional. Sets the desired number of items to return for each page. For example: ?itemsperpage=3
pageindex Optional. Sets the desired page for pagination. For example: ?pageindex=2
sort Optional. Control the sort order and direction of entities by specific attributes/fields. To sort decending, prefix the attribute/field with a minus sign (-). You may explicitly use the plus sign (+) to indicate the default setting of ascending. For example, to sort by "credits" ascending, and "title" decending: ?sort=credits,-title
cachedwithin Optional. Sets the desired cache timespan in seconds. For example: ?cachedwithin=120

Custom Query Parameters

Filter results by passing an attribute name of your entity, and the value to search for. Use the star (*) to denote wildcard.

The following example assumes the entity has an attribute named title:

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=findquery&siteid={siteid}&entityname={entityname}&title=about*

This should return any entities with a title attribute that starts with about, such as "about, About, About Us, About Time."

Example Response

If one or more entities are found, an items array will be present in the data object.

{
	"data": {
		"endindex": 1,
		"startindex": 1,
		"totalpages": 1,
		"totalitems": 1,
		"links": {
			"self": "http://domain/index.cfm/_api/json/v1/v10/?&sort=title&entityname=content&siteid=default&fields=title,summary,contentid&itemsperpage=10&maxitems=50&method=undefined&pageIndex=1"
		},
		"itemsperpage": 10,
		"items": [
			{
				"entityname": "content",
				"images": {},
				"contentid": "00000000000000000000000000000000001",
				"siteid": "default",
				"url": "/",
				"links": {
					"renderered": "http://domain/index.cfm/_api/json/v1/v10/_path/",
					"categoryassignments": "http://domain/index.cfm/_api/json/v1/default?method=findQuery&siteid=default&entityName=contentCategoryAssign&contenthistid=14CA1DCF-41A8-4D04-861AFE9D4162CD7C",
					"relatedcontent": "http://domain/index.cfm/_api/json/v1/default?method=findRelatedContent&siteid=default&entityName=content&id=00000000000000000000000000000000001",
					"crumbs": "http://domain/index.cfm/_api/json/v1/default?method=findCrumbArray&siteid=default&entityName=content&id=00000000000000000000000000000000001",
					"stats": "http://domain/index.cfm/_api/json/v1/default?method=findOne&siteid=default&entityName=stats&id=00000000000000000000000000000000001",
					"self": "http://domain/index.cfm/_api/json/v1/v10/_path/",
					"comments": "http://domain/index.cfm/_api/json/v1/default?method=findQuery&siteid=default&entityName=comment&contentid=00000000000000000000000000000000001",
					"site": "http://domain/index.cfm/_api/json/v1/default?method=findOne&entityName=site&siteid=default",
					"parent": "http://domain/index.cfm/_api/json/v1/default?method=findOne&siteid=default&entityName=content&id=00000000000000000000000000000000END",
					"kids": "http://domain/index.cfm/_api/json/v1/default?method=findQuery&siteid=default&entityName=content&parentid=00000000000000000000000000000000001"
				},
				"id": "00000000000000000000000000000000001",
				"title": "Home",
				"summary": ""
			}
		],
		"pageindex": 1
	},
	
	"method": "findQuery",
	"apiversion": "v1"
}

FindRelatedEntity

Gets a related entity.

Endpoint

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/{siteid}/{entityname}/{id}/{relatedentity}/?

OR

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=findquery&siteid={siteid}&entityname={relatedentity}&entitynamefk={id}

Request & Query Parameters

Path parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of where the entity will be stored.
entityname The entity's name.
id The ID of an entity.
relatedentity The name of a related entity.
entitynamefk The ID of an entity.

Example Response

If multiple entities are found, an items array will be present in the data object.

{
	"data": {
		"items": [
			{
				"id": "2C91A3B0-E375-9383-0B05636E6926868D",
				"name": "Example",
				"links": {
					"relatedentitylink1": "http://...",
					"relatedentitylink2": "http://..."
				}
			},
			{
				"id": "6C92F953-E042-CBF6-42F66BD74A4BEC0B",
				"name": "Another Example",
				"links": {
					"relatedentitylink1": "http://...",
					"relatedentitylink2": "http://..."
				}
			},

		]
	}
}

If only one entity is found, the data object will contain the entity's keys and corresponding values.

{
	"data": {
		"id": "2C91A3B0-E375-9383-0B05636E6926868D",
		"name": "Example",
		"links": {
			"relatedentitylink1": "http://...",
			"relatedentitylink2": "http://..."
		}
	}
}

FindCalendarItems

Get calendar content items.

Endpoint

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=findcalendaritems&siteid={siteid}&calendarid={calendarid}&start={start}&end={end}&format={format}

Request & Query Parameters

Path parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of where the entity will be stored.
calendarid The contentid of a Mura CMS content item with the "Type" set to Calendar.
start Optional. The start date to filter by.
end Optional. The end date to filter by.
format Optional. Options are default (the default setting) or fullcalendar. This determines how the response is formatted.
categoryid Optional. Filters results by categoryid.
tag Optional. Filters results by tag.

Example "Default" Response

If multiple entities are found, an items array will be present in the data object.

{
	"data": {
    	"items": [
			{
				"contentid": "2C91A3B0-E375-9383-0B05636E6926868D",
				"title": "Event 1",
				...
			},
			{
				"contentid": "6C92F953-E042-CBF6-42F66BD74A4BEC0B",
				"title": "Event 2",
				...
			},
			...
		]
	}
}

If only one entity is found, the data object will contain the entity's keys and corresponding values.

{
	"data": {
		"contentid": "2C91A3B0-E375-9383-0B05636E6926868D",
		"title": "Event 1",
		...
	}
}

Example "FullCalendar" Response

An events array will be returned.

{
	"events": [
		{
			"contentid": "2C91A3B0-E375-9383-0B05636E6926868D",
			"title": "Event 1",
			...
		},
		{
			"contentid": "6C92F953-E042-CBF6-42F66BD74A4BEC0B",
			"title": "Event 2",
			...
		},
		...
	]
}

GenerateCSRFTokens

Get a CSRF (Cross-Site Request Forgery) token. The CSRF token is required for special API methods such as delete and save.

Endpoint

GET https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=generateCSRFTokens&siteid={siteid}&context={entityid}

Request & Query Parameters

Parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of where the entity will be stored.
context The primary id of the entity currently being processed.

Example Response

{
	"data": {
		"csrf_token": "469153A8855FFA7ECB5A11BC8EB3F3C4",
		"csrf_token_expires": "42041.6270023"
	}
}

Save

Save an entity.

Endpoint

POST https://yourdomain.com/{context}/index.cfm/_api/json/v1/{siteid}/{entityname}/?csrf_token={csrf_token}&csrf_token_expires={csrf_token_expires}

OR

POST https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=save&siteid={siteid}&entityname={entityname}&csrf_token={csrf_token}&csrf_token_expires={csrf_token_expires}

Request & Query Parameters

Path parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of where the entity will be stored.
entityname The entity's name.
csrf_token The csrf_token is generated by the generateCSRFTokens method.
csrf_token_expires The csrf_token_expires is generated by the generateCSRFTokens method.

Example Response

The data object will contain the entity's keys and corresponding values.

{
	"data": {
		"id": "2C91A3B0-E375-9383-0B05636E6926868D",
		"name": "Example",
		"links": {
			"relatedentitylink1": "http://...",
			"relatedentitylink2": "http://..."
		}
	}
}

Delete

Delete an entity.

Endpoint

DELETE https://yourdomain.com/{context}/index.cfm/_api/json/v1/{siteid}/{entityname}/{id}/?csrf_token={csrf_token}&csrf_token_expires={csrf_token_expires}

OR

DELETE https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=delete&siteid={siteid}&entityname={entityname}&id={id}&csrf_token={csrf_token}&csrf_token_expires={csrf_token_expires}

Request & Query Parameters

Path parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of where the entity is stored.
entityname The entity's name.
id The ID of the entity.
csrf_token The csrf_token is generated by the generateCSRFTokens method.
csrf_token_expires The csrf_token_expires is generated by the generateCSRFTokens method.

Example Response

The data element will be an empty string.

{
  "data": ""
}

Login

Login a user.

Endpoint

POST https://yourdomain.com/{context}/index.cfm/_api/json/v1/{siteid}/login/?username={username}&password={password}

OR

POST https://yourdomain.com/{context}/index.cfm/_api/json/v1/?method=login&siteid={siteid}&username={username}&password={password}

Request & Query Parameters

Parameter Value
context The path to where Mura CMS resides within the webroot (typically, an empty string).
siteid The SiteID of the site attempting to login to.
username The User's username.
password The User's password.

Example Response

{
  "data": {
	"status": "success"
  }
}

data object

Key Value Type Value Description
status string success or failed, depending on the result of the attempt to login.

Decoupled Mura & JS Frameworks

You can build Decoupled Mura websites using any JavaScript framework, or plain Vanilla JavaScript as well. We have developed several example integrations for you to use as a starting point to understanding how to best accomplish this.

Vanilla JavaScript

The MuraDecoupled GitHub project is an example of building a Mura Decoupled website using standard JavaScript and Mura JS. The project is a Docker repository, so you will need that installed on your computer. This will allow you to quickly spin up the example and begin exploring a vanilla Mura / Javascript  integration.

Initial Setup

You will need a couple of items installed on your test machine. The latest version of Node, as well as Docker.

Next you will need the project itself. You should download MuraDecoupled to a working directory (i.e. a directory where the code can be deployed).

Next you need to start Mura up:

git clone https://github.com/murasoftware/mura-decoupled.git
cd mura-decoupled
git checkout master
docker-compose up

Then go to http://localhost:8888 to initialize Mura's install. You can login with the default (admin/admin) and edit the default site's settings:

  • Domain= The domain of the remote site that the the content will be served on. (localhost)
  • Is Remote = true
  • Remote Context = The directory structure off of the remote site's web root that the site lives (Leave Empty)
  • Remote Port = The port of the remote site (80)
  • Resource Domain = The domain that Mura will use the access resource like css and js scripts that are dynamically loaded. (localhost)

You can now visit the site at http://localhost

And finally go to your Mura admin (http://localhost:8888/admin) and reload Mura one more to and it will see the mura.config.json from the ./app directory.

For the rest of the details on this project, you can view the ReadMe file on the MuraDecoupled project itself.

Key Project Files

There are several key project files you will want to examine and/or modify while exploring this example project.

Directory or File Editable Description
/index.html Yes This is the landing page for the website, and contains only a) a reference to the local Mura JS instance, the application's /app/index.js file, and the most basic HTML required (HEAD/BODY tags).
/app/index.js Yes This is your core JavaScript project file, the one that does all the required work of fetching content via Mura JS and rendering it to the /index.html <BODY> tag.
/app/mura.config.json Yes This is the configuration file for the project. If you are familiar with Mura, you will recognize many of the standard configuration settings. This is where you register templates and configuration variables, register modules, and adjust site settings.
/app/configurators/examplejs.html Yes This is the base file for the ExampleJS module, a custom module built for this site.

 

JavaScript

The /app/index.js in the vanilla (sans any external frameworks like jQuery) JS Decoupled example is the core JavaScript project file, containing all of the JavaScript-related to rendering the base decoupled website, where Mura JS acts as both the DOM selector and framework-agnostic ... framework.

The first thing to be aware of is where the content is rendered to.

let currentContent;

This is the variable where the current content is registered to. Note that this becomes a Mura JS "Content" object, containing not only the associated content but a range of helper functions to render the page (i.e. navigation).

Next is the Mura initializaiton function:

Mura.preInit(function(){

   // your init code here

}

The "preInit" function is called as a precursor to the Mura "render" function, so that all actions taken here are pre-render. This can be important if your code informs what will take place on the page (such as the instantiation of Modules that the page will need to render, as per this example).

Within this block, we will create a number of custom Mura  Modules, which are self-contained and often dynamic components that make up a decoupled website's page. For instance, the page's header, footer, any sidebar or main content area, can all be created as Modules. Custom Modules can also be created, and contained within any of these aforementioned layout Modules.

Custom Module

In this example, we first create a custom module for our project called "Examplejs".

Mura.Module.Examplejs = Mura.UI.extend({
    renderClient: function() {
        this.context.mytitle = this.context.mytitle || "The 'Title' param has not been assigned";
        this.context.targetEl.innerHTML=`<h2>${Mura.escapeHTML(this.context.mytitle)}</h2>`;
        return this;
    }
});

Note that this Module, and indeed all Modules, are all extensions of the Mura.UI class. This class provides the important renderClient() and renderServer() methods that render your content.

You will notice that Modules are essentially an encapsulated, reusable component with their own render function. If you recall from the Creating Custom Modules section, this.context is the variable that holds all data associated to the module, including any settings registered via the configurator when it is added to the page.

Note that this.context contains one reserved variable name, this.context.targetEl. This is the target element to which your rendered content is applied, which is then rendered by the renderClient() function.

Layout Modules

We next find the actual components. First the Header Module, which will render our breadcrumbs and navigation:

Mura.Module.Header = Mura.UI.extend({
   //...
});

We achieve this by referencing the current (referenced as currentContent) .get('crumbs') method. If you examine the JSON API endpoint of any content page, you will see a variety of endpoints available, including one called "crumbs". This request returns an array of shorthand "minimal" content beans called ContentNavBeans. We then render the data returned by this request:

const crumbCollection=await currentContent
    .get('crumbs')
    .then((crumbs)=>{
        return crumbs;
    });

As with all Mura JS asynchronous requests, the response is returned as a Promise and in this example uses the await operator to handle the request.

Next we have a custom Module called Examplecollectionlayout, which is included as an example of how you might render a Collection in a decoupled website.

Mura.Module.Examplecollectionlayout = Mura.UI.extend({
   renderClient: function() {
// ... rendering code here
   }

});

You will find several important references to this in the mura.config.Json, which should help you understand the context of how these are registered specifically as "collection"-rendering objects. This Module includes several key concepts related to collections, such as paging navigation, via helper methods that are included in the context of any Mura Collection.

Note that this Module contains a conditional scroll, where new content is automatically called (asynchronously and then appended to the bottom of the page as the user scrolls downward. This is of course not a necessary embellishment, but is a commonly used one and good example of the flexibility of these types of requests.

With these two Modules sorted, we have reached the point where we are ready to render the page. All of this will occur in the encapsulated Mura() namespace, which ensures that all pre-render actions have been completed.

Mura(function(){

});

The first thing we have here is our layout template. A decoupled site can contain any number of templates (as defined in your mura.config.json file), but for this example we have one, "default", defined as follows:

const templates={
    default:`
        <nav id="primary-nav">
            <ul class="mura-primary-nav"></ul>
        </nav>
        <div class="mura-region-container" data-region="primarycontent"></div>
        <footer>
            <!-- Create a component named "Footer" and it will render here-->
            <div class="mura-object" data-object="component" data-objectid="footer"></div>
        </footer>
        <div class="mura-html-queues"></div>
        `
 };

In this template we defined several regions: a place for our navigation (mura-primary-nav), a container for our primary content (mura-region-container), and a footer. There is also a mura-html-queues which we use as a container to load any queued assets. Let's look a closer look at the footer, as this is an example of how you can hard-code a Module into your templates.

Footer

<footer>
   <!-- Create a component named "Footer" and it will render here-->
   <div class="mura-object" data-object="component" data-objectid="footer"></div>
</footer>

In this code you can see the div contains a class of mura-object and an attribute data-object of "component". When an object has a class of mura-object, this tells Mura JS that this is a Module, upon which the the element's data-object will be used to determine which Module is being referred to. In this case, we are referring to one of Mura's baked-in Modules, the Component Module.  Finally, there is also a data-objectid of "footer", which identifies the specific Component we want loaded. You can apply this same methodology to bake in custom Modules, such as the ones we created above.

Rendering

Now that we have a complete template, we can begin the process of rendering the page. We create a dedicated render() function, as we want to refer to this function whenever the hash of the page changes (as when a user clicks on a navigation link).

First, lets look at the request that loads the current hash (url) and retrieves the content:

let hash= location.hash || '#';

let query=Mura.getQueryStringParams();

Mura.renderFilename(
    hash.split('#')[1],
    Mura.extend(Mura.getQueryStringParams(),{expand:"crumbs"})
).then(function(content){

  currentContent=content;

});

In this example, we are retrieving the "hash" of the url and passing this to the renderFilename() function, which takes not only the hash but any  query string params, and uses those to retrieve the requested content page from Mura.

 

 

Configuration

The /app/mura.config.json file contains the configuration required to assemble your decoupled Mura website. This is where you will enter all of your global and site-specific configuration, settings specific to the Layout Manager, identify modules and even deploy custom Mura ORM objects.

{
    "global":{
        "rendererProperties":{
            "templateArray":["Default"],
            "collectionLayoutArray":["Examplecollectionlayout"],
            "hashurls":true,
            "primaryContentTypes":"Page,Link,File",
            "spacingoptions":[
                {"name":"Tight","value":"tight"},
                {"name":"Normal","value":"normal"},
                {"name":"Loose","value":"loose"}
            ],
            "modulethemeoptions":[
                {"name":"Brand Default","value":"module-brand"} // ...
            ],
            "coloroptions":[
                {"name":"White","value":"#ffffff"} // ...
            ],
            "ckeditor":{
                "contentcss":"http://localhost/app/css/editor.css",
                "templates":"",
                "stylesset":"",
                "customconfig":""
            }
        },
        "modules":{
            "Examplejs":{
                "name":"Examplejs",
                "contenttypes":"*",
                "configurator":[
                    {"type":"text","name":"mytitle","label":"My Label"}
                ]
             },
            "Examplecollectionlayout":{
                "name":"Example Collection Layout",
                "contenttypes":""
            },
            "Header":{
                "name":"Header",
                "contenttypes":"*"
            }
        },
        "entities":{
        },
    "sites":{
        "default":{
            "rendererProperties":{
            },
            "modules":{

            }
        }
    }
  }
}

                                         

Configuration Description
global Contains all configuration information for the decoupled Mura instance.
  renderProperties Configuration related to the rendering of content and Layout Manager.
    templateArray An array of template names, as defined within the JavaScript code.
    collectionLayoutArray An array of Module names that are to be treated as Collections.
    hashurls If set to true, the renderClient() function will update when the url hash changes.
    primaryContentTypes The list of Content Types that will be displayed in the Front End Editing Toolbar.
    spacingOptions Spacing options defined for the Layout Manager
    moduleThemeOptions Classes available to Module themes in the Layout Manager
    colorOptions Named colors available to Module themes in the Layout Manager
    ckeditor Editor-specific configuration
  entities An array of Mura ORM objects described in JSON (see the Assembler for details)
  modules The custom modules identified in the project's JavaScript (see Decoupled JavaScript)
  sites Contain a list of all the Mura sites available in the Front End Editing Toolbar
    default An example "default" site identified in the site Array
      renderProperties (et.al.) All configuration options available to global can be set here as well

You can of course include your own configuration information here, and access it via Mura.get('value'). For more insight into these settings, you may want to examine the Content Renderer documentation, as all settings there pair to the ones you see above.

 

Angular

The MuraAngularDecoupled GitHub project is an example of building a Mura Decoupled website using Angular as your JS framework. The project is a Docker repository, so you will need that installed on your computer. This will allow you to quickly spin up the example and begin exploring the Mura JS / Angular integration.

The latest up-to-date instructions are provided with the repository.

React & NextJS

The MuraNextJSDecoupled GitHub project is an example of building a Mura Decoupled website using the React framework NextJS as your JS framework. The project is a Docker repository, so you will need that installed on your computer. This will allow you to quickly spin up the example and begin exploring the Mura JS / React - NextJS integration.

The latest up-to-date instructions are provided with the repository.

Vue & NuxtJS

The MuraNuxtJSDecoupled GitHub project is an example of building a Mura Decoupled website using the Vue framework NuxtJS as your JS framework. The project is a Docker repository, so you will need that installed on your computer. This will allow you to quickly spin up the example and begin exploring the Mura JS / Vue - NuxtJS integration.

The latest up-to-date instructions are provided with the repository.