├── .gitignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── dist ├── backbone.intercept.js ├── backbone.intercept.min.js └── backbone.intercept.min.js.map ├── gruntfile.js ├── package.json ├── src ├── backbone.intercept.js └── wrapper.js └── test ├── .jshintrc ├── setup ├── helpers.js └── node.js ├── spec-runner.html └── unit ├── clicks.js ├── forms.js ├── navigate.js ├── root-element.js ├── start.js └── stop.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | bower_components 31 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "curly" : true, 5 | "eqeqeq" : true, 6 | "es3" : true, 7 | "forin" : false, 8 | "immed" : true, 9 | "indent" : 2, 10 | "latedef" : true, 11 | "newcap" : true, 12 | "noarg" : true, 13 | "noempty" : true, 14 | "nonbsp" : true, 15 | "nonew" : true, 16 | "plusplus" : false, 17 | "quotmark" : "single", 18 | "undef" : true, 19 | "unused" : true, 20 | "strict" : false, 21 | "trailing" : true, 22 | "maxparams" : 4, 23 | "maxdepth" : 2, 24 | 25 | "asi" : false, 26 | "boss" : false, 27 | "debug" : false, 28 | "eqnull" : false, 29 | "esnext" : false, 30 | "evil" : false, 31 | "expr" : true, 32 | "funcscope" : false, 33 | "globalstrict" : false, 34 | "iterator" : false, 35 | "lastsemic" : false, 36 | "laxbreak" : false, 37 | "laxcomma" : false, 38 | "loopfunc" : false, 39 | "maxerr" : 50, 40 | "multistr" : false, 41 | "notypeof" : false, 42 | "proto" : false, 43 | "scripturl" : false, 44 | "smarttabs" : false, 45 | "shadow" : false, 46 | "sub" : false, 47 | "supernew" : false, 48 | "validthis" : false, 49 | "noyield" : false, 50 | 51 | "browser" : true, 52 | "couch" : false, 53 | "devel" : false, 54 | "dojo" : false, 55 | "jquery" : false, 56 | "mootools" : false, 57 | "node" : false, 58 | "nonstandard" : false, 59 | "prototypejs" : false, 60 | "rhino" : false, 61 | "worker" : false, 62 | "wsh" : false, 63 | "yui" : false, 64 | "globals": { 65 | "Backbone": true, 66 | "_": true, 67 | "$": true, 68 | "require": true, 69 | "module": true, 70 | "define": true, 71 | "exports": true 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | before_script: 3 | - npm install -g grunt-cli 4 | node_js: 5 | - "0.10" 6 | - "0.12" 7 | sudo: false 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### [0.4.4](https://github.com/jmeas/backbone.intercept/releases/tag/0.4.4) 2 | 3 | - Adds control + click bypass 4 | 5 | ### [0.4.3](https://github.com/jmeas/backbone.intercept/releases/tag/0.4.3) 6 | 7 | - Updated dependencies to support the latest Backbone. 8 | 9 | ### [0.4.2](https://github.com/jmeas/backbone.intercept/releases/tag/0.4.2) 10 | 11 | - Updated Bower to support the latest Backbone, too! 12 | 13 | ### [0.4.1](https://github.com/jmeas/backbone.intercept/releases/tag/0.4.1) 14 | 15 | - Updated dependencies to support the latest Backbone. 16 | 17 | ### [0.4.0](https://github.com/jmeas/backbone.intercept/releases/tag/0.4.0) 18 | 19 | - Updated dependencies to support the latest Backbone and Underscore. 20 | 21 | ### [0.3.3](https://github.com/jmeas/backbone.intercept/releases/tag/v0.3.3) 22 | 23 | - **Bugfix:** Query strings are no longer stripped from intercepted links. 24 | 25 | ### [0.3.2](https://github.com/jmeas/backbone.intercept/releases/tag/v0.3.2) 26 | 27 | - **Bugfix:** Resolves an issue where relative links would be sent to the Router as absolute paths 28 | 29 | ### [0.3.1](https://github.com/jmeas/backbone.intercept/releases/tag/v0.3.1) 30 | 31 | - Corrects Underscore dependency (this library supports every version) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 James Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > I'm looking for maintainer(s) of this lib. If you're interested, [let me know](https://twitter.com/jmeaspls)! I'd be happy to help you get set up :v: 2 | 3 | # Backbone.Intercept 4 | 5 | [![Travis Build Status](http://img.shields.io/travis/jmeas/backbone.intercept.svg?style=flat)](https://travis-ci.org/jmeas/backbone.intercept) 6 | 7 | Backbone.Intercept intelligently manages link clicks and form submissions within Backbone applications. 8 | 9 | ### About 10 | 11 | The default action of form submissions and link clicks is often undesirable in Backbone applications. One 12 | would usually rather prevent that default behavior, then handle those through Backbone.history and possibly also 13 | in a Router's callback. Backbone doesn't do any of this for you, but Backbone.Intercept does. 14 | 15 | If you're writing `e.preventDefault()` in many of your view's event callbacks – or otherwise handling this problem on a per-view 16 | basis – then Backbone.Intercept might be what you're looking for. 17 | 18 | ```js 19 | // before Backbone.Intercept 20 | var MyView = Backbone.View.extend({ 21 | events: { 22 | 'click a': 'onClick', 23 | 'submit form': 'onSubmit' 24 | }, 25 | 26 | onClick: function(e) { 27 | e.preventDefault(); 28 | myRouter.navigate($(e.currentTarget).attr('href')); 29 | }, 30 | 31 | onSubmit: function(e) { 32 | e.preventDefault(); 33 | // form submit logic 34 | } 35 | }); 36 | 37 | // after Backbone.Intercept 38 | var MyView = Backbone.View.extend({ 39 | events: { 40 | 'submit form': 'onSubmit' 41 | }, 42 | 43 | onSubmit: function(e) { 44 | // form submit logic 45 | } 46 | }); 47 | ``` 48 | 49 | ## Installation 50 | 51 | Install through `bower` or `npm`. 52 | 53 | ```js 54 | bower install backbone.intercept 55 | npm install backbone.intercept 56 | ``` 57 | 58 | #### Dependencies 59 | 60 | Backbone.Intercept depends on Underscore, Backbone and a jQuery-like API on the `Backbone.$` object. 61 | 62 | ### Table of Contents 63 | 64 | - [Getting Started](#getting-started) 65 | - [Links](#links) 66 | - [Default Behavior](#default-behavior) 67 | - [Navigation](#navigation) 68 | - [Customizing the Behavior Per-Link](#customizing-the-behavior-per-link) 69 | - [Setting Global Link Trigger Behavior](#setting-global-link-trigger-behavior) 70 | - [Forms](#forms) 71 | - [Setting the Root Element of Backbone.Intercept](#setting-the-root-element-of-backboneintercept) 72 | - [When Not to Use Backbone.Intercept](#when-not-to-use-backboneintercept) 73 | - [API](#api) 74 | - [Properties](#properties) 75 | - [`VERSION`](#version) 76 | - [`rootSelector`](#rootselector) 77 | - [`defaults`](#defaults) 78 | - [Methods](#methods) 79 | - [`start()`](#start-options-) 80 | - [`stop()`](#stop) 81 | - [`navigate()`](#navigate-uri-options-) 82 | 83 | ## Getting Started 84 | 85 | Getting started is easy. Simply call `Backbone.Intercept.start()` when your application is started up. If 86 | you're using Marionette, this might look something like 87 | 88 | ```js 89 | app.on('start', Backbone.Intercept.start); 90 | ``` 91 | 92 | ### Links 93 | 94 | #### Default Behavior 95 | 96 | In general, links with relative URIs will be intercepted, whereas absolute URIs will be ignored by Backbone.Intercept. 97 | A few examples will best illustrate the default behavior of Intercept. 98 | 99 | ```js 100 | // The following URIs will be intercepted 101 | 'path/to/my-page'; 102 | '/absolute/path/to/my-page'; 103 | 'www.my-website.com'; 104 | 105 | // The following URIs will be ignored by Backbone.Intercept and handled like normal 106 | 'http://www.my-site.com'; 107 | '#my-page-fragment'; 108 | 'mailto:stacy@email.com'; 109 | 'javascript:void'; 110 | ``` 111 | 112 | #### Navigation 113 | 114 | By default your intercepted links will be sent along to `Backbone.history.navigate` to be processed. You can customize this 115 | by overriding the `navigate` method on Backbone.Intercept. By doing this you could make Intercept work with a Router instead, 116 | or integrate other libraries like [Backbone.Radio](https://github.com/marionettejs/backbone.radio). 117 | 118 | ```js 119 | // Create a Router 120 | var myRouter = new Backbone.Router(); 121 | 122 | // Attach it to Intercept 123 | Backbone.Intercept.navigate = function(uri, options) { 124 | myRouter.navigate(uri, options); 125 | }; 126 | 127 | // Or use a Backbone.Radio Channel 128 | Backbone.Intercept.navigate = function(uri, options) { 129 | var routerChannel = Backbone.Radio.channel('router'); 130 | routerChannel.command('navigate', uri, options); 131 | }; 132 | ``` 133 | 134 | If you don't want anything to happen when you click links you can specify the `navigate` function as a falsey value, 135 | or an empty function. 136 | 137 | ```js 138 | // This won't cause any navigation to occur when links are clicked 139 | Backbone.Intercept.navigate = undefined; 140 | ``` 141 | 142 | #### Customizing the Behavior Per-Link 143 | 144 | This behavior can be changed by setting custom attributes on the element. 145 | 146 | ```html 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | ``` 162 | 163 | #### Setting Global Link Trigger Behavior 164 | 165 | You can set the default trigger behavior by specifying it directly on the Backbone.Intercept `defaults` option. 166 | 167 | ```js 168 | // Let's set the trigger setting to false by default 169 | Backbone.Intercept.defaults.trigger = false; 170 | ``` 171 | 172 | ### Forms 173 | 174 | Forms are much simpler than links. All forms are intercepted unless the `action` attribute has been specified. And unlike links, there's 175 | no integration of forms with a Router. 176 | 177 | ```html 178 | 179 |
180 | 181 | 182 |
183 | ``` 184 | 185 | ### Setting the Root Element of Backbone.Intercept 186 | 187 | Backbone.Intercept will intercept links and forms on the `body` on the page, but this can be customized by setting the 188 | `rootSelector` property. 189 | 190 | ```js 191 | Backbone.Intercept.rootSelector = '.backbone-app'; 192 | ``` 193 | 194 | This is useful for webapps where Backbone might not be the only library running on the page. 195 | 196 | ### When Not To Use Backbone.Intercept 197 | 198 | Backbone.Intercept works best in an application that is entirely controlled by Backbone. Of course, not 199 | every project is like this. It's not uncommon for there to be Backbone components on a page that is otherwise 200 | not Backbone. In those situations it is likely a better choice to manage link clicks and form 201 | submissions on a per-view basis. 202 | 203 | This library is only meant to act as a proxy between the DOM and your JS, and nothing more, if you want to be able to cancel routes, this library won't help you with that, you'd need to handle that in your router. 204 | 205 | ## API 206 | 207 | ### Properties 208 | 209 | ##### `VERSION` 210 | 211 | The version of Backbone.Intercept. 212 | 213 | ##### `rootSelector` 214 | 215 | A query selector for the root element of Intercept. Defaults to `'body'`. 216 | 217 | ##### `defaults` 218 | 219 | An object for the default values for the router. There are three properties in defaults, `links`, `forms`, and `trigger`, 220 | and all three are `true` out-of-the-box. The first two options determine if Intercept handles links and forms, respectively. The 221 | `trigger` option determines if intercepted links pass `trigger:true` by default. 222 | 223 | The value of the `trigger` or `data-trigger` attribute on the anchor tag itself will always trump the value of the 224 | value of `trigger` in the `defaults` hash. 225 | 226 | ### Methods 227 | 228 | ##### `start( [options] )` 229 | 230 | Starts Backbone.Intercept. You can pass `options` as an argument to override the `defaults` property. 231 | 232 | ```js 233 | // In this app we will only intercept forms 234 | Backbone.Intercept.start({ 235 | links: false 236 | }); 237 | 238 | // And in this one only links 239 | Backbone.Intercept.start({ 240 | forms: false 241 | }); 242 | ``` 243 | 244 | ##### `stop()` 245 | 246 | Stop intercepting links and forms. 247 | 248 | ##### `navigate( uri, options )` 249 | default: `Backbone.history.navigate` 250 | 251 | A method that's called when links are intercepted. By default it just forwards it along to `Backbone.history.navigate`, but you 252 | can specify a custom method to do whatever you'd like. 253 | 254 | The uri is the value of the link's `href` attribute. `options` are the navigation options, which is just an object 255 | with a `trigger` property. 256 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone.intercept", 3 | "main": "dist/backbone.intercept.js", 4 | "version": "0.4.3", 5 | "homepage": "https://github.com/jmeas/backbone.intercept", 6 | "authors": [ 7 | "Jmeas " 8 | ], 9 | "description": "Automatically manage link clicks and form submissions within Backbone applications.", 10 | "keywords": [ 11 | "backbone", 12 | "marionette", 13 | "link", 14 | "links", 15 | "click", 16 | "router", 17 | "route", 18 | "routes", 19 | "history", 20 | "anchor", 21 | "intercept", 22 | "forms", 23 | "submit", 24 | "interception", 25 | "SPA" 26 | ], 27 | "license": "MIT", 28 | "ignore": [ 29 | "**/.*", 30 | "node_modules", 31 | "bower_components", 32 | "test", 33 | "tests" 34 | ], 35 | "dependencies": { 36 | "underscore": ">=1.3.3 <=1.8.3", 37 | "backbone": ">=0.9.9 <=1.3.3" 38 | }, 39 | "devDependencies": { 40 | "jquery": ">=1.8 <=3.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /dist/backbone.intercept.js: -------------------------------------------------------------------------------- 1 | // Backbone.Intercept v0.4.4 2 | (function(root, factory) { 3 | if (typeof define === 'function' && define.amd) { 4 | define(['backbone', 'underscore'], function(Backbone, _) { 5 | return factory(Backbone, _); 6 | }); 7 | } 8 | else if (typeof exports !== 'undefined') { 9 | var Backbone = require('backbone'); 10 | var _ = require('underscore'); 11 | module.exports = factory(Backbone, _); 12 | } 13 | else { 14 | factory(root.Backbone, root._); 15 | } 16 | }(this, function(Backbone, _) { 17 | 'use strict'; 18 | 19 | Backbone.Intercept = { 20 | 21 | VERSION: '0.4.4', 22 | 23 | rootSelector: 'body', 24 | 25 | defaults: { 26 | trigger : true, 27 | links : true, 28 | forms : true 29 | }, 30 | 31 | start: function(options) { 32 | options = _.defaults(options || {}, this.defaults); 33 | 34 | if (options.links) { 35 | this._getRootElement().on('click.backboneIntercept', 'a', _.bind(this._interceptLinks, this)); 36 | } 37 | if (options.forms) { 38 | this._getRootElement().on('submit.backboneIntercept', _.bind(this._interceptForms, this)); 39 | } 40 | }, 41 | 42 | stop: function() { 43 | this._getRootElement().off('.backboneIntercept'); 44 | }, 45 | 46 | navigate: function(uri, options) { 47 | Backbone.history.navigate(uri, options); 48 | }, 49 | 50 | // Creates and caches a jQuery object for the body element 51 | _getRootElement: function() { 52 | if (this._body) { return this._body; } 53 | this._body = Backbone.$(this.rootSelector); 54 | return this._body; 55 | }, 56 | 57 | // Prevent the default behavior of submitting the 58 | // form if the action attribute is present and is 59 | // a value 60 | _interceptForms: function(e) { 61 | if (e.target && e.target.action) { return; } 62 | e.preventDefault(); 63 | }, 64 | 65 | _interceptLinks: function(e) { 66 | // Only intercept left-clicks 67 | if (e.which !== 1) { return; } 68 | var $link = Backbone.$(e.currentTarget); 69 | 70 | // Get the href; stop processing if there isn't one 71 | var href = $link.attr('href'); 72 | if (!href) { return; } 73 | 74 | // Determine if we're supposed to bypass the link 75 | // based on its attributes 76 | var bypass = this._getAttr($link, 'bypass'); 77 | if (bypass !== undefined && bypass !== 'false') { 78 | return; 79 | } 80 | 81 | // If the Ctrl key is held, we want to open in a new tab. 82 | bypass = e.ctrlKey; 83 | if (bypass) { 84 | return; 85 | } 86 | 87 | // The options we pass along to navigate 88 | var navOptions = { 89 | trigger: this.defaults.trigger 90 | }; 91 | 92 | // Determine if it's trigger: false based on the attributes 93 | var trigger = this._getAttr($link, 'trigger'); 94 | 95 | if (trigger === 'false') { 96 | navOptions.trigger = false; 97 | } else if (trigger === 'true') { 98 | navOptions.trigger = true; 99 | } 100 | 101 | // Return if the URL is absolute, or if the protocol is mailto or javascript 102 | if (/^#|javascript:|mailto:|(?:\w+:)?\/\//.test(href)) { return; } 103 | 104 | // If we haven't been stopped yet, then we prevent the default action 105 | e.preventDefault(); 106 | 107 | // Get the computed pathname of the link, removing 108 | // the leading slash. Regex required for IE8 support 109 | var pathname = $link[0].pathname.replace(/^\//, '') + $link[0].search; 110 | 111 | // Lastly we send off the information to the router 112 | if (!this.navigate) { return; } 113 | this.navigate(pathname, navOptions); 114 | }, 115 | 116 | _getAttr: function($el, name) { 117 | var attr = $el.attr(name); 118 | if (attr !== undefined) { 119 | return attr; 120 | } 121 | var data = $el.attr('data-' + name); 122 | if (data !== undefined) { 123 | return data; 124 | } 125 | } 126 | }; 127 | 128 | 129 | return Backbone.Intercept; 130 | })); 131 | -------------------------------------------------------------------------------- /dist/backbone.intercept.min.js: -------------------------------------------------------------------------------- 1 | // Backbone.Intercept v0.4.4 2 | 3 | !function(a,b){if("function"==typeof define&&define.amd)define(["backbone","underscore"],function(a,c){return b(a,c)});else if("undefined"!=typeof exports){var c=require("backbone"),d=require("underscore");module.exports=b(c,d)}else b(a.Backbone,a._)}(this,function(a,b){"use strict";return a.Intercept={VERSION:"0.4.4",rootSelector:"body",defaults:{trigger:!0,links:!0,forms:!0},start:function(a){a=b.defaults(a||{},this.defaults),a.links&&this._getRootElement().on("click.backboneIntercept","a",b.bind(this._interceptLinks,this)),a.forms&&this._getRootElement().on("submit.backboneIntercept",b.bind(this._interceptForms,this))},stop:function(){this._getRootElement().off(".backboneIntercept")},navigate:function(b,c){a.history.navigate(b,c)},_getRootElement:function(){return this._body?this._body:(this._body=a.$(this.rootSelector),this._body)},_interceptForms:function(a){a.target&&a.target.action||a.preventDefault()},_interceptLinks:function(b){if(1===b.which){var c=a.$(b.currentTarget),d=c.attr("href");if(d){var e=this._getAttr(c,"bypass");if((void 0===e||"false"===e)&&!(e=b.ctrlKey)){var f={trigger:this.defaults.trigger},g=this._getAttr(c,"trigger");if("false"===g?f.trigger=!1:"true"===g&&(f.trigger=!0),!/^#|javascript:|mailto:|(?:\w+:)?\/\//.test(d)){b.preventDefault();var h=c[0].pathname.replace(/^\//,"")+c[0].search;this.navigate&&this.navigate(h,f)}}}}},_getAttr:function(a,b){var c=a.attr(b);if(void 0!==c)return c;var d=a.attr("data-"+b);return void 0!==d?d:void 0}},a.Intercept}); 4 | //# sourceMappingURL=backbone.intercept.min.js.map -------------------------------------------------------------------------------- /dist/backbone.intercept.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["backbone.intercept.js"],"names":["root","factory","define","amd","Backbone","_","exports","require","module","this","Intercept","VERSION","rootSelector","defaults","trigger","links","forms","start","options","_getRootElement","on","bind","_interceptLinks","_interceptForms","stop","off","navigate","uri","history","_body","$","e","target","action","preventDefault","which","$link","currentTarget","href","attr","bypass","_getAttr","undefined","ctrlKey","navOptions","test","pathname","replace","search","$el","name","data"],"mappings":";;CACC,SAASA,EAAMC,GACd,GAAsB,kBAAXC,SAAyBA,OAAOC,IACzCD,QAAQ,WAAY,cAAe,SAASE,EAAUC,GACpD,MAAOJ,GAAQG,EAAUC,SAGxB,IAAuB,mBAAZC,SAAyB,CACvC,GAAIF,GAAWG,QAAQ,YACnBF,EAAIE,QAAQ,aAChBC,QAAOF,QAAUL,EAAQG,EAAUC,OAGnCJ,GAAQD,EAAKI,SAAUJ,EAAKK,IAE9BI,KAAM,SAASL,EAAUC,GACzB,YAgHA,OA9GAD,GAASM,WAEPC,QAAS,QAETC,aAAc,OAEdC,UACEC,SAAU,EACVC,OAAU,EACVC,OAAU,GAGZC,MAAO,SAASC,GACdA,EAAUb,EAAEQ,SAASK,MAAeT,KAAKI,UAErCK,EAAQH,OACVN,KAAKU,kBAAkBC,GAAG,0BAA2B,IAAKf,EAAEgB,KAAKZ,KAAKa,gBAAiBb,OAErFS,EAAQF,OACVP,KAAKU,kBAAkBC,GAAG,2BAA4Bf,EAAEgB,KAAKZ,KAAKc,gBAAiBd,QAIvFe,KAAM,WACJf,KAAKU,kBAAkBM,IAAI,uBAG7BC,SAAU,SAASC,EAAKT,GACtBd,EAASwB,QAAQF,SAASC,EAAKT,IAIjCC,gBAAiB,WACf,MAAIV,MAAKoB,MAAgBpB,KAAKoB,OAC9BpB,KAAKoB,MAAQzB,EAAS0B,EAAErB,KAAKG,cACtBH,KAAKoB,QAMdN,gBAAiB,SAASQ,GACpBA,EAAEC,QAAUD,EAAEC,OAAOC,QACzBF,EAAEG,kBAGJZ,gBAAiB,SAASS,GAExB,GAAgB,IAAZA,EAAEI,MAAN,CACA,GAAIC,GAAQhC,EAAS0B,EAAEC,EAAEM,eAGrBC,EAAOF,EAAMG,KAAK,OACtB,IAAKD,EAAL,CAIA,GAAIE,GAAS/B,KAAKgC,SAASL,EAAO,SAClC,SAAeM,KAAXF,GAAmC,UAAXA,MAK5BA,EAAST,EAAEY,SACX,CAKA,GAAIC,IACF9B,QAASL,KAAKI,SAASC,SAIrBA,EAAUL,KAAKgC,SAASL,EAAO,UASnC,IAPgB,UAAZtB,EACF8B,EAAW9B,SAAU,EACA,SAAZA,IACT8B,EAAW9B,SAAU,IAInB,uCAAuC+B,KAAKP,GAAhD,CAGAP,EAAEG,gBAIF,IAAIY,GAAWV,EAAM,GAAGU,SAASC,QAAQ,MAAO,IAAMX,EAAM,GAAGY,MAG1DvC,MAAKiB,UACVjB,KAAKiB,SAASoB,EAAUF,QAG1BH,SAAU,SAASQ,EAAKC,GACtB,GAAIX,GAAOU,EAAIV,KAAKW,EACpB,QAAaR,KAATH,EACF,MAAOA,EAET,IAAIY,GAAOF,EAAIV,KAAK,QAAUW,EAC9B,YAAaR,KAATS,EACKA,MADT,KAOG/C,EAASM","file":"backbone.intercept.min.js"} -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | require('load-grunt-tasks')(grunt); 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | pkg: grunt.file.readJSON('package.json'), 8 | meta: { 9 | version: '<%= pkg.version %>', 10 | banner: '// Backbone.Intercept v<%= meta.version %>\n' 11 | }, 12 | 13 | preprocess: { 14 | intercept: { 15 | src: 'src/wrapper.js', 16 | dest: 'dist/backbone.intercept.js' 17 | } 18 | }, 19 | 20 | template: { 21 | options: { 22 | data: { 23 | version: '<%= meta.version %>' 24 | } 25 | }, 26 | intercept: { 27 | src: '<%= preprocess.intercept.dest %>', 28 | dest: '<%= preprocess.intercept.dest %>' 29 | } 30 | }, 31 | 32 | concat: { 33 | options: { 34 | banner: '<%= meta.banner %>' 35 | }, 36 | intercept: { 37 | src: '<%= preprocess.intercept.dest %>', 38 | dest: '<%= preprocess.intercept.dest %>' 39 | } 40 | }, 41 | 42 | uglify: { 43 | options: { 44 | banner: '<%= meta.banner %>' 45 | }, 46 | intercept: { 47 | src: '<%= preprocess.intercept.dest %>', 48 | dest: 'dist/backbone.intercept.min.js', 49 | options: { 50 | sourceMap: true 51 | } 52 | } 53 | }, 54 | 55 | jshint: { 56 | intercept: { 57 | options: { 58 | jshintrc: '.jshintrc' 59 | }, 60 | src: ['src/backbone.intercept.js'] 61 | }, 62 | tests: { 63 | options: { 64 | jshintrc: 'test/.jshintrc' 65 | }, 66 | src: ['test/unit/*.js'] 67 | } 68 | }, 69 | 70 | mochaTest: { 71 | spec: { 72 | options: { 73 | require: 'test/setup/node.js', 74 | reporter: 'dot', 75 | clearRequireCache: true, 76 | mocha: require('mocha') 77 | }, 78 | src: [ 79 | 'test/setup/helpers.js', 80 | 'test/unit/*.js' 81 | ] 82 | } 83 | }, 84 | 85 | connect: { 86 | options: { 87 | port: 8000, 88 | keepalive: true 89 | }, 90 | browser: {} 91 | } 92 | }); 93 | 94 | grunt.registerTask('test:browser', 'Test the library in the browser', [ 95 | 'jshint', 96 | 'connect' 97 | ]); 98 | 99 | grunt.registerTask('test', 'Test the library', [ 100 | 'jshint', 101 | 'mochaTest' 102 | ]); 103 | 104 | grunt.registerTask('build', 'Build the library', [ 105 | 'test', 106 | 'preprocess:intercept', 107 | 'template', 108 | 'concat', 109 | 'uglify' 110 | ]); 111 | 112 | grunt.registerTask('default', 'An alias of test', [ 113 | 'test' 114 | ]); 115 | }; 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone.intercept", 3 | "version": "0.4.4", 4 | "description": "Automatically manage link clicks and form submissions within Backbone applications.", 5 | "main": "dist/backbone.intercept.js", 6 | "scripts": { 7 | "test": "grunt" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jmeas/backbone.intercept.git" 12 | }, 13 | "keywords": [ 14 | "backbone", 15 | "marionette", 16 | "link", 17 | "links", 18 | "click", 19 | "router", 20 | "route", 21 | "routes", 22 | "history", 23 | "anchor", 24 | "intercept", 25 | "forms", 26 | "submit", 27 | "interception", 28 | "SPA" 29 | ], 30 | "author": "Jmeas", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/jmeas/backbone.intercept/issues" 34 | }, 35 | "homepage": "https://github.com/jmeas/backbone.intercept", 36 | "dependencies": { 37 | "backbone": ">=0.9.9 <=1.3.3", 38 | "underscore": ">=1.3.3 <=1.8.3" 39 | }, 40 | "devDependencies": { 41 | "chai": "^1.9.1", 42 | "grunt": "^0.4.5", 43 | "grunt-contrib-concat": "^0.5.0", 44 | "grunt-contrib-connect": "^0.9.0", 45 | "grunt-contrib-jshint": "^0.10.0", 46 | "grunt-contrib-uglify": "^0.5.1", 47 | "grunt-mocha-test": "^0.12.0", 48 | "grunt-preprocess": "^4.0.0", 49 | "grunt-template": "^0.2.3", 50 | "jquery": ">=1.8 <=3.0", 51 | "jsdom": "^3.0.0", 52 | "load-grunt-tasks": "^0.6.0", 53 | "mocha": "^1.21.4", 54 | "sinon": "^1.10.3", 55 | "sinon-chai": "^2.5.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/backbone.intercept.js: -------------------------------------------------------------------------------- 1 | Backbone.Intercept = { 2 | 3 | VERSION: '<%= version %>', 4 | 5 | rootSelector: 'body', 6 | 7 | defaults: { 8 | trigger : true, 9 | links : true, 10 | forms : true 11 | }, 12 | 13 | start: function(options) { 14 | options = _.defaults(options || {}, this.defaults); 15 | 16 | if (options.links) { 17 | this._getRootElement().on('click.backboneIntercept', 'a', _.bind(this._interceptLinks, this)); 18 | } 19 | if (options.forms) { 20 | this._getRootElement().on('submit.backboneIntercept', _.bind(this._interceptForms, this)); 21 | } 22 | }, 23 | 24 | stop: function() { 25 | this._getRootElement().off('.backboneIntercept'); 26 | }, 27 | 28 | navigate: function(uri, options) { 29 | Backbone.history.navigate(uri, options); 30 | }, 31 | 32 | // Creates and caches a jQuery object for the body element 33 | _getRootElement: function() { 34 | if (this._body) { return this._body; } 35 | this._body = Backbone.$(this.rootSelector); 36 | return this._body; 37 | }, 38 | 39 | // Prevent the default behavior of submitting the 40 | // form if the action attribute is present and is 41 | // a value 42 | _interceptForms: function(e) { 43 | if (e.target && e.target.action) { return; } 44 | e.preventDefault(); 45 | }, 46 | 47 | _interceptLinks: function(e) { 48 | // Only intercept left-clicks 49 | if (e.which !== 1) { return; } 50 | var $link = Backbone.$(e.currentTarget); 51 | 52 | // Get the href; stop processing if there isn't one 53 | var href = $link.attr('href'); 54 | if (!href) { return; } 55 | 56 | // Determine if we're supposed to bypass the link 57 | // based on its attributes 58 | var bypass = this._getAttr($link, 'bypass'); 59 | if (bypass !== undefined && bypass !== 'false') { 60 | return; 61 | } 62 | 63 | // If the Ctrl key is held, we want to open in a new tab. 64 | bypass = e.ctrlKey; 65 | if (bypass) { 66 | return; 67 | } 68 | 69 | // The options we pass along to navigate 70 | var navOptions = { 71 | trigger: this.defaults.trigger 72 | }; 73 | 74 | // Determine if it's trigger: false based on the attributes 75 | var trigger = this._getAttr($link, 'trigger'); 76 | 77 | if (trigger === 'false') { 78 | navOptions.trigger = false; 79 | } else if (trigger === 'true') { 80 | navOptions.trigger = true; 81 | } 82 | 83 | // Return if the URL is absolute, or if the protocol is mailto or javascript 84 | if (/^#|javascript:|mailto:|(?:\w+:)?\/\//.test(href)) { return; } 85 | 86 | // If we haven't been stopped yet, then we prevent the default action 87 | e.preventDefault(); 88 | 89 | // Get the computed pathname of the link, removing 90 | // the leading slash. Regex required for IE8 support 91 | var pathname = $link[0].pathname.replace(/^\//, '') + $link[0].search; 92 | 93 | // Lastly we send off the information to the router 94 | if (!this.navigate) { return; } 95 | this.navigate(pathname, navOptions); 96 | }, 97 | 98 | _getAttr: function($el, name) { 99 | var attr = $el.attr(name); 100 | if (attr !== undefined) { 101 | return attr; 102 | } 103 | var data = $el.attr('data-' + name); 104 | if (data !== undefined) { 105 | return data; 106 | } 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /src/wrapper.js: -------------------------------------------------------------------------------- 1 | (function(root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['backbone', 'underscore'], function(Backbone, _) { 4 | return factory(Backbone, _); 5 | }); 6 | } 7 | else if (typeof exports !== 'undefined') { 8 | var Backbone = require('backbone'); 9 | var _ = require('underscore'); 10 | module.exports = factory(Backbone, _); 11 | } 12 | else { 13 | factory(root.Backbone, root._); 14 | } 15 | }(this, function(Backbone, _) { 16 | 'use strict'; 17 | 18 | // @include backbone.intercept.js 19 | 20 | return Backbone.Intercept; 21 | })); 22 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "curly" : true, 5 | "eqeqeq" : true, 6 | "es3" : true, 7 | "forin" : true, 8 | "immed" : true, 9 | "indent" : 2, 10 | "latedef" : true, 11 | "newcap" : true, 12 | "noarg" : true, 13 | "noempty" : true, 14 | "nonbsp" : true, 15 | "nonew" : true, 16 | "plusplus" : true, 17 | "undef" : true, 18 | "unused" : true, 19 | "strict" : false, 20 | "trailing" : true, 21 | 22 | "asi" : false, 23 | "boss" : false, 24 | "debug" : false, 25 | "eqnull" : false, 26 | "esnext" : false, 27 | "evil" : false, 28 | "expr" : true, 29 | "funcscope" : false, 30 | "globalstrict" : false, 31 | "iterator" : false, 32 | "lastsemic" : false, 33 | "laxbreak" : false, 34 | "laxcomma" : false, 35 | "loopfunc" : false, 36 | "maxerr" : 50, 37 | "multistr" : false, 38 | "notypeof" : false, 39 | "proto" : false, 40 | "scripturl" : false, 41 | "smarttabs" : false, 42 | "shadow" : false, 43 | "sub" : false, 44 | "supernew" : false, 45 | "validthis" : false, 46 | "noyield" : false, 47 | 48 | "browser" : true, 49 | "couch" : false, 50 | "devel" : false, 51 | "dojo" : false, 52 | "jquery" : false, 53 | "mootools" : false, 54 | "node" : false, 55 | "nonstandard" : false, 56 | "prototypejs" : false, 57 | "rhino" : false, 58 | "worker" : false, 59 | "wsh" : false, 60 | "yui" : false, 61 | "globals": { 62 | "Backbone": true, 63 | "_": true, 64 | "$": true, 65 | "require": true, 66 | "module": true, 67 | "define": true, 68 | "exports": true, 69 | "expect": true, 70 | "beforeEach": true, 71 | "afterEach": true, 72 | "describe": true, 73 | "it": true 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/setup/helpers.js: -------------------------------------------------------------------------------- 1 | // Adds elements to the document 2 | var setFixtures = function () { 3 | _.each(arguments, function (content) { 4 | helpers.$fixtures.append(content); 5 | }); 6 | }; 7 | 8 | // Empties our body 9 | var clearFixtures = function () { 10 | helpers.$fixtures.empty(); 11 | }; 12 | 13 | function setupTestHelpers() { 14 | beforeEach(function() { 15 | this.originalIntercept = _.clone(Backbone.Intercept); 16 | this.sinon = sinon.sandbox.create(); 17 | this.setFixtures = setFixtures; 18 | this.clearFixtures = clearFixtures; 19 | global.stub = _.bind(this.sinon.stub, this.sinon); 20 | global.spy = _.bind(this.sinon.spy, this.sinon); 21 | }); 22 | 23 | afterEach(function() { 24 | clearFixtures(); 25 | this.sinon.restore(); 26 | delete global.stub; 27 | delete global.spy; 28 | Backbone.Intercept.stop(); 29 | Backbone.Intercept = this.originalIntercept; 30 | }); 31 | } 32 | 33 | var helpers = {}; 34 | 35 | var node = typeof exports !== 'undefined'; 36 | var $ = node ? require('jquery') : $; 37 | 38 | if (node) { 39 | helpers.$fixtures = $('body'); 40 | setupTestHelpers(); 41 | } 42 | 43 | // when running in browser 44 | else { 45 | this.global = window; 46 | mocha.setup('bdd'); 47 | 48 | window.expect = chai.expect; 49 | window.sinon = sinon; 50 | 51 | onload = function() { 52 | mocha.checkLeaks(); 53 | mocha.globals(['stub', 'spy']); 54 | mocha.run(); 55 | helpers.$fixtures = $('#fixtures'); 56 | setupTestHelpers(); 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /test/setup/node.js: -------------------------------------------------------------------------------- 1 | // Create our JSDom document 2 | global.jsdom = require('jsdom').jsdom; 3 | global.document = jsdom( 4 | '', 5 | { url: 'http://0.0.0.0:8000/test/spec-runner.html' } 6 | ); 7 | global.window = global.document.parentWindow; 8 | global.navigator = window.navigator = { 9 | userAgent: 'NodeJS JSDom', 10 | appVersion: '' 11 | }; 12 | 13 | var sinon = require('sinon'); 14 | var chai = require('chai'); 15 | var sinonChai = require('sinon-chai'); 16 | 17 | global.$ = require('jquery'); 18 | global._ = require('underscore'); 19 | global.Backbone = require('backbone'); 20 | global.Backbone.$ = global.$; 21 | 22 | chai.use(sinonChai); 23 | 24 | global.expect = chai.expect; 25 | global.sinon = sinon; 26 | 27 | require('../../src/backbone.intercept'); 28 | -------------------------------------------------------------------------------- /test/spec-runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone.Intercept Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /test/unit/clicks.js: -------------------------------------------------------------------------------- 1 | describe('When clicking a link', function() { 2 | beforeEach(function() { 3 | Backbone.Intercept.start(); 4 | }); 5 | 6 | describe('with a left click', function() { 7 | beforeEach(function() { 8 | this.leftClick = $.Event('click'); 9 | this.leftClick.which = 1; 10 | this.leftClick.preventDefault(); 11 | this.sinon.stub(this.leftClick, 'preventDefault'); 12 | }); 13 | 14 | describe('and the link is relative, without a leading slash', function() { 15 | beforeEach(function() { 16 | var $link = $(''); 17 | this.setFixtures($link); 18 | this.sinon.stub(Backbone.Intercept, 'navigate'); 19 | $link.trigger(this.leftClick); 20 | }); 21 | 22 | it('should intercept the link', function() { 23 | expect(this.leftClick.preventDefault).to.have.been.calledOnce; 24 | }); 25 | 26 | it('should pass trigger:true to the share method', function() { 27 | expect(Backbone.Intercept.navigate) 28 | .to.have.been.calledOnce 29 | .and.calledWithExactly('test/path/to/my-thing', {trigger:true}); 30 | }); 31 | }); 32 | 33 | describe('and the link is relative, with a leading slash', function() { 34 | beforeEach(function() { 35 | var $link = $(''); 36 | this.setFixtures($link); 37 | this.sinon.stub(Backbone.Intercept, 'navigate'); 38 | $link.trigger(this.leftClick); 39 | }); 40 | 41 | it('should intercept the link', function() { 42 | expect(this.leftClick.preventDefault).to.have.been.calledOnce; 43 | }); 44 | 45 | it('should pass trigger:true to the share method', function() { 46 | expect(Backbone.Intercept.navigate) 47 | .to.have.been.calledOnce 48 | .and.calledWithExactly('path/to/my-thing', {trigger:true}); 49 | }); 50 | }); 51 | 52 | describe('and the link starts with www', function() { 53 | beforeEach(function() { 54 | var $link = $(''); 55 | this.setFixtures($link); 56 | $link.trigger(this.leftClick); 57 | }); 58 | 59 | it('should intercept the link', function() { 60 | expect(this.leftClick.preventDefault).to.have.been.calledOnce; 61 | }); 62 | }); 63 | 64 | describe('and the link is a fragment (it starts with #)', function() { 65 | beforeEach(function() { 66 | var $link = $(''); 67 | this.setFixtures($link); 68 | $link.trigger(this.leftClick); 69 | }); 70 | 71 | it('should not intercept the link', function() { 72 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 73 | }); 74 | }); 75 | 76 | describe('and the link is absolute, starting with http://', function() { 77 | beforeEach(function() { 78 | var $link = $(''); 79 | this.setFixtures($link); 80 | $link.trigger(this.leftClick); 81 | }); 82 | 83 | it('should not intercept the link', function() { 84 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 85 | }); 86 | }); 87 | 88 | describe('and the link is absolute, starting with https://', function() { 89 | beforeEach(function() { 90 | var $link = $(''); 91 | this.setFixtures($link); 92 | $link.trigger(this.leftClick); 93 | }); 94 | 95 | it('should not intercept the link', function() { 96 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 97 | }); 98 | }); 99 | 100 | describe('and the link is using the javascript: protocol', function() { 101 | beforeEach(function() { 102 | var $link = $(''); 103 | this.setFixtures($link); 104 | $link.trigger(this.leftClick); 105 | }); 106 | 107 | it('should not intercept the link', function() { 108 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 109 | }); 110 | }); 111 | 112 | describe('and the link is using the mailto: protocol', function() { 113 | beforeEach(function() { 114 | var $link = $(''); 115 | this.setFixtures($link); 116 | $link.trigger(this.leftClick); 117 | }); 118 | 119 | it('should not intercept the link', function() { 120 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 121 | }); 122 | }); 123 | 124 | describe('and the link has no href attribute', function() { 125 | beforeEach(function() { 126 | var $link = $(''); 127 | this.setFixtures($link); 128 | $link.trigger(this.leftClick); 129 | }); 130 | 131 | it('should not intercept the link', function() { 132 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 133 | }); 134 | }); 135 | 136 | describe('and the "data-bypass" attribute exists on the link', function() { 137 | beforeEach(function() { 138 | var $link = $(''); 139 | this.setFixtures($link); 140 | $link.trigger(this.leftClick); 141 | }); 142 | 143 | it('should not intercept the link', function() { 144 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 145 | }); 146 | }); 147 | 148 | describe('and the "data-bypass" attribute is "true" on the link', function() { 149 | beforeEach(function() { 150 | var $link = $(''); 151 | this.setFixtures($link); 152 | $link.trigger(this.leftClick); 153 | }); 154 | 155 | it('should not intercept the link', function() { 156 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 157 | }); 158 | }); 159 | 160 | describe('and the "data-bypass" attribute is "false" on the link', function() { 161 | beforeEach(function() { 162 | var $link = $(''); 163 | this.setFixtures($link); 164 | $link.trigger(this.leftClick); 165 | }); 166 | 167 | it('should intercept the link', function() { 168 | expect(this.leftClick.preventDefault).to.have.been.calledOnce; 169 | }); 170 | }); 171 | 172 | describe('and the "bypass" attribute exists on the link', function() { 173 | beforeEach(function() { 174 | var $link = $(''); 175 | this.setFixtures($link); 176 | $link.trigger(this.leftClick); 177 | }); 178 | 179 | it('should not intercept the link', function() { 180 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 181 | }); 182 | }); 183 | 184 | describe('and the "bypass" attribute is "true" on the link', function() { 185 | beforeEach(function() { 186 | var $link = $(''); 187 | this.setFixtures($link); 188 | $link.trigger(this.leftClick); 189 | }); 190 | 191 | it('should not intercept the link', function() { 192 | expect(this.leftClick.preventDefault).to.not.have.been.calledOnce; 193 | }); 194 | }); 195 | 196 | describe('and the "bypass" attribute is "false" on the link', function() { 197 | beforeEach(function() { 198 | var $link = $(''); 199 | this.setFixtures($link); 200 | $link.trigger(this.leftClick); 201 | }); 202 | 203 | it('should intercept the link', function() { 204 | expect(this.leftClick.preventDefault).to.have.been.calledOnce; 205 | }); 206 | }); 207 | 208 | describe('and the "trigger" attribute is undefined on the link', function() { 209 | beforeEach(function() { 210 | var $link = $(''); 211 | this.setFixtures($link); 212 | this.sinon.stub(Backbone.Intercept, 'navigate'); 213 | $link.trigger(this.leftClick); 214 | }); 215 | 216 | it('should intercept the link', function() { 217 | expect(this.leftClick.preventDefault).to.have.been.calledOnce; 218 | }); 219 | 220 | it('should pass trigger:true to the share method', function() { 221 | expect(Backbone.Intercept.navigate) 222 | .to.have.been.calledOnce 223 | .and.calledWithExactly('test/link/to/my-page', {trigger:true}); 224 | }); 225 | }); 226 | 227 | describe('and the "trigger" attribute is false on the link', function() { 228 | beforeEach(function() { 229 | var $link = $(''); 230 | this.setFixtures($link); 231 | this.sinon.stub(Backbone.Intercept, 'navigate'); 232 | $link.trigger(this.leftClick); 233 | }); 234 | 235 | it('should intercept the link', function() { 236 | expect(this.leftClick.preventDefault).to.have.been.calledOnce; 237 | }); 238 | 239 | it('should pass trigger:false to the share method', function() { 240 | expect(Backbone.Intercept.navigate) 241 | .to.have.been.calledOnce 242 | .and.calledWithExactly('test/link/to/my-page', {trigger:false}); 243 | }); 244 | }); 245 | 246 | describe('and the "data-trigger" attribute is false on the link', function() { 247 | beforeEach(function() { 248 | var $link = $(''); 249 | this.setFixtures($link); 250 | this.sinon.stub(Backbone.Intercept, 'navigate'); 251 | $link.trigger(this.leftClick); 252 | }); 253 | 254 | it('should intercept the link', function() { 255 | expect(this.leftClick.preventDefault).to.have.been.calledOnce; 256 | }); 257 | 258 | it('should pass trigger:false to the share method', function() { 259 | expect(Backbone.Intercept.navigate) 260 | .to.have.been.calledOnce 261 | .and.calledWithExactly('test/link/to/my-page', {trigger:false}); 262 | }); 263 | }); 264 | 265 | describe('the link has search parameters', function() { 266 | beforeEach(function() { 267 | var $link = $(''); 268 | this.setFixtures($link); 269 | this.sinon.stub(Backbone.Intercept, 'navigate'); 270 | $link.trigger(this.leftClick); 271 | }); 272 | 273 | it('should intercept the link', function() { 274 | expect(this.leftClick.preventDefault).to.have.been.calledOnce; 275 | }); 276 | 277 | it('should have the search parameters inside the link navigated to', function() { 278 | expect(Backbone.Intercept.navigate) 279 | .to.have.been.calledOnce 280 | .and.calledWithExactly('test/path/to/my-thing?page=1&limit=50', {trigger:true}); 281 | }); 282 | }); 283 | }); 284 | 285 | describe('with a middle click', function() { 286 | beforeEach(function() { 287 | this.middleClick = $.Event('click'); 288 | this.middleClick.which = 2; 289 | this.middleClick.preventDefault(); 290 | this.sinon.stub(this.middleClick, 'preventDefault'); 291 | }); 292 | 293 | describe('and the link is relative, without a leading slash', function() { 294 | beforeEach(function() { 295 | var $link = $(''); 296 | this.setFixtures($link); 297 | $link.trigger(this.middleClick); 298 | }); 299 | 300 | it('should not intercept the link', function() { 301 | expect(this.middleClick.preventDefault).to.not.have.been.calledOnce; 302 | }); 303 | }); 304 | }); 305 | 306 | describe('with a right click', function() { 307 | beforeEach(function() { 308 | this.rightClick = $.Event('click'); 309 | this.rightClick.which = 3; 310 | this.rightClick.preventDefault(); 311 | this.sinon.stub(this.rightClick, 'preventDefault'); 312 | }); 313 | 314 | describe('and the link is relative, without a leading slash', function() { 315 | beforeEach(function() { 316 | var $link = $(''); 317 | this.setFixtures($link); 318 | $link.trigger(this.rightClick); 319 | }); 320 | 321 | it('should not intercept the link', function() { 322 | expect(this.rightClick.preventDefault).to.not.have.been.calledOnce; 323 | }); 324 | }); 325 | }); 326 | }); 327 | -------------------------------------------------------------------------------- /test/unit/forms.js: -------------------------------------------------------------------------------- 1 | describe('When submitting a form', function() { 2 | beforeEach(function() { 3 | Backbone.Intercept.start(); 4 | this.submitEvent = $.Event('submit'); 5 | this.submitEvent.preventDefault(); 6 | this.sinon.spy(this.submitEvent, 'preventDefault'); 7 | }); 8 | 9 | describe('and the form does not have an action attribute', function() { 10 | beforeEach(function() { 11 | var $form = $('
'); 12 | this.setFixtures($form); 13 | $form.trigger(this.submitEvent); 14 | }); 15 | 16 | it('should prevent the default action', function() { 17 | expect(this.submitEvent.preventDefault).to.have.been.calledOnce; 18 | }); 19 | }); 20 | 21 | describe('and the form has a defined action attribute', function() { 22 | beforeEach(function() { 23 | var $form = $('
'); 24 | this.setFixtures($form); 25 | $form.trigger(this.submitEvent); 26 | }); 27 | 28 | it('should not prevent the default action', function() { 29 | expect(this.submitEvent.preventDefault).to.not.have.been.calledOnce; 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/navigate.js: -------------------------------------------------------------------------------- 1 | describe('When calling the navigate method', function() { 2 | beforeEach(function() { 3 | this.sinon.stub(Backbone.history, 'navigate'); 4 | Backbone.Intercept.navigate('path/to/my-resource', {trigger:true}); 5 | }); 6 | 7 | it('should pass the URI along to the history navigate method', function() { 8 | expect(Backbone.history.navigate) 9 | .to.have.been.calledOnce 10 | .and.calledWithExactly('path/to/my-resource', {trigger:true}); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/unit/root-element.js: -------------------------------------------------------------------------------- 1 | describe('When setting the root selector before starting Intercept', function() { 2 | beforeEach(function() { 3 | Backbone.Intercept.rootSelector = '.backbone-root'; 4 | 5 | // Create a normal link; one that shouldn't be intercepted 6 | this.$normalLink = $(''); 7 | this.setFixtures(this.$normalLink); 8 | 9 | // Create the scope of our app 10 | this.$scope = $('
'); 11 | this.setFixtures(this.$scope); 12 | 13 | // Lastly, insert a link into the scope 14 | this.$scopedLink = $(''); 15 | this.$scope.append(this.$scopedLink); 16 | 17 | // Start intercept and click around 18 | Backbone.Intercept.start(); 19 | 20 | this.leftClick = $.Event('click'); 21 | this.leftClick.which = 1; 22 | this.leftClick.preventDefault(); 23 | }); 24 | 25 | describe('and clicking a link outside of the scoped element', function() { 26 | beforeEach(function() { 27 | this.$normalLink.trigger(this.leftClick); 28 | }); 29 | 30 | it('should not prevent the default behavior of that link', function() { 31 | expect(this.leftClick.preventDefault).to.not.have.beenCalled; 32 | }); 33 | }); 34 | 35 | describe('and clicking a link within the scoped element', function() { 36 | beforeEach(function() { 37 | this.$scopedLink.trigger(this.leftClick); 38 | }); 39 | 40 | it('should prevent the default behavior of that link', function() { 41 | expect(this.leftClick.preventDefault).to.have.beenCalledOnce; 42 | }); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /test/unit/start.js: -------------------------------------------------------------------------------- 1 | describe('When calling start', function() { 2 | beforeEach(function() { 3 | this.sinon.stub(_, 'bind', _.identity); 4 | }); 5 | 6 | describe('and passing no options', function() { 7 | beforeEach(function() { 8 | this.$body = Backbone.Intercept._getRootElement(); 9 | this.sinon.stub(this.$body, 'on'); 10 | Backbone.Intercept.start(); 11 | }); 12 | 13 | it('should set handlers for click and submit events', function() { 14 | expect(_.bind) 15 | .to.have.been.calledTwice 16 | .and.calledWithExactly(Backbone.Intercept._interceptLinks, Backbone.Intercept) 17 | .and.calledWithExactly(Backbone.Intercept._interceptForms, Backbone.Intercept); 18 | expect(this.$body.on) 19 | .to.have.been.calledTwice 20 | .and.calledWithExactly('click.backboneIntercept', 'a', Backbone.Intercept._interceptLinks) 21 | .and.calledWithExactly('submit.backboneIntercept', Backbone.Intercept._interceptForms); 22 | }); 23 | }); 24 | 25 | describe('and specifying false for links', function() { 26 | beforeEach(function() { 27 | this.$body = Backbone.Intercept._getRootElement(); 28 | this.sinon.stub(this.$body, 'on'); 29 | Backbone.Intercept.start({links: false}); 30 | }); 31 | 32 | it('should only set handlers for link events', function() { 33 | expect(_.bind) 34 | .to.have.been.calledOnce 35 | .and.calledWithExactly(Backbone.Intercept._interceptForms, Backbone.Intercept); 36 | expect(this.$body.on) 37 | .to.have.been.calledOnce 38 | .and.calledWithExactly('submit.backboneIntercept', Backbone.Intercept._interceptForms); 39 | }); 40 | }); 41 | 42 | describe('and specifying false for forms', function() { 43 | beforeEach(function() { 44 | this.$body = Backbone.Intercept._getRootElement(); 45 | this.sinon.stub(this.$body, 'on'); 46 | Backbone.Intercept.start({forms: false}); 47 | }); 48 | 49 | it('should only set handlers for link events', function() { 50 | expect(_.bind) 51 | .to.have.been.calledOnce 52 | .and.calledWithExactly(Backbone.Intercept._interceptLinks, Backbone.Intercept); 53 | expect(this.$body.on) 54 | .to.have.been.calledOnce 55 | .and.calledWithExactly('click.backboneIntercept', 'a', Backbone.Intercept._interceptLinks); 56 | }); 57 | }); 58 | 59 | describe('and specifying false for links and forms', function() { 60 | beforeEach(function() { 61 | this.$body = Backbone.Intercept._getRootElement(); 62 | this.sinon.stub(this.$body, 'on'); 63 | Backbone.Intercept.start({forms: false, links: false}); 64 | }); 65 | 66 | it('should not set any handlers', function() { 67 | expect(this.$body.on).to.not.have.beenCalled; 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/unit/stop.js: -------------------------------------------------------------------------------- 1 | describe('When calling stop', function() { 2 | beforeEach(function() { 3 | this.$body = Backbone.Intercept._getRootElement(); 4 | this.sinon.stub(this.$body, 'off'); 5 | Backbone.Intercept.stop(); 6 | }); 7 | 8 | it('should remove all listeners', function() { 9 | expect(this.$body.off) 10 | .to.have.been.calledOnce 11 | .and.calledWithExactly('.backboneIntercept'); 12 | }); 13 | }); 14 | --------------------------------------------------------------------------------