Mura 10: Mura.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:
- How to target the container element of where the module has been applied to the layout.
- 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);
}
});
});