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: