├── .bowerrc ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── bower.json ├── demo ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── app │ ├── .buildignore │ ├── .htaccess │ ├── favicon.ico │ ├── index.html │ ├── robots.txt │ ├── scripts │ │ ├── app.js │ │ └── controllers │ │ │ └── main.js │ ├── styles │ │ ├── bootstrap.css │ │ └── main.css │ └── views │ │ └── main.html ├── bower.json ├── gulpfile.js ├── karma-e2e.conf.js ├── karma.conf.js ├── package.json └── test │ ├── .jshintrc │ ├── runner.html │ └── spec │ └── controllers │ └── main.js ├── dist ├── LICENSE.md ├── README.md ├── bower.json ├── gestures.js └── gestures.min.js ├── gulpfile.js ├── karma.conf.js ├── package.json ├── src └── gestures.js └── test └── gestures.Spec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "components" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | node_modules 3 | components 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "angular": false, 23 | "Hammer": false, 24 | // testing 25 | "it": false, 26 | "beforeEach": false, 27 | "describe": false, 28 | "expect": false, 29 | "inject": false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | // load all grunt tasks 5 | require('load-grunt-tasks')(grunt); 6 | require('time-grunt')(grunt); 7 | 8 | grunt.initConfig({ 9 | concat : { 10 | dist : { 11 | files : { 12 | /*'components/hammerjs/hammer.js', */ 13 | 'dist/gestures.js' : [ 'src/gestures.js' ] 14 | } 15 | } 16 | }, 17 | watch: { 18 | js: { 19 | files: ['src/{,*/}*.js'], 20 | tasks: ['build'] 21 | } 22 | }, 23 | clean : { 24 | dist : { 25 | files : [ { 26 | dot : true, 27 | src : [ 'dist/**' ] 28 | } ] 29 | }, 30 | server : '.tmp' 31 | }, 32 | uglify: { 33 | dist: { 34 | files: { 'dist/gestures.min.js': [ 'dist/gestures.js' ] } 35 | } 36 | }, 37 | jshint : { 38 | options : { 39 | jshintrc : '.jshintrc' 40 | }, 41 | all : [ 'Gruntfile.js', 'src/*.js' ] 42 | }, 43 | copy: { 44 | main: { 45 | files: [ 46 | { 47 | expand: true, 48 | src: ['./*.md'], 49 | dest: 'dist/', 50 | filter: 'isFile' 51 | } // copy *.md 52 | ] 53 | } 54 | } 55 | }); 56 | 57 | grunt.registerTask('build', [ 58 | 'clean:dist', 59 | // 'jshint', 60 | 'concat', 61 | 'uglify', 62 | 'copy', 63 | 'bowerdist' 64 | ]); 65 | 66 | grunt.registerTask('default', [ 'build' ]); 67 | grunt.registerTask('watchme', [ 'watch' ]); 68 | 69 | grunt.registerTask('bowerdist', function () { 70 | var bower = require('./bower.json'); 71 | var fs = require('fs'); 72 | 73 | bower.main = bower.main.map(function (main) { 74 | return main.replace('dist/', ''); 75 | }); 76 | 77 | fs.writeFileSync('./dist/bower.json', JSON.stringify(bower, null, 2)); 78 | }); 79 | }; 80 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## The MIT License 2 | 3 | Copyright (c) 2012-2013 Patrick Bartsch 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-gestures 2 | 3 | AngularJS directive that adds support for multi touch gestures to your app, based on hammer.js. 4 | 5 | ## Usage 6 | 7 | * Include `gestures.js` or `gestures.min.js` into your page 8 | * Declare `'angular-gestures'` as a dependency for your angular app: `angular.module('myApp', ['angular-gestures']);` 9 | * Config the **recognizers** before you use the directive like this: `hammerDefaultOptsProvider.set({recognizers: [[Hammer.Tap, {time: 250}]] });` 10 | * Use attributes on containers the same way you use `ng-click`: e.g. `hm-tap` 11 | ```HTML 12 | 13 | ``` 14 | * You can use angular interpolations like this : `hm-swipe="remove_something({{ id }})"` 15 | * You can also use Hammer.js options by e.g. `hm-tap-opts="{hold: false}"` 16 | 17 | Note that [hammer.js](http://hammerjs.github.io/) is an additional requirement and is not included in `angular-gestures`. 18 | 19 | ### Event data 20 | 21 | Pass the `$event` object in the usual way e.g. `hm-drag="myDrag($event)"` then access its internals like so: 22 | ```JS 23 | $scope.myDrag = function(event) { 24 | console.log(event.gesture); 25 | } 26 | ``` 27 | Refer to the [Hammer.js docs](https://github.com/EightMedia/hammer.js/wiki/Getting-Started) for more details on the properties of `event`. 28 | 29 | ## Supported events 30 | 31 | 32 | * hmDoubleTap : 'doubletap', 33 | * hmDragstart : 'dragstart', 34 | * hmDrag : 'drag', 35 | * hmDragUp : 'dragup', 36 | * hmDragDown : 'dragdown', 37 | * hmDragLeft : 'dragleft', 38 | * hmDragRight : 'dragright', 39 | * hmDragend : 'dragend', 40 | * hmHold : 'hold', 41 | * hmPinch : 'pinch', 42 | * hmPinchIn : 'pinchin', 43 | * hmPinchOut : 'pinchout', 44 | * hmRelease : 'release', 45 | * hmRotate : 'rotate', 46 | * hmSwipe : 'swipe', 47 | * hmSwipeUp : 'swipeup', 48 | * hmSwipeDown : 'swipedown', 49 | * hmSwipeLeft : 'swipeleft', 50 | * hmSwipeRight : 'swiperight', 51 | * hmTap : 'tap', 52 | * hmTouch : 'touch', 53 | * hmTransformstart : 'transformstart', 54 | * hmTransform : 'transform', 55 | * hmTransformend : 'transformend' 56 | 57 | 58 | All [Hammerjs events](https://github.com/EightMedia/hammer.js/wiki/Getting-Started) are supported. The corresponding Angularjs attribute has `hm-` prepended to the name. So for example, the 'doubletap' event becomes `hm-double-tap` etc. 59 | 60 | *Attention* : *end and *start events are NOT CamelCased because of issues caused by $animate interference. 61 | 62 | ## Default options 63 | To set recognizer default options you can use `hammerDefaultOptsProvider`. Access it like in the demo: 64 | 65 | ``` 66 | angular.module('angularGesturesDemoApp', ['angular-gestures', 'ngRoute']) 67 | .config(function ($routeProvider, hammerDefaultOptsProvider) { 68 | $routeProvider 69 | .when('/', { 70 | templateUrl: 'views/main.html', 71 | controller: 'MainCtrl' 72 | }) 73 | .otherwise({ 74 | redirectTo: '/' 75 | }); 76 | hammerDefaultOptsProvider.set({ 77 | recognizers: [[Hammer.Tap, {time: 250}]] 78 | }); 79 | }); 80 | ``` 81 | 82 | ## Bower 83 | If you want to use angular-momentum-scroll with bower, add the following dependency to your component.json 84 | 85 | `"angular-gestures": "latest"` 86 | 87 | ## Require.js/AMD/Node.js 88 | angular-gestures has support for Require.js/AMD/Node.js. When using AMD modules, make sure that you define 89 | hammer.js using `Hammer`, same goes for `node.js`. If you are not using Require.js/AMD/Node.js, angular-gestures 90 | will fall back to using the global `Hammer`/`angular` objects. -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-gestures", 3 | "description": "AngularJS directive that adds support for multi touch gestures to your app. Based on hammer.js.", 4 | "version": "0.3.3", 5 | "main": [ 6 | "dist/gestures.js" 7 | ], 8 | "homepage": "http://github.com/wzr1337/angular-gestures", 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/wzr1337/bower-angular-gestures" 12 | }, 13 | "author": "wzr1337", 14 | "license": "MIT", 15 | "readmeFilename": "README.md", 16 | "dependencies": { 17 | "angular": ">=1.2.0 <=2.0.0", 18 | "hammerjs": "~2.0.0" 19 | }, 20 | "devDependencies": { 21 | "angular-mocks": ">=1.2.0 <=2.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demo/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /demo/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /demo/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | app/bower_components 6 | -------------------------------------------------------------------------------- /demo/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "angular": false, 23 | "Hammer" : false 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demo/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.8' 4 | - '0.10' 5 | before_script: 6 | - 'npm install -g bower grunt-cli' 7 | - 'bower install' 8 | -------------------------------------------------------------------------------- /demo/app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /demo/app/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache Configuration File 2 | 3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access 4 | # to the main server config file (usually called `httpd.conf`), you should add 5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. 6 | 7 | # ############################################################################## 8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) # 9 | # ############################################################################## 10 | 11 | # ------------------------------------------------------------------------------ 12 | # | Cross-domain AJAX requests | 13 | # ------------------------------------------------------------------------------ 14 | 15 | # Enable cross-origin AJAX requests. 16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 17 | # http://enable-cors.org/ 18 | 19 | # 20 | # Header set Access-Control-Allow-Origin "*" 21 | # 22 | 23 | # ------------------------------------------------------------------------------ 24 | # | CORS-enabled images | 25 | # ------------------------------------------------------------------------------ 26 | 27 | # Send the CORS header for images when browsers request it. 28 | # https://developer.mozilla.org/en/CORS_Enabled_Image 29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 31 | 32 | 33 | 34 | 35 | SetEnvIf Origin ":" IS_CORS 36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 37 | 38 | 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | # | Web fonts access | 43 | # ------------------------------------------------------------------------------ 44 | 45 | # Allow access from all domains for web fonts 46 | 47 | 48 | 49 | Header set Access-Control-Allow-Origin "*" 50 | 51 | 52 | 53 | 54 | # ############################################################################## 55 | # # ERRORS # 56 | # ############################################################################## 57 | 58 | # ------------------------------------------------------------------------------ 59 | # | 404 error prevention for non-existing redirected folders | 60 | # ------------------------------------------------------------------------------ 61 | 62 | # Prevent Apache from returning a 404 error for a rewrite if a directory 63 | # with the same name does not exist. 64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews 65 | # http://www.webmasterworld.com/apache/3808792.htm 66 | 67 | Options -MultiViews 68 | 69 | # ------------------------------------------------------------------------------ 70 | # | Custom error messages / pages | 71 | # ------------------------------------------------------------------------------ 72 | 73 | # You can customize what Apache returns to the client in case of an error (see 74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: 75 | 76 | ErrorDocument 404 /404.html 77 | 78 | 79 | # ############################################################################## 80 | # # INTERNET EXPLORER # 81 | # ############################################################################## 82 | 83 | # ------------------------------------------------------------------------------ 84 | # | Better website experience | 85 | # ------------------------------------------------------------------------------ 86 | 87 | # Force IE to render pages in the highest available mode in the various 88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. 89 | 90 | 91 | Header set X-UA-Compatible "IE=edge" 92 | # `mod_headers` can't match based on the content-type, however, we only 93 | # want to send this header for HTML pages and not for the other resources 94 | 95 | Header unset X-UA-Compatible 96 | 97 | 98 | 99 | # ------------------------------------------------------------------------------ 100 | # | Cookie setting from iframes | 101 | # ------------------------------------------------------------------------------ 102 | 103 | # Allow cookies to be set from iframes in IE. 104 | 105 | # 106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 107 | # 108 | 109 | # ------------------------------------------------------------------------------ 110 | # | Screen flicker | 111 | # ------------------------------------------------------------------------------ 112 | 113 | # Stop screen flicker in IE on CSS rollovers (this only works in 114 | # combination with the `ExpiresByType` directives for images from below). 115 | 116 | # BrowserMatch "MSIE" brokenvary=1 117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 118 | # BrowserMatch "Opera" !brokenvary 119 | # SetEnvIf brokenvary 1 force-no-vary 120 | 121 | 122 | # ############################################################################## 123 | # # MIME TYPES AND ENCODING # 124 | # ############################################################################## 125 | 126 | # ------------------------------------------------------------------------------ 127 | # | Proper MIME types for all files | 128 | # ------------------------------------------------------------------------------ 129 | 130 | 131 | 132 | # Audio 133 | AddType audio/mp4 m4a f4a f4b 134 | AddType audio/ogg oga ogg 135 | 136 | # JavaScript 137 | # Normalize to standard type (it's sniffed in IE anyways): 138 | # http://tools.ietf.org/html/rfc4329#section-7.2 139 | AddType application/javascript js jsonp 140 | AddType application/json json 141 | 142 | # Video 143 | AddType video/mp4 mp4 m4v f4v f4p 144 | AddType video/ogg ogv 145 | AddType video/webm webm 146 | AddType video/x-flv flv 147 | 148 | # Web fonts 149 | AddType application/font-woff woff 150 | AddType application/vnd.ms-fontobject eot 151 | 152 | # Browsers usually ignore the font MIME types and sniff the content, 153 | # however, Chrome shows a warning if other MIME types are used for the 154 | # following fonts. 155 | AddType application/x-font-ttf ttc ttf 156 | AddType font/opentype otf 157 | 158 | # Make SVGZ fonts work on iPad: 159 | # https://twitter.com/FontSquirrel/status/14855840545 160 | AddType image/svg+xml svg svgz 161 | AddEncoding gzip svgz 162 | 163 | # Other 164 | AddType application/octet-stream safariextz 165 | AddType application/x-chrome-extension crx 166 | AddType application/x-opera-extension oex 167 | AddType application/x-shockwave-flash swf 168 | AddType application/x-web-app-manifest+json webapp 169 | AddType application/x-xpinstall xpi 170 | AddType application/xml atom rdf rss xml 171 | AddType image/webp webp 172 | AddType image/x-icon ico 173 | AddType text/cache-manifest appcache manifest 174 | AddType text/vtt vtt 175 | AddType text/x-component htc 176 | AddType text/x-vcard vcf 177 | 178 | 179 | 180 | # ------------------------------------------------------------------------------ 181 | # | UTF-8 encoding | 182 | # ------------------------------------------------------------------------------ 183 | 184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. 185 | AddDefaultCharset utf-8 186 | 187 | # Force UTF-8 for certain file formats. 188 | 189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml 190 | 191 | 192 | 193 | # ############################################################################## 194 | # # URL REWRITES # 195 | # ############################################################################## 196 | 197 | # ------------------------------------------------------------------------------ 198 | # | Rewrite engine | 199 | # ------------------------------------------------------------------------------ 200 | 201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is 202 | # necessary for the following directives to work. 203 | 204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to 205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the 206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks 207 | 208 | # Also, some cloud hosting services require `RewriteBase` to be set: 209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site 210 | 211 | 212 | Options +FollowSymlinks 213 | # Options +SymLinksIfOwnerMatch 214 | RewriteEngine On 215 | # RewriteBase / 216 | 217 | 218 | # ------------------------------------------------------------------------------ 219 | # | Suppressing / Forcing the "www." at the beginning of URLs | 220 | # ------------------------------------------------------------------------------ 221 | 222 | # The same content should never be available under two different URLs especially 223 | # not with and without "www." at the beginning. This can cause SEO problems 224 | # (duplicate content), therefore, you should choose one of the alternatives and 225 | # redirect the other one. 226 | 227 | # By default option 1 (no "www.") is activated: 228 | # http://no-www.org/faq.php?q=class_b 229 | 230 | # If you'd prefer to use option 2, just comment out all the lines from option 1 231 | # and uncomment the ones from option 2. 232 | 233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 234 | 235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 236 | 237 | # Option 1: rewrite www.example.com → example.com 238 | 239 | 240 | RewriteCond %{HTTPS} !=on 241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 243 | 244 | 245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 246 | 247 | # Option 2: rewrite example.com → www.example.com 248 | 249 | # Be aware that the following might not be a good idea if you use "real" 250 | # subdomains for certain parts of your website. 251 | 252 | # 253 | # RewriteCond %{HTTPS} !=on 254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 256 | # 257 | 258 | 259 | # ############################################################################## 260 | # # SECURITY # 261 | # ############################################################################## 262 | 263 | # ------------------------------------------------------------------------------ 264 | # | Content Security Policy (CSP) | 265 | # ------------------------------------------------------------------------------ 266 | 267 | # You can mitigate the risk of cross-site scripting and other content-injection 268 | # attacks by setting a Content Security Policy which whitelists trusted sources 269 | # of content for your site. 270 | 271 | # The example header below allows ONLY scripts that are loaded from the current 272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't 273 | # work as-is for your site! 274 | 275 | # To get all the details you'll need to craft a reasonable policy for your site, 276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or 277 | # see the specification: http://w3.org/TR/CSP). 278 | 279 | # 280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'" 281 | # 282 | # Header unset Content-Security-Policy 283 | # 284 | # 285 | 286 | # ------------------------------------------------------------------------------ 287 | # | File access | 288 | # ------------------------------------------------------------------------------ 289 | 290 | # Block access to directories without a default document. 291 | # Usually you should leave this uncommented because you shouldn't allow anyone 292 | # to surf through every directory on your server (which may includes rather 293 | # private places like the CMS's directories). 294 | 295 | 296 | Options -Indexes 297 | 298 | 299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | # Block access to hidden files and directories. 302 | # This includes directories used by version control systems such as Git and SVN. 303 | 304 | 305 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 306 | RewriteCond %{SCRIPT_FILENAME} -f 307 | RewriteRule "(^|/)\." - [F] 308 | 309 | 310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 311 | 312 | # Block access to backup and source files. 313 | # These files may be left by some text editors and can pose a great security 314 | # danger when anyone has access to them. 315 | 316 | 317 | Order allow,deny 318 | Deny from all 319 | Satisfy All 320 | 321 | 322 | # ------------------------------------------------------------------------------ 323 | # | Secure Sockets Layer (SSL) | 324 | # ------------------------------------------------------------------------------ 325 | 326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: 327 | # prevent `https://www.example.com` when your certificate only allows 328 | # `https://secure.example.com`. 329 | 330 | # 331 | # RewriteCond %{SERVER_PORT} !^443 332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 333 | # 334 | 335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 336 | 337 | # Force client-side SSL redirection. 338 | 339 | # If a user types "example.com" in his browser, the above rule will redirect him 340 | # to the secure version of the site. That still leaves a window of opportunity 341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the 342 | # request. The following header ensures that browser will ONLY connect to your 343 | # server via HTTPS, regardless of what the users type in the address bar. 344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ 345 | 346 | # 347 | # Header set Strict-Transport-Security max-age=16070400; 348 | # 349 | 350 | # ------------------------------------------------------------------------------ 351 | # | Server software information | 352 | # ------------------------------------------------------------------------------ 353 | 354 | # Avoid displaying the exact Apache version number, the description of the 355 | # generic OS-type and the information about Apache's compiled-in modules. 356 | 357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! 358 | 359 | # ServerTokens Prod 360 | 361 | 362 | # ############################################################################## 363 | # # WEB PERFORMANCE # 364 | # ############################################################################## 365 | 366 | # ------------------------------------------------------------------------------ 367 | # | Compression | 368 | # ------------------------------------------------------------------------------ 369 | 370 | 371 | 372 | # Force compression for mangled headers. 373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping 374 | 375 | 376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 378 | 379 | 380 | 381 | # Compress all output labeled with one of the following MIME-types 382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 383 | # and can remove the `` and `` lines 384 | # as `AddOutputFilterByType` is still in the core directives). 385 | 386 | AddOutputFilterByType DEFLATE application/atom+xml \ 387 | application/javascript \ 388 | application/json \ 389 | application/rss+xml \ 390 | application/vnd.ms-fontobject \ 391 | application/x-font-ttf \ 392 | application/x-web-app-manifest+json \ 393 | application/xhtml+xml \ 394 | application/xml \ 395 | font/opentype \ 396 | image/svg+xml \ 397 | image/x-icon \ 398 | text/css \ 399 | text/html \ 400 | text/plain \ 401 | text/x-component \ 402 | text/xml 403 | 404 | 405 | 406 | 407 | # ------------------------------------------------------------------------------ 408 | # | Content transformations | 409 | # ------------------------------------------------------------------------------ 410 | 411 | # Prevent some of the mobile network providers from modifying the content of 412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. 413 | 414 | # 415 | # Header set Cache-Control "no-transform" 416 | # 417 | 418 | # ------------------------------------------------------------------------------ 419 | # | ETag removal | 420 | # ------------------------------------------------------------------------------ 421 | 422 | # Since we're sending far-future expires headers (see below), ETags can 423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags. 424 | 425 | # `FileETag None` is not enough for every server. 426 | 427 | Header unset ETag 428 | 429 | 430 | FileETag None 431 | 432 | # ------------------------------------------------------------------------------ 433 | # | Expires headers (for better cache control) | 434 | # ------------------------------------------------------------------------------ 435 | 436 | # The following expires headers are set pretty far in the future. If you don't 437 | # control versioning with filename-based cache busting, consider lowering the 438 | # cache time for resources like CSS and JS to something like 1 week. 439 | 440 | 441 | 442 | ExpiresActive on 443 | ExpiresDefault "access plus 1 month" 444 | 445 | # CSS 446 | ExpiresByType text/css "access plus 1 year" 447 | 448 | # Data interchange 449 | ExpiresByType application/json "access plus 0 seconds" 450 | ExpiresByType application/xml "access plus 0 seconds" 451 | ExpiresByType text/xml "access plus 0 seconds" 452 | 453 | # Favicon (cannot be renamed!) 454 | ExpiresByType image/x-icon "access plus 1 week" 455 | 456 | # HTML components (HTCs) 457 | ExpiresByType text/x-component "access plus 1 month" 458 | 459 | # HTML 460 | ExpiresByType text/html "access plus 0 seconds" 461 | 462 | # JavaScript 463 | ExpiresByType application/javascript "access plus 1 year" 464 | 465 | # Manifest files 466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" 467 | ExpiresByType text/cache-manifest "access plus 0 seconds" 468 | 469 | # Media 470 | ExpiresByType audio/ogg "access plus 1 month" 471 | ExpiresByType image/gif "access plus 1 month" 472 | ExpiresByType image/jpeg "access plus 1 month" 473 | ExpiresByType image/png "access plus 1 month" 474 | ExpiresByType video/mp4 "access plus 1 month" 475 | ExpiresByType video/ogg "access plus 1 month" 476 | ExpiresByType video/webm "access plus 1 month" 477 | 478 | # Web feeds 479 | ExpiresByType application/atom+xml "access plus 1 hour" 480 | ExpiresByType application/rss+xml "access plus 1 hour" 481 | 482 | # Web fonts 483 | ExpiresByType application/font-woff "access plus 1 month" 484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 485 | ExpiresByType application/x-font-ttf "access plus 1 month" 486 | ExpiresByType font/opentype "access plus 1 month" 487 | ExpiresByType image/svg+xml "access plus 1 month" 488 | 489 | 490 | 491 | # ------------------------------------------------------------------------------ 492 | # | Filename-based cache busting | 493 | # ------------------------------------------------------------------------------ 494 | 495 | # If you're not using a build process to manage your filename version revving, 496 | # you might want to consider enabling the following directives to route all 497 | # requests such as `/css/style.12345.css` to `/css/style.css`. 498 | 499 | # To understand why this is important and a better idea than `*.css?v231`, read: 500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring 501 | 502 | # 503 | # RewriteCond %{REQUEST_FILENAME} !-f 504 | # RewriteCond %{REQUEST_FILENAME} !-d 505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 506 | # 507 | 508 | # ------------------------------------------------------------------------------ 509 | # | File concatenation | 510 | # ------------------------------------------------------------------------------ 511 | 512 | # Allow concatenation from within specific CSS and JS files, e.g.: 513 | # Inside of `script.combined.js` you could have 514 | # 515 | # 516 | # and they would be included into this single file. 517 | 518 | # 519 | # 520 | # Options +Includes 521 | # AddOutputFilterByType INCLUDES application/javascript application/json 522 | # SetOutputFilter INCLUDES 523 | # 524 | # 525 | # Options +Includes 526 | # AddOutputFilterByType INCLUDES text/css 527 | # SetOutputFilter INCLUDES 528 | # 529 | # 530 | 531 | # ------------------------------------------------------------------------------ 532 | # | Persistent connections | 533 | # ------------------------------------------------------------------------------ 534 | 535 | # Allow multiple requests to be sent over the same TCP connection: 536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. 537 | 538 | # Enable if you serve a lot of static content but, be aware of the 539 | # possible disadvantages! 540 | 541 | # 542 | # Header set Connection Keep-Alive 543 | # 544 | -------------------------------------------------------------------------------- /demo/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wzr1337/angular-gestures/2b5226b8286436c5bbdb4d82951abc0659f7f187/demo/app/favicon.ico -------------------------------------------------------------------------------- /demo/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /demo/app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /demo/app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularGesturesDemoApp', ['angular-gestures', 'ngRoute']) 4 | .config(function ($routeProvider, hammerDefaultOptsProvider) { 5 | $routeProvider 6 | .when('/', { 7 | templateUrl: 'views/main.html', 8 | controller: 'MainCtrl' 9 | }) 10 | .otherwise({ 11 | redirectTo: '/' 12 | }); 13 | hammerDefaultOptsProvider.set({ 14 | recognizers: [ 15 | [Hammer.Tap,{ event: 'tap'}], 16 | [Hammer.Tap, { event: 'doubletap', taps: 2 }, [], ['tap']], 17 | [Hammer.Press], 18 | [Hammer.Pan] 19 | ] 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /demo/app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularGesturesDemoApp') 4 | .controller('MainCtrl', function ($scope) { 5 | $scope.type = '--'; 6 | $scope.handleGesture = function($event) { 7 | console.log('button event', $event); 8 | $scope.type = $event.type; 9 | $event.srcEvent.stopPropagation(); 10 | }; 11 | $scope.handleParentGesture = function($event) { 12 | console.log('parent event', $event); 13 | $scope.type = $event.type; 14 | }; 15 | }); 16 | -------------------------------------------------------------------------------- /demo/app/styles/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fafafa; 3 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | color: #333; 5 | } 6 | 7 | .hero-unit { 8 | margin: 4rem auto 0 auto; 9 | width: 90%; 10 | font-size: 1.5rem; 11 | font-weight: 200; 12 | line-height: 2rem; 13 | background-color: #eee; 14 | border-radius: 6px; 15 | padding: 4rem; 16 | } 17 | 18 | .hero-unit h1 { 19 | font-size: 4rem; 20 | line-height: 1; 21 | } 22 | 23 | .hero-unit #container { 24 | width : 10rem; 25 | height : 10rem; 26 | line-height: 5rem; 27 | } 28 | -------------------------------------------------------------------------------- /demo/app/views/main.html: -------------------------------------------------------------------------------- 1 |
7 |

Angular Gestures Demo

8 |

Please tap, press(hold) and pan the following container

9 |
You can {{ type }} this container
16 |
17 | -------------------------------------------------------------------------------- /demo/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularGesturesDemo", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.2.9", 6 | "angular-gestures": "~0.3.0", 7 | "angular-route": "~1.2.9", 8 | "hammerjs": "~2.0.0" 9 | }, 10 | "devDependencies": { 11 | "angular-mocks": "~1.2.9", 12 | "angular-scenario": "~1.2.9" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /demo/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var jshint = require('gulp-jshint'); 5 | var uglify = require('gulp-uglify'); 6 | var rev = require('gulp-rev'); 7 | var usemin = require('gulp-usemin'); 8 | var path = require('path'); 9 | var rimraf = require('gulp-rimraf'); 10 | var ngAnnotate = require('gulp-ng-annotate'); 11 | var server = require('gulp-webserver'); 12 | 13 | var bases = { 14 | app: 'app/', 15 | dist: 'dist/' 16 | }; 17 | 18 | var paths = { 19 | scripts: ['scripts/**/*.js', '!scripts/libs/**/*.js'], 20 | styles: ['styles/**/*.css'], 21 | html: ['index.html', '404.html'], 22 | views: ['views/**/*.html'] 23 | }; 24 | 25 | gulp.task('copy', ['clean'], function() { 26 | return gulp.src(paths.views, {cwd: bases.app, read:true}) 27 | .pipe(gulp.dest(path.join(bases.dist, 'views'))); 28 | }); 29 | 30 | gulp.task('clean', function() { 31 | var stream = gulp.src([bases.dist], { read: false }) // much faster 32 | .pipe(rimraf()); 33 | return stream; 34 | }); 35 | 36 | // Process scripts and concatenate them into one output file 37 | gulp.task('scripts', ['clean'], function() { 38 | return gulp.src(paths.scripts, {cwd: bases.app}) 39 | .pipe(jshint('.jshintrc')) 40 | .pipe(jshint.reporter('jshint-stylish')); 41 | }); 42 | 43 | 44 | gulp.task('usemin', ['clean', 'scripts'], function () { 45 | return gulp.src('./*.html', {cwd: bases.app}) 46 | .pipe(usemin({ 47 | js: [ngAnnotate(), uglify(), rev()] 48 | })) 49 | .pipe(gulp.dest(bases.dist)); 50 | }); 51 | 52 | // Rerun the task when a file changes 53 | gulp.task('watch', function() { 54 | gulp.watch(path.join(bases.app, paths.scripts[0]), ['usemin']); 55 | }); 56 | 57 | gulp.task('default', ['clean', 'usemin', 'copy']); 58 | 59 | gulp.task('serve', function() { 60 | gulp.src('./') 61 | .pipe(server({ 62 | livereload: true, 63 | directoryListing: true, 64 | open: true 65 | })); 66 | }); 67 | -------------------------------------------------------------------------------- /demo/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['ng-scenario'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'test/e2e/**/*.js' 15 | ], 16 | 17 | // list of files / patterns to exclude 18 | exclude: [], 19 | 20 | // web server port 21 | port: 8080, 22 | 23 | // level of logging 24 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 25 | logLevel: config.LOG_INFO, 26 | 27 | 28 | // enable / disable watching file and executing tests whenever any file changes 29 | autoWatch: false, 30 | 31 | 32 | // Start these browsers, currently available: 33 | // - Chrome 34 | // - ChromeCanary 35 | // - Firefox 36 | // - Opera 37 | // - Safari (only Mac) 38 | // - PhantomJS 39 | // - IE (only Windows) 40 | browsers: ['Chrome'], 41 | 42 | 43 | // Continuous Integration mode 44 | // if true, it capture browsers, run tests and exit 45 | singleRun: false 46 | 47 | // Uncomment the following lines if you are using grunt's server to run the tests 48 | // proxies: { 49 | // '/': 'http://localhost:9000/' 50 | // }, 51 | // URL root prevent conflicts with the site root 52 | // urlRoot: '_karma_' 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /demo/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['jasmine'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'app/bower_components/angular/angular.js', 15 | 'app/bower_components/angular-mocks/angular-mocks.js', 16 | 'app/bower_components/angular-gestures/gestures.min.js', 17 | 'app/scripts/*.js', 18 | 'app/scripts/**/*.js', 19 | 'test/mock/**/*.js', 20 | 'test/spec/**/*.js' 21 | ], 22 | 23 | // list of files / patterns to exclude 24 | exclude: [], 25 | 26 | // web server port 27 | port: 8080, 28 | 29 | // level of logging 30 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 31 | logLevel: config.LOG_INFO, 32 | 33 | 34 | // enable / disable watching file and executing tests whenever any file changes 35 | autoWatch: false, 36 | 37 | 38 | // Start these browsers, currently available: 39 | // - Chrome 40 | // - ChromeCanary 41 | // - Firefox 42 | // - Opera 43 | // - Safari (only Mac) 44 | // - PhantomJS 45 | // - IE (only Windows) 46 | browsers: ['Chrome'], 47 | 48 | 49 | // Continuous Integration mode 50 | // if true, it capture browsers, run tests and exit 51 | singleRun: false 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-gestures-demo", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "gulp": "^3.8.11", 7 | "gulp-autoprefixer": "^2.1.0", 8 | "gulp-concat": "^2.5.2", 9 | "gulp-jshint": "^1.9.2", 10 | "gulp-minify-css": "^0.5.1", 11 | "gulp-minify-html": "^1.0.1", 12 | "gulp-ng-annotate": "^0.5.2", 13 | "gulp-ngmin": "^0.3.0", 14 | "gulp-rev": "^3.0.1", 15 | "gulp-rimraf": "^0.1.1", 16 | "gulp-uglify": "^1.1.0", 17 | "gulp-usemin": "^0.3.11", 18 | "gulp-webserver": "^0.9.0", 19 | "jshint-stylish": "^1.0.1" 20 | }, 21 | "engines": { 22 | "node": ">=0.10.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "spyOn": false 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /demo/test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /demo/test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('angularGesturesDemoApp', 'angular-gestures')); 7 | 8 | var MainCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MainCtrl = $controller('MainCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a variable "type" to the scope', function () { 20 | expect(scope.type).toBe('--'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /dist/LICENSE.md: -------------------------------------------------------------------------------- 1 | ## The MIT License 2 | 3 | Copyright (c) 2012-2013 Patrick Bartsch 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | # angular-gestures 2 | 3 | AngularJS directive that adds support for multi touch gestures to your app, based on hammer.js. 4 | 5 | ## Usage 6 | 7 | * Include `gestures.js` or `gestures.min.js` into your page 8 | * Declare `'angular-gestures'` as a dependency for your angular app: `angular.module('myApp', ['angular-gestures']);` 9 | * Config the **recognizers** before you use the directive like this: `hammerDefaultOptsProvider.set({recognizers: [[Hammer.Tap, {time: 250}]] });` 10 | * Use attributes on containers the same way you use `ng-click`: e.g. `hm-tap` 11 | ```HTML 12 | 13 | ``` 14 | * You can use angular interpolations like this : `hm-swipe="remove_something({{ id }})"` 15 | * You can also use Hammer.js options by e.g. `hm-tap-opts="{hold: false}"` 16 | 17 | Note that [hammer.js](http://hammerjs.github.io/) is an additional requirement and is not included in `angular-gestures`. 18 | 19 | ### Event data 20 | 21 | Pass the `$event` object in the usual way e.g. `hm-drag="myDrag($event)"` then access its internals like so: 22 | ```JS 23 | $scope.myDrag = function(event) { 24 | console.log(event.gesture); 25 | } 26 | ``` 27 | Refer to the [Hammer.js docs](https://github.com/EightMedia/hammer.js/wiki/Getting-Started) for more details on the properties of `event`. 28 | 29 | ## Supported events 30 | 31 | 32 | * hmDoubleTap : 'doubletap', 33 | * hmDragstart : 'dragstart', 34 | * hmDrag : 'drag', 35 | * hmDragUp : 'dragup', 36 | * hmDragDown : 'dragdown', 37 | * hmDragLeft : 'dragleft', 38 | * hmDragRight : 'dragright', 39 | * hmDragend : 'dragend', 40 | * hmHold : 'hold', 41 | * hmPinch : 'pinch', 42 | * hmPinchIn : 'pinchin', 43 | * hmPinchOut : 'pinchout', 44 | * hmRelease : 'release', 45 | * hmRotate : 'rotate', 46 | * hmSwipe : 'swipe', 47 | * hmSwipeUp : 'swipeup', 48 | * hmSwipeDown : 'swipedown', 49 | * hmSwipeLeft : 'swipeleft', 50 | * hmSwipeRight : 'swiperight', 51 | * hmTap : 'tap', 52 | * hmTouch : 'touch', 53 | * hmTransformstart : 'transformstart', 54 | * hmTransform : 'transform', 55 | * hmTransformend : 'transformend' 56 | 57 | 58 | All [Hammerjs events](https://github.com/EightMedia/hammer.js/wiki/Getting-Started) are supported. The corresponding Angularjs attribute has `hm-` prepended to the name. So for example, the 'doubletap' event becomes `hm-double-tap` etc. 59 | 60 | *Attention* : *end and *start events are NOT CamelCased because of issues caused by $animate interference. 61 | 62 | ## Default options 63 | To set recognizer default options you can use `hammerDefaultOptsProvider`. Access it like in the demo: 64 | 65 | ``` 66 | angular.module('angularGesturesDemoApp', ['angular-gestures', 'ngRoute']) 67 | .config(function ($routeProvider, hammerDefaultOptsProvider) { 68 | $routeProvider 69 | .when('/', { 70 | templateUrl: 'views/main.html', 71 | controller: 'MainCtrl' 72 | }) 73 | .otherwise({ 74 | redirectTo: '/' 75 | }); 76 | hammerDefaultOptsProvider.set({ 77 | recognizers: [[Hammer.Tap, {time: 250}]] 78 | }); 79 | }); 80 | ``` 81 | 82 | ## Bower 83 | If you want to use angular-momentum-scroll with bower, add the following dependency to your component.json 84 | 85 | `"angular-gestures": "latest"` 86 | 87 | ## Require.js/AMD/Node.js 88 | angular-gestures has support for Require.js/AMD/Node.js. When using AMD modules, make sure that you define 89 | hammer.js using `Hammer`, same goes for `node.js`. If you are not using Require.js/AMD/Node.js, angular-gestures 90 | will fall back to using the global `Hammer`/`angular` objects. -------------------------------------------------------------------------------- /dist/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-gestures", 3 | "description": "AngularJS directive that adds support for multi touch gestures to your app. Based on hammer.js.", 4 | "version": "0.3.1", 5 | "main": [ 6 | "gestures.min.js" 7 | ], 8 | "homepage": "http://github.com/wzr1337/angular-gestures", 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/wzr1337/bower-angular-gestures" 12 | }, 13 | "author": "wzr1337", 14 | "license": "MIT", 15 | "readmeFilename": "README.md", 16 | "dependencies": { 17 | "angular": ">=1.2.0 <=1.4.0", 18 | "hammerjs": "~2.0.0" 19 | }, 20 | "devDependencies": { 21 | "angular-mocks": ">=1.2.0 <=1.4.0" 22 | } 23 | } -------------------------------------------------------------------------------- /dist/gestures.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspired by AngularJS' implementation of "click dblclick mousedown..." 3 | * 4 | * This ties in the Hammer 2 events to attributes like: 5 | * 6 | * hm-tap="add_something()" hm-swipe="remove_something()" 7 | * 8 | * and also has support for Hammer options with: 9 | * 10 | * hm-tap-opts="{hold: false}" 11 | * 12 | * or any other of the "hm-event" listed underneath. 13 | */ 14 | 15 | 'use strict'; 16 | (function (root, factory) { 17 | // AMD 18 | if (typeof define === 'function' && define.amd) { 19 | define(['angular', 'Hammer'], function (angular, Hammer) { 20 | return factory({}, angular, Hammer); 21 | }); 22 | } 23 | // Node.js 24 | else if (typeof exports === 'object') { 25 | module.exports = factory({}, require('angular'), require('Hammer')); 26 | } 27 | // Angular 28 | else if (angular) { 29 | factory(root, root.angular, root.Hammer); 30 | } 31 | }(this,function(global,angular,Hammer){ 32 | angular.module('angular-gestures', []); 33 | 34 | var HGESTURES = { 35 | hmDoubleTap: 'doubletap', 36 | hmDragstart: 'panstart', // will bedeprecated soon, us Pan* 37 | hmDrag: 'pan', // will bedeprecated soon, us Pan* 38 | hmDragUp: 'panup', // will bedeprecated soon, us Pan* 39 | hmDragDown: 'pandown', // will bedeprecated soon, us Pan* 40 | hmDragLeft: 'panleft', // will bedeprecated soon, us Pan* 41 | hmDragRight: 'panright', // will bedeprecated soon, us Pan* 42 | hmDragend: 'panend', // will bedeprecated soon, us Pan* 43 | hmPanstart: 'panstart', 44 | hmPan: 'pan', 45 | hmPanUp: 'panup', 46 | hmPanDown: 'pandown', 47 | hmPanLeft: 'panleft', 48 | hmPanRight: 'panright', 49 | hmPanend: 'panend', 50 | hmHold: 'press', 51 | hmPinch: 'pinch', 52 | hmPinchstart: 'pinchstart', 53 | hmPinchend: 'pinchend', 54 | hmPinchIn: 'pinchin', 55 | hmPinchOut: 'pinchout', 56 | hmPress: 'press', 57 | hmPressUp: 'pressup', 58 | hmRelease: 'pressup', 59 | hmRotate: 'rotate', 60 | hmSwipe: 'swipe', 61 | hmSwipeUp: 'swipeup', 62 | hmSwipeDown: 'swipedown', 63 | hmSwipeLeft: 'swipeleft', 64 | hmSwipeRight: 'swiperight', 65 | hmTap: 'tap', 66 | hmTouch: 'touch', 67 | hmTransformstart: 'transformstart', 68 | hmTransform: 'transform', 69 | hmTransformend: 'transformend' 70 | }; 71 | 72 | var HRECOGNIZERS = { 73 | hmDoubleTap: [Hammer.Tap, 'Hammer.Tap'], 74 | hmDragstart: [Hammer.Pan, 'Hammer.Pan'], 75 | hmDrag: [Hammer.Pan, 'Hammer.Pan'], 76 | hmDragUp: [Hammer.Pan, 'Hammer.Pan'], 77 | hmDragDown: [Hammer.Pan, 'Hammer.Pan'], 78 | hmDragLeft: [Hammer.Pan, 'Hammer.Pan'], 79 | hmDragRight: [Hammer.Pan, 'Hammer.Pan'], 80 | hmDragend: [Hammer.Pan, 'Hammer.Pan'], 81 | hmPanstart: [Hammer.Pan, 'Hammer.Pan'], 82 | hmPan: [Hammer.Pan, 'Hammer.Pan'], 83 | hmPanUp: [Hammer.Pan, 'Hammer.Pan'], 84 | hmPanDown: [Hammer.Pan, 'Hammer.Pan'], 85 | hmPanLeft: [Hammer.Pan, 'Hammer.Pan'], 86 | hmPanRight: [Hammer.Pan, 'Hammer.Pan'], 87 | hmPanend: [Hammer.Pan, 'Hammer.Pan'], 88 | hmHold: [Hammer.Press, 'Hammer.Press'], 89 | hmPinch: [Hammer.Pinch, 'Hammer.Pinch'], 90 | hmPinchstart: [Hammer.Pinch, 'Hammer.Pinch'], 91 | hmPinchend: [Hammer.Pinch, 'Hammer.Pinch'], 92 | hmPinchIn: [Hammer.Pinch, 'Hammer.Pinch'], 93 | hmPinchOut: [Hammer.Pinch, 'Hammer.Pinch'], 94 | hmPress: [Hammer.Press, 'Hammer.Press'], 95 | hmPressUp: [Hammer.Press, 'Hammer.Press'], 96 | hmRelease: [Hammer.Press, 'Hammer.Press'], 97 | hmRotate: [Hammer.Rotate, 'Hammer.Rotate'], 98 | hmSwipe: [Hammer.Swipe, 'Hammer.Swipe'], 99 | hmSwipeUp: [Hammer.Swipe, 'Hammer.Swipe'], 100 | hmSwipeDown: [Hammer.Swipe, 'Hammer.Swipe'], 101 | hmSwipeLeft: [Hammer.Swipe, 'Hammer.Swipe'], 102 | hmSwipeRight: [Hammer.Swipe, 'Hammer.Swipe'], 103 | hmTap: [Hammer.Tap, 'Hammer.Tap'] 104 | }; 105 | 106 | var VERBOSE = false; 107 | 108 | angular.forEach(HGESTURES, function(eventName, directiveName) { 109 | angular.module('angular-gestures').directive(directiveName, ['$parse', '$log', '$timeout', 'hammerDefaultOpts', function($parse, $log, $timeout, hammerDefaultOpts) { 110 | return function(scope, element, attr) { 111 | var handler; 112 | attr.$observe(directiveName, function(value) { 113 | var callback = $parse(value); 114 | var opts = $parse(attr[directiveName + 'Opts'])(scope, {}); 115 | var defaultOpts = angular.copy(hammerDefaultOpts); 116 | 117 | angular.extend(defaultOpts, opts); 118 | 119 | if (angular.isUndefined(element.hammertime)) { 120 | 121 | // validate that needed recognizer is enabled 122 | var recognizers = angular.isDefined(defaultOpts.recognizers) ? defaultOpts.recognizers : []; 123 | var recognizer = HRECOGNIZERS[directiveName]; 124 | if(angular.isDefined(recognizer)) { 125 | var enabled = false; 126 | angular.forEach(recognizers, function(r) { 127 | if (recognizer[0] === r[0]) { 128 | if (angular.isUndefined(r[1].enable) || r[1].enable === true) { 129 | enabled = true; 130 | } 131 | } 132 | }); 133 | if (!enabled) { 134 | throw new Error('Directive ' + directiveName + ' requires gesture recognizer [' + recognizer[1] + '] to be enabled'); 135 | } 136 | } 137 | 138 | element.hammer = new Hammer.Manager(element[0], defaultOpts); 139 | scope.$on('$destroy', function() { 140 | element.hammer.off(eventName); 141 | element.hammer.destroy(); 142 | }); 143 | } 144 | 145 | handler = function(event) { 146 | if (VERBOSE) { 147 | $log.debug('angular-gestures: ', eventName, event); 148 | } 149 | var callbackHandler = function () { 150 | var cb = callback(scope, { $event : event}); 151 | if (typeof cb === 'function') { 152 | cb.call(scope, event); 153 | } 154 | }; 155 | 156 | if (scope.$root.$$phase === '$apply' || 157 | scope.$root.$$phase === '$digest') { 158 | callbackHandler(); 159 | } else { 160 | scope.$apply(callbackHandler); 161 | } 162 | 163 | }; 164 | // register actual event 165 | element.hammer.on(eventName, handler); 166 | }); 167 | }; 168 | }]); 169 | }); 170 | 171 | angular.module('angular-gestures').provider('hammerDefaultOpts', function HammerDefaultOptsProvider() { 172 | var opts = {}; 173 | 174 | this.set = function(value) { 175 | opts = value; 176 | }; 177 | 178 | this.$get = function() { 179 | return opts; 180 | }; 181 | }); 182 | })); 183 | -------------------------------------------------------------------------------- /dist/gestures.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(a,b){"function"==typeof define&&define.amd?define(["angular","Hammer"],function(a,c){return b({},a,c)}):"object"==typeof exports?module.exports=b({},require("angular"),require("Hammer")):angular&&b(a,a.angular,a.Hammer)}(this,function(a,b,c){b.module("angular-gestures",[]);var d={hmDoubleTap:"doubletap",hmDragstart:"panstart",hmDrag:"pan",hmDragUp:"panup",hmDragDown:"pandown",hmDragLeft:"panleft",hmDragRight:"panright",hmDragend:"panend",hmPanstart:"panstart",hmPan:"pan",hmPanUp:"panup",hmPanDown:"pandown",hmPanLeft:"panleft",hmPanRight:"panright",hmPanend:"panend",hmHold:"press",hmPinch:"pinch",hmPinchstart:"pinchstart",hmPinchend:"pinchend",hmPinchIn:"pinchin",hmPinchOut:"pinchout",hmPress:"press",hmPressUp:"pressup",hmRelease:"pressup",hmRotate:"rotate",hmSwipe:"swipe",hmSwipeUp:"swipeup",hmSwipeDown:"swipedown",hmSwipeLeft:"swipeleft",hmSwipeRight:"swiperight",hmTap:"tap",hmTouch:"touch",hmTransformstart:"transformstart",hmTransform:"transform",hmTransformend:"transformend"},e={hmDoubleTap:[c.Tap,"Hammer.Tap"],hmDragstart:[c.Pan,"Hammer.Pan"],hmDrag:[c.Pan,"Hammer.Pan"],hmDragUp:[c.Pan,"Hammer.Pan"],hmDragDown:[c.Pan,"Hammer.Pan"],hmDragLeft:[c.Pan,"Hammer.Pan"],hmDragRight:[c.Pan,"Hammer.Pan"],hmDragend:[c.Pan,"Hammer.Pan"],hmPanstart:[c.Pan,"Hammer.Pan"],hmPan:[c.Pan,"Hammer.Pan"],hmPanUp:[c.Pan,"Hammer.Pan"],hmPanDown:[c.Pan,"Hammer.Pan"],hmPanLeft:[c.Pan,"Hammer.Pan"],hmPanRight:[c.Pan,"Hammer.Pan"],hmPanend:[c.Pan,"Hammer.Pan"],hmHold:[c.Press,"Hammer.Press"],hmPinch:[c.Pinch,"Hammer.Pinch"],hmPinchstart:[c.Pinch,"Hammer.Pinch"],hmPinchend:[c.Pinch,"Hammer.Pinch"],hmPinchIn:[c.Pinch,"Hammer.Pinch"],hmPinchOut:[c.Pinch,"Hammer.Pinch"],hmPress:[c.Press,"Hammer.Press"],hmPressUp:[c.Press,"Hammer.Press"],hmRelease:[c.Press,"Hammer.Press"],hmRotate:[c.Rotate,"Hammer.Rotate"],hmSwipe:[c.Swipe,"Hammer.Swipe"],hmSwipeUp:[c.Swipe,"Hammer.Swipe"],hmSwipeDown:[c.Swipe,"Hammer.Swipe"],hmSwipeLeft:[c.Swipe,"Hammer.Swipe"],hmSwipeRight:[c.Swipe,"Hammer.Swipe"],hmTap:[c.Tap,"Hammer.Tap"]},f=!1;b.forEach(d,function(a,d){b.module("angular-gestures").directive(d,["$parse","$log","$timeout","hammerDefaultOpts",function(g,h,i,j){return function(i,k,l){var m;l.$observe(d,function(n){var o=g(n),p=g(l[d+"Opts"])(i,{}),q=b.copy(j);if(b.extend(q,p),b.isUndefined(k.hammertime)){var r=b.isDefined(q.recognizers)?q.recognizers:[],s=e[d];if(b.isDefined(s)){var t=!1;if(b.forEach(r,function(a){s[0]===a[0]&&(b.isUndefined(a[1].enable)||a[1].enable===!0)&&(t=!0)}),!t)throw new Error("Directive "+d+" requires gesture recognizer ["+s[1]+"] to be enabled")}k.hammer=new c.Manager(k[0],q),i.$on("$destroy",function(){k.hammer.off(a),k.hammer.destroy()})}m=function(b){f&&h.debug("angular-gestures: ",a,b);var c=function(){var a=o(i,{$event:b});"function"==typeof a&&a.call(i,b)};"$apply"===i.$root.$$phase||"$digest"===i.$root.$$phase?c():i.$apply(c)},k.hammer.on(a,m)})}}])}),b.module("angular-gestures").provider("hammerDefaultOpts",function(){var a={};this.set=function(b){a=b},this.$get=function(){return a}})}); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var karma = require('karma').server; 3 | 4 | /** 5 | * Run test once and exit 6 | */ 7 | gulp.task('test', function (done) { 8 | karma.start({ 9 | configFile: __dirname + '/karma.conf.js', 10 | singleRun: true 11 | }, done); 12 | }); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Apr 04 2015 10:33:04 GMT+0200 (CEST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'components/**/*.min.js', // dependecies 19 | 'components/angular-mocks/angular-mocks.js', // dependecies 20 | 'src/**/*.js', 21 | 'test/**/*.Spec.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | ], 28 | 29 | 30 | // preprocess matching files before serving them to the browser 31 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 32 | preprocessors: { 33 | }, 34 | 35 | 36 | // test results reporter to use 37 | // possible values: 'dots', 'progress' 38 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 39 | reporters: ['progress'], 40 | 41 | 42 | // web server port 43 | port: 9876, 44 | 45 | 46 | // enable / disable colors in the output (reporters and logs) 47 | colors: true, 48 | 49 | 50 | // level of logging 51 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 52 | logLevel: config.LOG_INFO, 53 | 54 | 55 | // enable / disable watching file and executing tests whenever any file changes 56 | autoWatch: true, 57 | 58 | 59 | // start these browsers 60 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 61 | browsers: ['Chrome'], 62 | 63 | 64 | // Continuous Integration mode 65 | // if true, Karma captures browsers, runs the tests and exits 66 | singleRun: false 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-gestures", 3 | "version": "0.3.3", 4 | "description": "AngularJS directive that adds support for multi touch gestures to your app. Based on hammer.js.", 5 | "main": "dist/gestures.js", 6 | "scripts": { 7 | "test": "grunt test", 8 | "build": "grunt build" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/wzr1337/angular-gestures.git" 13 | }, 14 | "keywords": [ 15 | "angularjs", 16 | "hammer.js", 17 | "gestures", 18 | "multitouch" 19 | ], 20 | "author": "wzr1337", 21 | "license": "MIT", 22 | "readmeFilename": "README.md", 23 | "dependencies": { 24 | "angular": ">=1.2.0 <=2.0.0", 25 | "hammerjs": "~2.0.0" 26 | }, 27 | "devDependencies": { 28 | "grunt": "~0.4.1", 29 | "grunt-contrib-clean": "~0.4.0", 30 | "grunt-contrib-compress": "~0.4.9", 31 | "grunt-contrib-concat": "~0.1.3", 32 | "grunt-contrib-copy": "~0.4.0", 33 | "grunt-contrib-jshint": "~0.11.2", 34 | "grunt-contrib-uglify": "~0.2.0", 35 | "grunt-contrib-watch": "^0.6.1", 36 | "gulp": "^3.8.11", 37 | "gulp-karma": "0.0.4", 38 | "jasmine-core": "^2.2.0", 39 | "karma": "^0.12.31", 40 | "karma-chrome-launcher": "^0.1.7", 41 | "karma-cli": "0.0.4", 42 | "karma-jasmine": "^0.3.5", 43 | "load-grunt-tasks": "^1.0.0", 44 | "matchdep": "~0.1.1", 45 | "time-grunt": "^1.0.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/gestures.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspired by AngularJS' implementation of "click dblclick mousedown..." 3 | * 4 | * This ties in the Hammer 2 events to attributes like: 5 | * 6 | * hm-tap="add_something()" hm-swipe="remove_something()" 7 | * 8 | * and also has support for Hammer options with: 9 | * 10 | * hm-tap-opts="{hold: false}" 11 | * 12 | * or any other of the "hm-event" listed underneath. 13 | */ 14 | 15 | 'use strict'; 16 | (function (root, factory) { 17 | // AMD 18 | if (typeof define === 'function' && define.amd) { 19 | define(['angular', 'Hammer'], function (angular, Hammer) { 20 | return factory({}, angular, Hammer); 21 | }); 22 | } 23 | // Node.js 24 | else if (typeof exports === 'object') { 25 | module.exports = factory({}, require('angular'), require('Hammer')); 26 | } 27 | // Angular 28 | else if (angular) { 29 | factory(root, root.angular, root.Hammer); 30 | } 31 | }(this,function(global,angular,Hammer){ 32 | angular.module('angular-gestures', []); 33 | 34 | var HGESTURES = { 35 | hmDoubleTap: 'doubletap', 36 | hmDragstart: 'panstart', // will be deprecated soon, use Pan* 37 | hmDrag: 'pan', // will be deprecated soon, use Pan* 38 | hmDragUp: 'panup', // will be deprecated soon, use Pan* 39 | hmDragDown: 'pandown', // will be deprecated soon, use Pan* 40 | hmDragLeft: 'panleft', // will be deprecated soon, use Pan* 41 | hmDragRight: 'panright', // will be deprecated soon, use Pan* 42 | hmDragend: 'panend', // will be deprecated soon, use Pan* 43 | hmPanstart: 'panstart', 44 | hmPan: 'pan', 45 | hmPanUp: 'panup', 46 | hmPanDown: 'pandown', 47 | hmPanLeft: 'panleft', 48 | hmPanRight: 'panright', 49 | hmPanend: 'panend', 50 | hmHold: 'press', 51 | hmPinch: 'pinch', 52 | hmPinchstart: 'pinchstart', 53 | hmPinchend: 'pinchend', 54 | hmPinchIn: 'pinchin', 55 | hmPinchOut: 'pinchout', 56 | hmPress: 'press', 57 | hmPressUp: 'pressup', 58 | hmRelease: 'pressup', 59 | hmRotate: 'rotate', 60 | hmSwipe: 'swipe', 61 | hmSwipeUp: 'swipeup', 62 | hmSwipeDown: 'swipedown', 63 | hmSwipeLeft: 'swipeleft', 64 | hmSwipeRight: 'swiperight', 65 | hmTap: 'tap', 66 | hmTouch: 'touch', 67 | hmTransformstart: 'transformstart', 68 | hmTransform: 'transform', 69 | hmTransformend: 'transformend' 70 | }; 71 | 72 | var HRECOGNIZERS = { 73 | hmDoubleTap: [Hammer.Tap, 'Hammer.Tap'], 74 | hmDragstart: [Hammer.Pan, 'Hammer.Pan'], 75 | hmDrag: [Hammer.Pan, 'Hammer.Pan'], 76 | hmDragUp: [Hammer.Pan, 'Hammer.Pan'], 77 | hmDragDown: [Hammer.Pan, 'Hammer.Pan'], 78 | hmDragLeft: [Hammer.Pan, 'Hammer.Pan'], 79 | hmDragRight: [Hammer.Pan, 'Hammer.Pan'], 80 | hmDragend: [Hammer.Pan, 'Hammer.Pan'], 81 | hmPanstart: [Hammer.Pan, 'Hammer.Pan'], 82 | hmPan: [Hammer.Pan, 'Hammer.Pan'], 83 | hmPanUp: [Hammer.Pan, 'Hammer.Pan'], 84 | hmPanDown: [Hammer.Pan, 'Hammer.Pan'], 85 | hmPanLeft: [Hammer.Pan, 'Hammer.Pan'], 86 | hmPanRight: [Hammer.Pan, 'Hammer.Pan'], 87 | hmPanend: [Hammer.Pan, 'Hammer.Pan'], 88 | hmHold: [Hammer.Press, 'Hammer.Press'], 89 | hmPinch: [Hammer.Pinch, 'Hammer.Pinch'], 90 | hmPinchstart: [Hammer.Pinch, 'Hammer.Pinch'], 91 | hmPinchend: [Hammer.Pinch, 'Hammer.Pinch'], 92 | hmPinchIn: [Hammer.Pinch, 'Hammer.Pinch'], 93 | hmPinchOut: [Hammer.Pinch, 'Hammer.Pinch'], 94 | hmPress: [Hammer.Press, 'Hammer.Press'], 95 | hmPressUp: [Hammer.Press, 'Hammer.Press'], 96 | hmRelease: [Hammer.Press, 'Hammer.Press'], 97 | hmRotate: [Hammer.Rotate, 'Hammer.Rotate'], 98 | hmSwipe: [Hammer.Swipe, 'Hammer.Swipe'], 99 | hmSwipeUp: [Hammer.Swipe, 'Hammer.Swipe'], 100 | hmSwipeDown: [Hammer.Swipe, 'Hammer.Swipe'], 101 | hmSwipeLeft: [Hammer.Swipe, 'Hammer.Swipe'], 102 | hmSwipeRight: [Hammer.Swipe, 'Hammer.Swipe'], 103 | hmTap: [Hammer.Tap, 'Hammer.Tap'] 104 | }; 105 | 106 | var VERBOSE = false; 107 | 108 | angular.forEach(HGESTURES, function(eventName, directiveName) { 109 | angular.module('angular-gestures').directive(directiveName, ['$parse', '$log', '$timeout', 'hammerDefaultOpts', function($parse, $log, $timeout, hammerDefaultOpts) { 110 | return function(scope, element, attr) { 111 | var handler; 112 | attr.$observe(directiveName, function(value) { 113 | var callback = $parse(value); 114 | var opts = $parse(attr[directiveName + 'Opts'])(scope, {}); 115 | var defaultOpts = angular.copy(hammerDefaultOpts); 116 | 117 | angular.extend(defaultOpts, opts); 118 | 119 | if (angular.isUndefined(element.hammertime)) { 120 | 121 | // validate that needed recognizer is enabled 122 | var recognizers = angular.isDefined(defaultOpts.recognizers) ? defaultOpts.recognizers : []; 123 | var recognizer = HRECOGNIZERS[directiveName]; 124 | if(angular.isDefined(recognizer)) { 125 | var enabled = false; 126 | angular.forEach(recognizers, function(r) { 127 | if (recognizer[0] === r[0]) { 128 | if (angular.isUndefined(r[1].enable) || r[1].enable === true) { 129 | enabled = true; 130 | } 131 | } 132 | }); 133 | if (!enabled) { 134 | throw new Error('Directive ' + directiveName + ' requires gesture recognizer [' + recognizer[1] + '] to be enabled'); 135 | } 136 | } 137 | 138 | element.hammer = new Hammer.Manager(element[0], defaultOpts); 139 | scope.$on('$destroy', function() { 140 | element.hammer.off(eventName); 141 | element.hammer.destroy(); 142 | }); 143 | } 144 | 145 | handler = function(event) { 146 | if (VERBOSE) { 147 | $log.debug('angular-gestures: ', eventName, event); 148 | } 149 | var callbackHandler = function () { 150 | var cb = callback(scope, { $event : event}); 151 | if (typeof cb === 'function') { 152 | cb.call(scope, event); 153 | } 154 | }; 155 | 156 | if (scope.$root.$$phase === '$apply' || 157 | scope.$root.$$phase === '$digest') { 158 | callbackHandler(); 159 | } else { 160 | scope.$apply(callbackHandler); 161 | } 162 | 163 | }; 164 | // register actual event 165 | element.hammer.on(eventName, handler); 166 | }); 167 | }; 168 | }]); 169 | }); 170 | 171 | angular.module('angular-gestures').provider('hammerDefaultOpts', function HammerDefaultOptsProvider() { 172 | var opts = {}; 173 | 174 | this.set = function(value) { 175 | opts = value; 176 | }; 177 | 178 | this.$get = function() { 179 | return opts; 180 | }; 181 | }); 182 | })); 183 | -------------------------------------------------------------------------------- /test/gestures.Spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('A suite', function() { 4 | it('contains spec with an expectation', function() { 5 | expect(true).toBe(true); 6 | }); 7 | }); 8 | 9 | describe('Gesture recognizers validation', function() { 10 | 11 | var $compile, 12 | $rootScope; 13 | 14 | beforeEach(module('angular-gestures', function(hammerDefaultOptsProvider) { 15 | hammerDefaultOptsProvider.set({ 16 | recognizers: [ 17 | [Hammer.Tap, {}], 18 | [Hammer.Pinch, { 19 | enable: false 20 | }], 21 | [Hammer.Rotate, { 22 | enable: true 23 | }], 24 | ] 25 | }); 26 | })); 27 | 28 | beforeEach(inject(function(_$compile_, _$rootScope_) { 29 | $compile = _$compile_; 30 | $rootScope = _$rootScope_; 31 | })); 32 | 33 | it('should throw if no swipe recognizer is not configured and hmSwipe directive is used', function() { 34 | expect(function() { 35 | var element = $compile("
")($rootScope); 36 | $rootScope.$digest(); 37 | }).toThrow(new Error('Directive hmSwipe requires gesture recognizer [Hammer.Swipe] to be enabled')); 38 | }); 39 | 40 | it('should not throw if tap recognizer is configured and hmTap directive is used', function() { 41 | var element = $compile("
")($rootScope); 42 | $rootScope.$digest(); 43 | }); 44 | 45 | it('should throw if pinch recognizer is configured but disabled and hmPinch directive is used', function() { 46 | expect(function() { 47 | var element = $compile("
")($rootScope); 48 | $rootScope.$digest(); 49 | }).toThrow(new Error('Directive hmPinch requires gesture recognizer [Hammer.Pinch] to be enabled')); 50 | }); 51 | 52 | it('should not throw if rotate recognizer is configured and explicitly enabled and hmRotate directive is used', function() { 53 | var element = $compile('
')($rootScope); 54 | $rootScope.$digest(); 55 | }); 56 | 57 | it('should not throw if hmTouch directive is used (no recognizer needed)', function() { 58 | var element = $compile('
')($rootScope); 59 | $rootScope.$digest(); 60 | }); 61 | }); --------------------------------------------------------------------------------