vendor/assets/javascripts/emerson/view.js in emerson-0.0.6 vs vendor/assets/javascripts/emerson/view.js in emerson-0.0.7
- old
+ new
@@ -77,16 +77,16 @@
var element = $(e);
var as_view = element.add(element.find(selectors.view)).filter(selectors.view);
var as_trait = element.add(element.find(selectors.traits)).filter(selectors.traits);
_.each(as_view, function(html) {
- var element = $(html);
+ var element = $(html); // why jQuery here, if $sub() in a moment?
attach.call(element, views, [element.data(attrs.view)]);
});
_.each(as_trait, function(html) {
- var element = $(html);
+ var element = $(html); // why jQuery here, if $sub() in a moment?
attach.call(element, traits, element.data(attrs.traits).split(/\s+/), true);
});
});
return this;
@@ -95,27 +95,74 @@
// Internal Objects
// --------------------------------------------------------------------------
// ### View constructor.
- //
// View instances are "subclasses" of the base lib object, decorated with our
// View.prototype and the provided definition.
function View() {}
- // View instance setup definition.
+ // ### View setup definition.
_.extend(View, {
setup : {
initialize : function() {},
+ connected : false,
subscribe : {}
}
});
- // View instance prototype definition.
- _.extend(View.prototype, {});
+ // ### View prototype definition.
+ // **TODO**: Add specs for #connect & #outlet.
+ //
+ // * `connect` specifies which, if any, `outlets` should be registered as
+ // methods on the instance.
+ //
+ // false - do not connect any outlets (default)
+ // true - connect all outlets
+ // [outlet(s)] - connect the named outlets.
+ //
+ // * `outlet` returns descendent element(s) with matching a `data-outlet`
+ // attribute. This may be used for a given outlet whether or not it has
+ // been "connected". Outlets are useful as a means of allowing for View
+ // and Trait definitions with somewhat flexible DOM. Rather than using
+ // specific selectors (or polluting the `class` attribute), the DOM for
+ // a given instance indicates which nodes should match for the View.
+ _.extend(View.prototype, {
+ connect : function(config) {
+ var self = this;
+ var outlets = this.find('[data-outlet]');
+ if(config === true) {
+ _.each(outlets, function(subject) {
+ var outlet = $(subject);
+ self[outlet.data('outlet')] = function() {
+ return outlet;
+ };
+ });
+ }
+ else if($.isArray(config)) {
+ _.each(config, function(key) {
+ var outlet = self.find('[data-outlet="' + key + '"]');
+
+ if(outlet.length) {
+ self[key] = function() {
+ return outlet;
+ };
+ }
+ });
+ }
+
+ return this;
+ },
+
+ outlet : function(key) {
+ return this.find('[data-outlet="' + key + '"]');
+ }
+ });
+
+
// Internal Implementation
// --------------------------------------------------------------------------
// Attr definitions.
// @private
@@ -193,11 +240,11 @@
function attach(library, keys, mode_p) {
var self = this, def;
var id = eid(this[0]);
_.each(_.flatten(keys), function(key) {
- var mode, match, built, init, events, set;
+ var mode, match, built, init, events, set, setup;
if(mode_p && (match = /^([^(]+)\((.+)\)/.exec(key))) {
key = match[1];
mode = match[2];
}
@@ -206,21 +253,21 @@
if(_.include(set, id)) {
return; // do not re-apply.
}
- // Build an instance, attach event handlers, initialize and record.
+ // Build an instance, connect outlets, bind events, init and record.
if(def = library[key]) {
- built = def(self, self.context);
- init = def.setup.initialize;
- events = def.setup.subscribe;
+ setup = def.setup;
+ built = def(self, self.context);
+ built.connect(setup.connected);
- _.each(events, function(handler, key) {
+ _.each(setup.subscribe, function(handler, key) {
bind(built, key, handler);
});
- init.call(built, mode);
+ setup.initialize.call(built, mode);
set.push(id);
}
});
return this;
@@ -229,53 +276,63 @@
// ### bind
// Attach event handler(s).
//
// Emerson.view(key, {
// subscribe : {
- // 'click' : handler, // simple
- // 'click focus' : handler, // multiple event types
- // 'selector' : { // specific child target
+ // 'click' : handler, // simple
+ // 'click focus' : handler, // multiple event types
+ // 'selector' : { // specific child target
// 'click' : handler,
// 'focus' : handler
// },
- // document : { // bind document, for events
- // 'click' : handler // fired outside of the view
- // 'selector' : { // TODO
+ // document : { // bind document, for events
+ // 'click' : handler // fired outside of the view
+ // 'selector' : {
// 'click' : handler
- // }
+ // }
+ // },
+ // 'outlet:name' : { // a custom key to specify a
+ // 'click' : handler // defined outlet as the scope
// }
// }
// });
//
// Emerson event handling differs from that of, say, stock jQuery in that
// `this` within the context of the handler will be a view instance. The
// event argument is unadultered, allowing access to the full set of targets
// as defined by the baselib (e.g., jQuery).
//
- // Note that, in the document-binding case, an event like `click` would be a
- // bad idea. A more useful (and less costly) use case would be a form of
- // pub/sub.
+ // **Special notes regarding document-bound handlers**:
//
- // For example, view "A" could trigger an event indicating that it has
- // rendered a new instance, to which "B" (elsewhere) would listen in order
- // to update, say, a count of instances of "A".
- function bind(instance, key, handler, selector) {
+ // Binding an event like `click` (without selector scope) to the document
+ // would likely be a bad idea. A more useful (and less costly) use case
+ // would be a sort of pub/sub. For example, view "A" could trigger an event
+ // indicating that it has rendered a new instance, to which "B" (elsewhere)
+ // would listen in order to update, say, a count of instances of "A".
+ //
+ // Additionally, event handlers bound to the document will not be cleaned
+ // up when the associated view instance is removed.
+ function bind(instance, key, handler, selector, binder) {
if($.isPlainObject(handler)) {
- _.each(handler, function(subhandler, subkey) {
- bind(instance, subkey, subhandler, key);
- });
- }
- else {
- if(selector === 'document') {
- $(document).on(key, function() {
- return handler.apply(instance, arguments);
+ if(key === 'document') {
+ _.each(handler, function(subhandler, subkey) {
+ bind(instance, subkey, subhandler, undefined, $(document));
});
}
else {
- instance.on(key, selector, function() {
- return handler.apply(instance, arguments);
+ _.each(handler, function(subhandler, subkey) {
+ bind(instance, subkey, subhandler, key, binder);
});
}
+ }
+ else {
+ if(/^outlet/.test(selector)) {
+ selector = '[data-outlet="' + selector.split(':')[1] + '"]';
+ }
+
+ (binder || instance).on(key, selector, function() { // selector may be undefined
+ return handler.apply(instance, arguments);
+ });
}
}
// ### $sub
// Basically a copy of jQuery.sub, but more generic and with changes to: