- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
This will be the first post of the series on getting Backbone.js for designing better Single page apps and UI Pages in Service Now.
Back in 2010-2011, When I started working on Service Now,We were struggling to learn Jelly to code UI pages in Service Now.Now in 2013, most of us already know the best practices in Jelly, and there are lots and lots of material/blogs/support/videos available on Jelly. So in a way, we're lot better that what we were(at least I) back on 2011.What Jelly doesn't do is, it doesn't separate your DOM from the Data.And this series of posts on Backbone.js will concentrate on separating your DOM from your data, and easily attach events/modify DOM and touch base on some good features of Backbone worth mentioning.
Why Backbone?
To store the data you are working with in to objects, so you can modify them, move them, store them into tables, without muddling with jQuery/Prototype DOM Selectors.
We will introduce something called MVC Javascript framework, for managing your apps/UI pages better.
What's effect on the page load as we are loading many Custom libraries:
There definitely will be an increase in Page load time,because you will be loading jQuery.min,,underscore.js and backbone.js. Users will see a very tiny lag of about 1000 ms.But believe me, If you are okay with this lag, your huge UI Page will become 10x manageable and extensible.
So the rule is, If you are writing a small UI page, better not to use MVC, but you are writing something huge in your CMS, or a UI Page, using backbone.js might help.
Today, we will see a very simple UI Page written using Backbone.js MVC. I'll in this post discuss:
[*] How to get Backbone.js,Underscore.js and Handlebars.js to Service Now, and how to make them point to jQuery on the page.
[*] A comparison between initializing your models and rendering the data against rendering the data using
.
<g:evaluate>
I won't however discuss on What a model is, what a collection is and how they are related. There are excellent tutorials all over the internet.
Getting Backbone.js into Service Now:
[*] Download backbone.min.js from here : http://backbonejs.org/backbone-min.js
[*] Copy it to a UI Script.
[*] Modify the
to point to jQuery like this :
$
a.$=t.jQuery||t.$j||t.Zepto||t.ender||t.$
Getting Underscore.js into Service Now:
[*] Download Underscore.min.js from here : http://underscorejs.org/underscore-min.js
[*] Copy it to a UI Script.
Underscore.js doesn't have any dependency on jQuery.So you need not do any change to UI Script.
Getting HandleBars.js into Service Now:
Those who already worked with Underscore.js might ask me why I'm going with Handlebars, when Underscore has a templating function too. The reason is ServiceNow's HTML field doesn't allow
<% %>
. I know what you are thinking:"We can escape it right?" Yes, we can, and the HTML field will allow this, but templating won't work.Hence I had to fall back to Handlebars, which is excellent btw, and uses
{{ }}
, that doesn't create any issues.
[*] Download Handlebars.js from here : http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0-rc.3/handlebars.min.js
[*] Copy it to a UI Script.
Once you have set everything up, Let's get our UI Page running.
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<!-- Import all our UI Scripts -->
<script></script>
<script></script>
<script></script>
<script></script>
<head>
<style>
.done{
border:0;
readonly : true;
}
</style>
</head>
<body>
<table width="772" style="margin-left:100px" id="ghours">
<tr style="background-color:#64AFEB; border :1px solid white; height:30px">
<th width="195" scope="col">Group Name</th>
<th width="195" scope="col">Number of Hours</th>
<th width="194" scope="col">Month</th>
<th width="82" scope="col">$[sp]</th>
<th width="82" scope="col">$[sp]</th>
</tr>
<g:evaluate>
gr = new GlideRecord("u_estimate_table");
gr.query();
</g:evaluate>
<input type="hidden" value="${gr.getTableName()}" id="tableName"/>
<script>
<th width="195" ><input type="text" value ="{{ Group }}" class="done" ></input></th>
<th width="195" ><input type="text" value = "{{ Hours }}" class="done" ></input></th>
<th width="194" ><input type = "text" value = "{{ Month }}" class="done" ></input></th>
<th width="82" ><input type = "button" class="editT" value="Edit"></input></th>
<th width="82" ><input type = "button" class="removeT" value ="Remove" ></input></th>
</script>
<<j:while test="${gr.next()}">
<tr class="old" id="${gr.sys_id}">
<th width="195" ><input class="done" type="text" value ="${gr.u_group.name}" ></input></th>
<th width="195" ><input type="text" value = "${gr.u_hours}" class="done"></input></th>
<th width="194" ><input class="done" type = "text" value = "${gr.u_month}"></input></th>
<th width="82" ><input type = "button" class="editT" value="Edit" ></input></th>
<th width="82" ><input type = "button" class="removeT" value ="Remove"></input></th>
</tr>
</j:while>
</table >
I'm not very good at CSS, so bear with me and my tables. The above HTML doesn't nothing but to set up 3 columns, By the names Group, Hours and Months.We are retrieving these values from a table called u_estimate_table, which has a Group reference, the number of Hours the Group worked and the month.
As I mentioned earlier, our focus today will be on retrieving the data from table u_estimate_table and display it using Jelly and Backbone.js.
Let's first do it using Jelly -
In Jelly, you write a Glide Evaluate tag, and Iterate using a loop, which is exactly what I'm doing in the UI Page.
Two, We need to register all the rows created because of Jelly as Models, and fill our Collections from the models. This is done in the Client Script of UI page like this :
(function($){
window.App = {
Models : {},
Collections : {},
Views : {}
};
//A single estimate
App.Models.Estimate = Backbone.Model.extend({
});
//A collection of estimates which takes a model "estimate"
App.Collections.Estimates = Backbone.Collection.extend({
model : App.Models.Estimate,
initialize: function() {
console.clear();
_.each(
$('#ghours .old'),
function(a){
// Create the model
var est = new App.Models.Estimate();
var gname = $(a).find('input')[0];
$(gname).attr('readonly', true);
var hours = $(a).find('input')[1];
$(hours).attr('readonly', true);
var month = $(a).find('input')[2];
$(hours).attr('readonly', true);
var sys_id = $(a).children()[0];
// set the model correctly
est.set({
Group: $(gname).val(),
Hours: $(hours).val(),
Month:$(month).val(),
table: $('#tableName').val(),
sys_id:$(a).attr('id'),
});
var estView = new App.Views.Estimate({
model: est,
el: a
});
// Add this new model to the collection
this.add(estView);
},
this
);
}
});
//The collection view.To paint the big picture.
App.Views.Estimates = Backbone.View.extend({
render : function(){
this.collection.each(this.addTo,this);
return this;
},
addTo:function(estimate){
var dir = new App.Views.Estimate({model:estimate});
$('#ghours > tbody:last').append(dir.render().el);
}
});
//An individual model view.
App.Views.Estimate = Backbone.View.extend({
tagName: 'TR',
defaults: {
sys_id : '-1'
},
initialize:function(){
this.model.on('change',this.render,this);
this.model.on('destroy',this.destroy,this);
},
destroy:function(){
this.$el.remove();
},
events: {
'click .editT':'editAll',
'dblclick .done' : 'edit',
'blur .editTab': 'doneOne',
'click .removeT':'removeRow'
},
render :function(){
var source = $("#taskTemplate").html();
var template = Handlebars.compile(source);
var src = template(this.model.toJSON());
var a = this.$el.html(src);
this.$el.attr('id', this.model.get('sysId'));
return this;
}
});
var estimates = new App.Collections.Estimates();
console.log(estimates.toJSON());
}
})($j);
Notice the last line where we write
var estimates = new App.Collections.Estimates();
which will initialize the collection, called estimate. Scroll up to the initialize method, in which I'm reading all the rows, on the page and then converting them to Models and adding them to collection. Now all the rows rendered using
<g:evaluate>
are converted into a collection.
Handle everything using Backbone:
Now,Let's comment the Jelly g:evaluate and j:while , and handle it using Backbone.js Collection initialization by passing a JSON Object.
//Call a script Include, as we are on Client Side, for an Asyc call -
var ga = new GlideAjax('HelloWorld');
//Call my super super Script Include which returns the table data in JSON format -
ga.addParam('sysparm_name','sendToClient');
ga.addParam('sysparm_tbl','u_estimate_table');
ga.addParam('sysparm_opt','u_group,u_hours,u_month');
ga.addParam('sysparm_bool','true');
ga.getXML(HelloWorldParse);
function HelloWorldParse(response) {
var answer = response.responseXML.documentElement.getAttribute("answer");
//Getting a JSON Object, as answer will be a JSON String
var jsonArray = $.parseJSON(answer);
//Notice the difference here, In the earlier method, we just initialize the new App.Collections.Estimates(jsonArray);//Object, But here we pass a JSON object. When you pass a JSON object to a collection, it will automatically convert them into Models..
// and render it. In the render will be using HandleBars.js to template it. I'll expand on this templating more in my next post.
var estimates = new App.Collections.Estimates(jsonArray);
var tasksView = new App.Views.Estimates({collection:estimates}).render();
}
More on my Script Include : SendJSON2
Comparison :
Let's compare both the methods and see which one is better. For comparison, I used a simple Chrome extension called test benchmark-
In the graph above,
[list]
[*]A - represents the Page Load time when I ran the UI page with Method 1, using Jelly and registering Models.
[*]B - represents the Page Load time when I ran the UI page with Method 2,completely using Backbone.
Notice that, The Method 1 loads 2000 ms faster than Method 2. Hence I would go by using Method1.
Take Away:
When you are working with Backbone.js, always use the super <g:evaluate>, Don't use the native Backbone.js JSON initialization.
The above are the two ways to initialize a Model and a collection and bring Backbone.js to Service Now. We've barely scratched the surface.There is lots to come on events, and views which I'll slowly introduce in my next posts, along with the best ways you can define your Attributes of a model so that they integrate seamlessly with Service Now!
Note: You can also get the JSON array using an AJAX call. But I don't want to involve another processor in the middle. So the Script Include.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.