Dollar Slice
🍕 "Cheap and easy."
Client-side micro-framework with heavy inspiration from Angular and Backbone.
We use this micro-framework to increase the speed of JavaScript initialization. The familiar syntax and narrow scope reduce the need for onboarding and training. Dollar slice works best when you don't need the data-binding, routing, and complexity of a Single Page App, but you still want your client-side code to be more structured than a bag of jQuery plugins.
Dollar Slice divides client-side code into controllers and services. Controllers control a portion of the page. Services are reusable singletons that controllers can share. Both are deliberately designed to create testable and maintainable code.
Installation
npm install --save dollar-slice
You probably have some sort of build process, so just make sure it knows about /index.js
.
##Table of Contents
##Basics
The syntax of a controller is a simplified version of AngularJS for familiarity. This is the basic outline of a controller:
DS.controller('name', function () {
return function () {
};
});
Controllers are very lightweight. Instantiating a controller for a list is acceptable, but you could also create a controller for each item in the list and the performance will be comparable to creating that number of objects normally.
To instantiate a controller, use get
:
var controller = DS.get('name', el);
Controllers are always bound to an element on the page, so one of the arguments passed to get
must be a plain DOM element. Any number of arguments can be passed to get
, and they will become the parameters of the controller's constructor. For example:
DS.controller('name', function () {
return function (message, el, answer) {
console.log(message, el, answer);
};
});
var el = document.querySelector('body');
var controller = DS.get('name', 'hello there!', el, 42);
It is often useful to save the controller's element, which makes it available to functions in the controller's prototype. Conversely, it's also very useful to not save the element, because DOM elements are very large and saving them in memory is expensive. In any case, this is an example of saving an element that was passed as the first argument.
DS.controller('name', function () {
var constructor = function (el) {
this.el = el;
};
constructor.prototype = {
count: function () {
var el = this.el;
return el.children.length;
}
};
return constructor;
});
##Event Binding
Controllers have a highly performant shorthand for binding events. Its syntax is the same as BackboneJS for familiarity. It uses native DOM event binding for speed and binds each event's function to the controller so that the meaning of this
is consistent. All css selectors are valid, and events with no selector apply to the controller's root element.
DS.controller('list', function () {
var constructor = function (el) {
this.iam = 'a click example';
};
constructor.prototype = {
events: {
'scroll': 'onScroll',
'.item click': 'onItemClick'
},
onScroll: function (e) {
var el = e.target;
el.style.opacity = el.scrollTop / el.clientHeight;
}
onItemClick: function (e) {
console.log('clicked', e.target, 'and I am', this.iam);
}
};
return constructor;
});
##Shared Variables and Functions
Controllers can have variables and functions that are shared between each instance, saving memory and keeping code DRY. These shared variables and functions do not have access to this
.
DS.controller('name', function() {
var defaultMessage = ' was clicked!';
function logMe(message) {
console.log(message);
}
});
Shared variables and functions can be referenced from anywhere inside the controller.
DS.controller('list', function() {
var defaultMessage = ' was clicked!';
var constructor = function() {}
constructor.prototype = {
events: {
'click': 'onClick'
},
onClick: function(e) {
console.log(e.target + defaultMessage);
}
};
return constructor;
});
##Services
The syntax of a service is a simplied version of AngularJS:
DS.service('name', function () {
var items = [];
this.getItem = function(index) {
return items[index];
};
});
Just like AngularJS, using an array as the second parameter defines that controller's dependencies, which can be services or values. This format is very useful because of JavaScript minification, which changes the names of variables in scopes to things like a
, b
, c
, etc, but leaves the strings defining a controller's dependencies alone.
DS.service('myService', function () {
console.log('service created');
});
DS.controller('myController', ['myService', function (myService) {
return function (el) {
console.log('controller created for' + el.tagName);
};
}]);
DS.get('myController', document.body);
Services are only created once, and only when they're a dependency of an instantiated controller. If a controller that uses the service is never requested, then the service will never be created.
DS.service('lonelyService', function() {
console.log('lonely service created!');
});
DS.controller('myController', ['anotherService', function(anotherService) {
return function(el) {
console.log('controller created!');
};
}]);
DS.get('myController', document.body);
##Values
Values are useful for storing config and providing safe references that can be mocked during testing.
DS.value('$', jQuery);
DS.value('_', lodash);
DS.value('$document', document);
DS.value('config', {'hostname': 'nymag.com', 'env': 'prod'});
Values can be injected as well. For example:
DS.controller('list', ['$', '_', 'config', function ($, _, config) {
return function () {
$(this.el).find('.items:first').fadeIn();
console.log('list config:', _.pick(config, 'env'));
};
}]);
##Full Example
Here's what a fully-featured controller looks like. Note the shared variables and functions, event handlers, and dependency injection.
DS.controller('item-controller', ['myService', function(myService) {
var defaultMessage = ' was clicked!';
function logMe(message) {
console.log(message);
}
var constructor = function (el) {
this.button = el.querySelector('button');
this.tagName = el.tagName;
};
constructor.prototype = {
events: {
'click': 'onClick',
'button click': 'onButtonClick'
},
onClick: function() {
logMe(this.tagName + defaultMessage);
},
onButtonClick: function(e) {
var buttonName = this.button.tagName;
e.stopPropagation();
logMe(buttonName + ' was clicked, but the click event won\'t propagate!');
myService.trigger('customevent');
}
};
return constructor;
}]);