From de634991d2fbb0f502406d4d738fcb63e2eeca75 Mon Sep 17 00:00:00 2001 From: Sam Saccone Date: Thu, 28 Nov 2013 21:59:39 -0500 Subject: [PATCH 1/2] add support for the ui hash within events and triggers This fixes the selector duplication that can occur within the triggers and event hash keys --- .../view.uiEventAndTriggers.spec.js | 90 +++++++++++++++++++ src/marionette.view.js | 24 ++++- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 spec/javascripts/view.uiEventAndTriggers.spec.js diff --git a/spec/javascripts/view.uiEventAndTriggers.spec.js b/spec/javascripts/view.uiEventAndTriggers.spec.js new file mode 100644 index 0000000000..1c2e4b0b7e --- /dev/null +++ b/spec/javascripts/view.uiEventAndTriggers.spec.js @@ -0,0 +1,90 @@ +describe("view ui event trigger configuration", function(){ + "use strict"; + + describe("@ui syntax within events and triggers", function() { + var view, view2, fooHandler, attackHandler; + + var View = Backbone.Marionette.ItemView.extend({ + ui: { + foo: '.foo', + bar: '#tap' + }, + + triggers: { + "click @ui.foo": "do:foo" + }, + + events: { + "click @ui.bar": "attack" + }, + + attack: function() { + attackHandler(); + }, + + render: function(){ + this.$el.html("
"); + } + }); + + var View2 = View.extend({ + triggers: function() { + return { + "click @ui.foo": { + event: "do:foo", + preventDefault: true, + stopPropagation: false + } + } + }, + + events: function() { + return { + "click @ui.bar": function() { + return "attack" + } + } + } + }); + + beforeEach(function(){ + view = new View({ + model: new Backbone.Model() + }); + + view2 = new View({ + model: new Backbone.Model() + }); + + view.render(); + view2.render(); + + fooHandler = jasmine.createSpy("do:foo event handler"); + attackHandler = jasmine.createSpy("attack handler"); + + spyOn(view, "attack").andCallThrough(); + view.on("do:foo", fooHandler); + view2.on("do:foo", fooHandler); + }); + + it("should correctly trigger an event", function(){ + view.$(".foo").trigger("click"); + expect(fooHandler).toHaveBeenCalled(); + }); + + it("should correctly call an event", function(){ + view.$("#tap").trigger('click'); + expect(attackHandler).toHaveBeenCalled(); + }); + + it("should correctly call an event with a functional events hash", function(){ + view2.$("#tap").trigger('click'); + expect(attackHandler).toHaveBeenCalled(); + }); + + it("should correctly call an event with a functional triggers hash", function(){ + view2.$(".foo").trigger("click"); + expect(fooHandler).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/marionette.view.js b/src/marionette.view.js index 4a4ce543ed..99836b0fa0 100644 --- a/src/marionette.view.js +++ b/src/marionette.view.js @@ -14,6 +14,9 @@ Marionette.View = Backbone.View.extend({ // of this.options // at some point however this may be removed this.options = options || {}; + + // parses out the @ui DSL for events + this.events = this.normalizeUIKeys(_.result(this, 'events')); Backbone.View.prototype.constructor.apply(this, args); Marionette.MonitorDOMRefresh(this); @@ -46,6 +49,25 @@ Marionette.View = Backbone.View.extend({ return _.extend(target, templateHelpers); }, + // allows for the use of the @ui. syntax within + // a given key for triggers and events + // swaps the @ui with the associated selector + normalizeUIKeys: function(hash) { + if (typeof(hash) === "undefined") { + return; + } + + _.each(_.keys(hash), function(v) { + var split = v.split("@ui."); + if (split.length === 2) { + hash[split[0]+this.ui[split[1]]] = hash[v]; + delete hash[v]; + } + }, this); + + return hash; + }, + // Configure `triggers` to forward DOM events to view // events. `triggers: {"click .foo": "do:foo"}` configureTriggers: function(){ @@ -54,7 +76,7 @@ Marionette.View = Backbone.View.extend({ var triggerEvents = {}; // Allow `triggers` to be configured as a function - var triggers = _.result(this, "triggers"); + var triggers = this.normalizeUIKeys(_.result(this, "triggers")); // Configure the triggers, prevent default // action and stop propagation of DOM events From 426209f43940922a48af631629306271c13d68b4 Mon Sep 17 00:00:00 2001 From: Sam Saccone Date: Tue, 3 Dec 2013 21:46:25 -0500 Subject: [PATCH 2/2] update docs to reflect the new @ui. syntax --- docs/marionette.itemview.md | 2 ++ docs/marionette.view.md | 59 +++++++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/docs/marionette.itemview.md b/docs/marionette.itemview.md index 7d855364b3..ee6cc7a547 100644 --- a/docs/marionette.itemview.md +++ b/docs/marionette.itemview.md @@ -249,6 +249,8 @@ same UI element more than once in your view's code. Instead of duplicating the selector, you can simply reference it by `this.ui.elementName`: +You can also use the ui hash values from within events and trigger keys using the ```"@ui.elementName"```: syntax + ```js Backbone.Marionette.ItemView.extend({ tagName: "tr", diff --git a/docs/marionette.view.md b/docs/marionette.view.md index d2a2d6f149..ab36703c02 100644 --- a/docs/marionette.view.md +++ b/docs/marionette.view.md @@ -4,7 +4,7 @@ Marionette has a base `Marionette.View` type that other views extend from. This base view provides some common and core functionality for other views to take advantage of. -**Note:** The `Marionette.View` type is not intended to be +**Note:** The `Marionette.View` type is not intended to be used directly. It exists as a base view for other view types to be extended from, and to provide a common location for behaviors that are shared across all views. @@ -16,6 +16,7 @@ behaviors that are shared across all views. * [View onBeforeClose](#view-onbeforeclose) * [View "dom:refresh" / onDomRefresh event](#view-domrefresh--ondomrefresh-event) * [View.triggers](#viewtriggers) +* [View.events](#viewevents) * [View.modelEvents and View.collectionEvents](#viewmodelevents-and-viewcollectionevents) * [View.serializeData](#viewserializedata) * [View.bindUIElements](#viewbinduielements) @@ -109,8 +110,8 @@ v.close(); // view will remain open Triggered after the view has been rendered, has been shown in the DOM via a Marionette.Region, and has been re-rendered. -This event / callback is useful for -[DOM-dependent UI plugins](http://lostechies.com/derickbailey/2012/02/20/using-jquery-plugins-and-ui-controls-with-backbone/) such as +This event / callback is useful for +[DOM-dependent UI plugins](http://lostechies.com/derickbailey/2012/02/20/using-jquery-plugins-and-ui-controls-with-backbone/) such as [jQueryUI](http://jqueryui.com/) or [KendoUI](http://kendoui.com). ```js @@ -126,9 +127,28 @@ Backbone.Marionette.ItemView.extend({ For more information about integration Marionette w/ KendoUI (also applicable to jQueryUI and other UI widget suites), see [this blog post on KendoUI + Backbone](http://www.kendoui.com/blogs/teamblog/posts/12-11-26/backbone_and_kendo_ui_a_beautiful_combination.aspx). +## View.events +Since Views extend from backbone`s view class, you gain the benifit of the [events hash](http://backbonejs.org/#View-delegateEvents). + +Some preprocessing sugar is added on top to add the ability to cross utilize the ```ui``` hash. + +```js +MyView = Backbone.Marionette.ItemView.extend({ + // ... + + ui: { + "cat": ".dog" + }, + + events: { + "click @ui.cat": "bark" //is the same as "click .dog": + } +}); +``` + ## View.triggers -Views can define a set of `triggers` as a hash, which will +Views can define a set of `triggers` as a hash, which will convert a DOM event into a `view.triggerMethod` call. The left side of the hash is a standard Backbone.View DOM @@ -151,7 +171,7 @@ view.on("something:do:it", function(args){ alert("I DID IT!"); }); -// "click" the 'do-something' DOM element to +// "click" the 'do-something' DOM element to // demonstrate the DOM event conversion view.$(".do-something").trigger("click"); ``` @@ -171,7 +191,7 @@ Backbone.Marionette.CompositeView.extend({ }); ``` -You can also specify the `triggers` as a function that +You can also specify the `triggers` as a function that returns a hash of trigger configurations ```js @@ -184,6 +204,19 @@ Backbone.Marionette.CompositeView.extend({ }); ``` +Trigger keys can be configured to cross utilize the ```ui``` hash. + +```js +Backbone.Marionette.ItemView.extend({ + ui: { + 'monkey': '.guybrush' + }, + triggers: { + 'click @ui.monkey': 'see:LeChuck' // equivalent of "click .guybrush" + } +}); +``` + Triggers work with all View types that extend from the base Marionette.View. @@ -259,7 +292,7 @@ the model and collection events re-bound. ### Multiple Callbacks Multiple callback functions can be specified by separating them with a -space. +space. ```js Backbone.Marionette.CompositeView.extend({ @@ -346,9 +379,9 @@ do not provide a helper method mechanism while Handlebars templates do. A `templateHelpers` attribute can be applied to any View object that -renders a template. When this attribute is present its contents -will be mixed in to the data object that comes back from the -`serializeData` method. This will allow you to create helper methods +renders a template. When this attribute is present its contents +will be mixed in to the data object that comes back from the +`serializeData` method. This will allow you to create helper methods that can be called from within your templates. ### Basic Example @@ -412,10 +445,10 @@ templateHelpers: { ### Object Or Function As `templateHelpers` You can specify an object literal (as shown above), a reference -to an object literal, or a function as the `templateHelpers`. +to an object literal, or a function as the `templateHelpers`. -If you specify a function, the function will be invoked -with the current view instance as the context of the +If you specify a function, the function will be invoked +with the current view instance as the context of the function. The function must return an object that can be mixed in to the data for the view.