In BackboneJS, classes/objects are called Models and groups of these models go into Collections. Using RequireJS, you define something and return that something for use in other files. In many BackboneJS examples, I’ve seen say a Person Model and a Person Collection stored in separate files, with each one being imported separately to the script they’re used in. Seeing as you generally use Models and Collections together at the same time, I was looking for a way to have them both in the one file. The solution is pretty simple and involves some simple Javascript object manipulation. I should give credit for this solution to my friend Ronan, although I’m not sure if he came up with it originally!
So, just to illustrate the normal Backbone way of doing things, sticking wih our Person class/object, you might have 2 files in your JS folder, models/person.js and collections/persons.js, as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| // models/person.js
define(['underscore', 'backbone'],
function(_, Backbone) {
Person = Backbone.Model.extend({
url: '/person',
// ... etc
});
return Person;
}
);
//collections/persons.js
define(['underscore', 'backbone', 'models/person']
function(_, Backbone, Person) {
Persons = Backbone.Collection.extend({
model: Person,
// ... etc
});
return Persons;
}
);
// some other JS file
require('models/person');
require('collections/persons');
var people = new Persons();
people.add(new Person()); |
So, I’m looking for a way to just have one require
‘d file, that will import both my Model and Collection. A way this can be achieved is by defining a Person as a standard Javascript object, then having Model and Collection as attributes of that object. We need to add each attribute one step at a time, we can’t just do it all in one big Person = { // ... }
block. Then, once the file is imported, we can do stuff like var people = new Person.Collection();
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| // person.js
define(['underscore', 'backbone'],
function(_, Backbone) {
var Person = {}; // create initial object
Person.Model = Backbone.Model.extend({
url: '/person',
// ... etc
});
// in order to access the Person.Model, we have to specify each attribute separately
Person.Collection = Backbone.Collection.extend({
model: Person.Model,
// ... etc
});
return Person;
}
);
// some other JS file
require('person');
var people = new Person.Collection();
people.add(new Person.Model()); |
Read more...
I’ve been working alot with RequireJS lately and love the whole modular JS movement, where blocks of code are safely contained within separate functions, that can be loaded in when necessary, without worrying about variable/function name clashes. Generally each module is stored in it’s own file but you can combine several modules into one giant file, in theory containing your entire JS code for your site!
When working with Google Maps, I was thinking it’d be nice to simply extend the google.maps.Map
class and add in my own functions, e.g. showLoading
to display a nice, semi-transparent ‘Loading…’ div in the centre of the map while the code does an AJAX request to get some data. So, you’d end up with something like:
1 2
| var map = new Map();
map.showLoading(); |
So, first thing you need to do is add the Asynch plugin for requireJS to your requireJS config. For external plugins, I tend to source them from https://cdnjs.com/ if possible, cutting down on some of my own server bandwidth. I have my RequireJS config in my require.min.js
file, after the main block of minified code. Here’s an edited version, with the asynch plugin on line 6:
1 2 3 4 5 6 7 8 9 10 11 12 13
| requirejs.config({
'baseUrl': '//cdn.mydomain.com/js',
'paths': {
'jquery': 'libs/jquery.min',
'underscore': 'libs/underscore.min',
'async': '//cdnjs.cloudflare.com/ajax/libs/requirejs-async/0.1.1/async'
},
'shim': {
'underscore': {
exports: '_'
},
}
}); |
Next up, we have a map.js
file, which, via the define
function, we’ll pass UnderscoreJS and the Google Maps JS code using the Asynch plugin. This file is going to return a Map
object, which we can then use to instantiate new instances of the class. The file starts off with a Map
function, which takes as a parameter the id of the div you want to put the map into and the latitude & longitude of the map’s centre. Obviously you could pass more parameters if you wish, e.g. initial zoom level etc. This Map function will then create a variable of type google.maps.Map
, set the map up and return the variable as output from the function. This way when you do var myMap = new Map();
, your myMap
will be able to call all of google.maps.Map
‘s functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| define(
['underscore', 'async!https://maps.googleapis.com/maps/api/js?sensor=false'],
function(_) {
var Map = function (div, lat, lng) {
zoom: 15,
center: new google.maps.LatLng(lat, lng)
});
return map;
}
// more code below to follow...
return Map;
}); |
We also need to give our Map
‘s protoype the google.maps.Map
prototype, which is simply:
1
| Map.prototype = google.maps.Map.prototype; |
Finally, if we want to add our own functions to our Map
, we use UnderscoreJS to extend the Map.prototype
with our functions. So, for example, I have one called isBigDrag
to detect if the user has dragged the map a reasonable amount to fetch more data, as well as showLoading
and hideLoading
to show/hide my nice ‘Loading…’ div.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| _.extend(Map.prototype, {
isBigDrag: function (prevCentre) {
var currentCentre = this.getCenter();
// .. etc
},
showLoading: function() {
// etc.
},
hideLoading: function() {
// etc.
}
}); |
See below for the entire file, which is stored in ‘classes/map.js’ in baseUrl
above.
To pass this new Map
class into a script and to be able to do var myMap = new Map();
, we do so via a call to requirejs in the script, as follows:
1 2 3 4 5 6 7
| requirejs([
'underscore', 'classes/map',
], function(_, Map) {
var myMap = new Map('id', 50.0, 10.0);
myMap.showLoading();
// etc.
}); |
Took me a while to figure it all out but works pretty well now. The classes/maps.js file in it’s entirety is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| define(
['underscore', 'async!https://maps.googleapis.com/maps/api/js?sensor=false'],
function(_) {
var Map = function (div, lat, lng) {
zoom: 15,
center: new google.maps.LatLng(lat, lng)
});
return map;
}
Map.prototype = google.maps.Map.prototype;
_.extend(Map.prototype, {
isBigDrag: function (prevCentre) {
var currentCentre = this.getCenter();
// .. etc
},
showLoading: function() {
// etc.
},
hideLoading: function() {
// etc.
}
});
return Map;
}); |
Read more...