svc.js | |
---|---|
(function (namespace, version){
if (typeof(namespace) === "undefined") {
namespace = {};
} else if (! (typeof(namespace) === "object" || typeof(namespace) === "function")) {
throw "Failed to initialize SVC - invalid namespace";
}
var svc = namespace.svc = {};
svc.VERSION = version; | |
Subject | |
The main work horse of our framework. This is a stripped down version of the Subject; it provides a very simple equality test, the ability to destroy itself, notify events, and subscribe to/unsubscribe from any notifications. | svc.Subject = Class.create({ |
Our constructor just sets up the mapping from notifcations to functions. | initialize: function (args) {
this._notificationToObservers = $H();
}, |
Equality test is just a strict equals against another | isEqual: function (subject) {
return this === subject;
}, |
Destroy self. Notify that we are dead, and clear out all subscribed functions. | destroy: function () {
this.notify('subject:destroy');
this._notificationToObservers = $H();
}, |
Notify that a particular | notify: function (notification) {
var observers = this._notificationToObservers.get(notification);
var args = $A(arguments);
|
Remove the notification from the arguments array. | args.shift();
|
Add the subject as the first argument. | args.unshift(this);
if (observers) {
observers.invoke('apply', null, args);
}
}, |
Add a subscription of a | subscribe: function (notification, f) {
var observers = this._notificationToObservers.get(notification);
if (observers) {
observers.push(f);
} else {
this._notificationToObservers.set(notification, [f]);
}
}, |
Remove a subscription of a | unsubscribe: function (notification, f) {
var observers = this._notificationToObservers.get(notification);
if (observers) {
this._notificationToObservers.set(notification, observers.without(f));
}
}
}); |
ModifiableSubject | |
ModifiableSubject is a more specific implementation of the subject, which provides the ability to set attributes and sending out default events for those events. It maintains flags to watch the state of the object. | svc.ModifiableSubject = Class.create(svc.Subject, { |
Create the object, setting the dirty flag to false. | initialize: function ($super, args) {
$super(args);
this._dirty = false;
|
TODO: automatically do silent sets for all values in args that aren't reserved | }, |
Retrieve the particular value of a | get: function (property) {
return this[':' + property];
}, |
Get all the | properties: function () {
var properties = $A();
for (var property in this) {
if (property.charAt(0) === ':') {
properties.push(property.slice(1));
}
}
return properties;
}, |
Set the object to be | clean: function () {
this._dirty = false;
this.notify('subject:clean');
}, |
Set the object to be | dirty: function () {
this._dirty = true;
this.notify('subject:dirty');
}, |
Set a | set: function (property, value, silent) {
if (this.get(property) == value) { return false; }
this[':' + property] = value;
if (! silent) {
this.notify('subject:change:' + property);
this.dirty();
}
return true;
}, |
Checks to see if the object has changes. | isDirty: function () {
return this._dirty;
}
}); |
Collection | |
| svc.Collection = Class.create(svc.Subject, { |
Our constructor expects one variable labeled | initialize: function ($super, args) {
$super(args);
this._collection = $A(args.collection || []);
this._sortFunction = args.sortFunction;
if (this._sortFunction) {
this._collection.sort(this._sortFunction);
}
},
|
Get a value in the | at: function (index) {
if (! this.inRange(index)) {
throw 'svc.Collection.at: invalid index.';
}
return this._collection[index];
}, |
Get a particular | get: function (subject) {
return this._collection.find(
function (entry) {
return entry.isEqual(subject);
}
);
}, |
Return all items in the collection. | getAll: function () {
return this._collection;
}, |
Determines where a | indexOf: function (subject) {
var index = -1;
this._collection.find(
function (entry) {
++index;
return entry.isEqual(subject);
}
);
return index === this.size() ? -1 : index;
}, |
Determines whether or not an index fits into the | inRange: function (index) {
return index >= 0 && index < this.size();
}, |
Returns the size of the collection. | size: function () {
return this._collection.length;
}, |
Add a | add: function (subject) {
if (this.get(subject)) { return; }
this._collection.push(subject);
if (this._sortFunction) {
this._collection.sort(this._sortFunction);
}
this.notify('collection:add', subject);
subject.notify('collection:add');
}, |
Remove all | clear: function () {
var cleared = this.getAll();
this._collection = $A();
cleared.invoke('notify', 'collection:clear');
this.notify('collection:clear');
}, |
Remove a single | remove: function (subject) {
var entry = this.get(subject);
if (! entry) { return; }
this._collection = this._collection.without(entry);
entry.notify('collection:remove');
this.notify('collection:remove', entry);
return entry;
},
_each: function (iterator) {
this._collection._each(iterator);
}
}); |
Mixin | svc.Collection.addMethods(Enumerable); |
View | |
The base View class of the SVC. It will automatically make a call to 'draw' the element on the page. If the element is already on the page, this function can be used to locate and store pointers to the elements on the page. It also handles destroying itself when told to. It provides some often used getter methods and also provides methods to subscribe to (and unsubscribe from) nofitications from the subject. | svc.View = Class.create({ |
Our constructor calls draw and subscribes a teardown method to the destroy event. It requires a
| initialize: function (args) {
this._subject = args.subject;
this._element = this.draw();
this._subscribedFunctions = $H();
this.subscribe('subject:destroy', this.tearDown.bind(this));
},
|
This will be where you define the main element of a view, you can also define other elements which can be stored as instance variables to reduce page wide scans. | draw: function () {
throw "View.js: draw must be defined in a subclass.";
},
|
This will need to go, but is required for now. | update: function () { |
stub method, needs to be impemented in subclasses as well. | },
|
The default method that gets called when the subject is destroyed. It removes the element from the page and unsubscribes all events. Feel free to override it as needed. | tearDown: function () {
var element = this.getElement();
if (element) {
element.hide();
var parentElement = element.parentNode;
if (parentElement) { parentElement.removeChild(element); }
}
this.unsubscribeAll();
},
|
Gets the main | getElement: function () {
return this._element;
},
|
Get the | getSubject: function () {
return this._subject;
},
|
Subscribe a | subscribe: function (notification, fn) {
this._subscribedFunctions.set(notification, fn);
this.getSubject() && this.getSubject().subscribe(notification, fn);
},
|
Unsubscribe from a particular | unsubscribe: function (notification) {
var fn = this._subscribedFunctions.get(notification);
this._subscribedFunctions.unset(notification);
this.getSubject() && this.getSubject().unsubscribe(notification, fn);
},
|
Clear out all subscribed functions for the view. | unsubscribeAll: function () {
this._subscribedFunctions.keys().each(this.unsubscribe.bind(this));
}
}); |
ActionView | |
| svc.ActionView = Class.create(svc.View, {
|
Our initializer takes an | initialize: function ($super, args) {
this._action = args.action;
this._controller = args.controller;
|
| this._field = null;
$super(args); |
We store the fire function so it can be unregistered later. | this._boundFireFunction = this.fire.bind(this);
this.observeDOMEvents(args.events);
}, |
Get the | getField: function () {
return this._field;
},
|
We associate the passed in | observeDOMEvents: function (events) { |
We don't operate on | if (! this.getField()) {
return;
}
if (!Object.isArray(events)) {
events = [events];
}
events.uniq().compact().each(
function (event) {
this.getField().observe(event, this._boundFireFunction);
}.bind(this)
);
}, |
We call the desired | fire: function () {
var args = $A(arguments);
args.unshift(this.getSubject());
this._controller[this._action].apply(this._controller, args);
}
}); |
Controller | |
The base controller. It doesn't do anything, except provide an inheritence chain. | svc.Controller = Class.create({
initialize: function (args) {
}
}); |
AjaxController | |
A wrapper around prototypes | svc.AjaxController = Class.create(svc.Controller, {
|
Our initializer takes an | initialize: function ($super, args) {
$super(args);
this._actionPath = args.actionPath;
this._actionMethod = args.actionMethod || 'post';
}, |
Make a request to the | makeRequest: function (args, callback) {
var boundOnSuccess = this.onSuccess.bind(this);
var wrappedSuccess = callback && typeof(callback) === 'function' ? boundOnSuccess.wrap(callback).bind(this) : boundOnSuccess;
var req = new Ajax.Request(this.path(), {
method: this.method(),
parameters: this.parameters().update(args),
onCreate: this.onCreate.bind(this),
onSuccess: wrappedSuccess,
onFailure: this.onFailure.bind(this),
onComplete: this.onComplete.bind(this)
});
}, |
Defines the | method: function () {
if (! this._actionMethod) { throw "AjaxController.js: method must be defined"; }
return this._actionMethod;
}, |
The method called when the AJAX request is completed. | onComplete: Prototype.emptyFunction,
|
The method called when the AJAX request is created. | onCreate: Prototype.emptyFunction,
|
The method called when the AJAX request fails. | onFailure: Prototype.emptyFunction,
|
The method called when the AJAX request succeeds. | onSuccess: Prototype.emptyFunction,
|
An object representing the parameters, this should be extended or overwritten. | parameters: function () { return $H(); }, |
Retrieves the | path: function () {
if (! this._actionPath) { throw "AjaxController.js: path must be defined"; }
return this._actionPath;
}
}); |
AjaxSingleRequestController | |
This creates a simple mutex lock on AJAX queries so that only one happens at a time. It inherits from
the normal | svc.AjaxSingleRequestController = Class.create(svc.AjaxController, { |
Our initializer is the same as | initialize: function ($super, args) {
$super(args);
this._inProgress = false;
},
|
Make a request if nothing is in progress. | makeRequest: function ($super, args, callback) {
if (this._inProgress) { return; }
this._inProgress = true;
$super(args, callback);
}, |
When the request finishes, unlock the progress lock | onComplete: function () { this._inProgress = false; }
});
})(null || (function(){ return this; })(), "1.1");
|