diff --git a/README.md b/README.md index dbaa29e..1f2b32e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# Angular Hammer v2.1.10 +# Angular Hammer v3.0-jsdw -An [Angular.js](https://angularjs.org/) module that enables you to bind custom behavior to [Hammer.js](http://hammerjs.github.io/) touch events. It was derived from the [Angular Hammer](https://github.com/monospaced/angular-hammer) project by [Monospaced](https://github.com/monospaced). +An [Angular.js](https://angularjs.org/) module that enables you to bind custom behavior to [Hammer.js](http://hammerjs.github.io/) touch events. It is a heavily modified version of Ryan Mullins' [angular-hammer](https://github.com/RyanMullins/angular-hammer) module, which itself was derived from the [Angular Hammer](https://github.com/monospaced/angular-hammer) project by [Monospaced](https://github.com/monospaced). + +Tweaks from Ryan Mullins version include an additional directive to allow setting of global presets and importing of global presets from `Hammer.defaults.presets`, and other general tidyup. ## Installation diff --git a/angular.hammer.js b/angular.hammer.js index 8983531..444c96a 100644 --- a/angular.hammer.js +++ b/angular.hammer.js @@ -1,7 +1,10 @@ // ---- Angular Hammer ---- -// Copyright (c) 2014 Ryan S Mullins +// Copyright (c) 2015 Ryan S Mullins // Licensed under the MIT Software License +// +// (fairly heavy) modifications by James Wilson +// (function (window, angular, Hammer) { 'use strict'; @@ -9,31 +12,10 @@ // Checking to make sure Hammer and Angular are defined if (typeof angular === 'undefined') { - if (typeof require !== 'undefined' && require) { - try { - angular = require('angular'); - } catch (e) { - return console.log('ERROR: Angular Hammer could not require() a reference to angular'); - } - } else if (typeof window.angular !== 'undefined') { - angular = window.angular; - } else { - return console.log('ERROR: Angular Hammer could not find or require() a reference to angular'); - } + throw Error("angular-hammer: AngularJS (window.angular) is undefined but is necessary."); } - if (typeof Hammer === 'undefined') { - if (typeof require !== 'undefined' && require) { - try { - Hammer = require('hammerjs'); - } catch (e) { - return console.log('ERROR: Angular Hammer could not require() a reference to Hammer'); - } - } else if (typeof window.Hammer !== 'undefined') { - Hammer = window.Hammer; - } else { - return console.log('ERROR: Angular Hammer could not find or require() a reference to Hammer'); - } + throw Error("angular-hammer: HammerJS (window.Hammer) is undefined but is necessary."); } /** @@ -85,7 +67,193 @@ * @requires angular * @requires hammer */ - angular.module('hmTouchEvents', []); + var NAME = 'hmTouchEvents'; + var hmTouchEvents = angular.module('hmTouchEvents', []); + + /** + * Provides a common interface for configuring global manager and recognizer + * options. Allows things like tap duration etc to be defaulted globally and + * overridden on a per-directive basis as needed. + * + * @return {Object} functions to add manager and recognizer options. + */ + hmTouchEvents.provider(NAME, function(){ + + var self = this; + var defaultRecognizerOpts = false; + var recognizerOptsHash = {}; + var managerOpts = {}; + + // + // In order to use the Hamme rpresets provided, we need + // to map the recognizer fn to some name: + // + var recognizerFnToName = {}; + recognizerFnToName[ Hammer.Tap.toString() ] = "tap"; + recognizerFnToName[ Hammer.Pan.toString() ] = "pan"; + recognizerFnToName[ Hammer.Pinch.toString() ] = "pinch"; + recognizerFnToName[ Hammer.Press.toString() ] = "press"; + recognizerFnToName[ Hammer.Rotate.toString() ] = "rotate"; + recognizerFnToName[ Hammer.Swipe.toString() ] = "swipe"; + + // + // normalize opts, setting its name as it should be keyed by + // and any must-have options. currently only doubletap is treated + // specially. each _name leads to a new recognizer. + // + function normalizeRecognizerOptions(opts){ + opts = angular.copy(opts); + + if(opts.event){ + + if(opts.event == "doubletap"){ + opts.type = "tap"; + if(!opts.taps) opts.taps = 2; + opts._name = "doubletap"; + } else { + opts._name = false; + } + + } else { + opts._name = opts.type || false; + } + + return opts; + } + // + // create default opts for some eventName. + // again, treat doubletap specially. + // + function defaultOptionsForEvent(eventName){ + if(eventName == "custom"){ + throw Error(NAME+"Provider: no defaults exist for custom events"); + } + var ty = getRecognizerTypeFromeventName(eventName); + return normalizeRecognizerOptions( + eventName == "doubletap" + ? {type:ty, event:"doubletap"} + : {type:ty} + ); + } + + // + // Make use of presets from Hammer.defaults.preset array + // in angular-hammer events. + // + self.applyHammerPresets = function(){ + var hammerPresets = Hammer.defaults.preset; + + //add every preset that, when normalized, has a _name. + //this precludes most custom events. + angular.forEach(hammerPresets, function(presetArr){ + + var data = presetArr[1]; + if(!data.type) data.type = recognizerFnToName[presetArr[0]]; + data = normalizeRecognizerOptions(data); + if(!data._name) return; + recognizerOptsHash[data._name] = data; + }); + } + + // + // Add a manager option (key/val to extend or object to set all): + // + self.addManagerOption = function(name, val){ + if(typeof name == "object"){ + angular.extend(managerOpts, name); + } + else { + managerOpts[name] = val; + } + } + + // + // Add a recognizer option: + // + self.addRecognizerOption = function(val){ + if(Array.isArray(val)){ + for(var i = 0; i < val.length; i++) self.addRecognizerOption(val[i]); + return; + } + if(typeof val !== "object"){ + throw Error(NAME+"Provider: addRecognizerOption: should be object or array of objects"); + } + val = normalizeRecognizerOptions(val); + + //hash by name if present, else if no event name, + //set as defaults. + if(val._name){ + recognizerOptsHash[val.type] = val; + } else if(!val.event){ + defaultRecognizerOpts = val; + } + + } + + //provide an interface to this that the hm-* directives use + //to extend their recognizer/manager opts. + self.$get = function(){ + return { + extendWithDefaultManagerOpts: function(opts){ + if(typeof opts != "object"){ + opts = {}; + } else { + opts = angular.copy(opts); + } + for(var name in managerOpts) { + if(!opts[name]) opts[name] = angular.copy(managerOpts[name]); + } + return opts; + }, + extendWithDefaultRecognizerOpts: function(eventName, opts){ + if(typeof opts !== "object"){ + opts = []; + } + if(!Array.isArray(opts)){ + opts = [opts]; + } + + //dont apply anything if this is custom event + //(beyond normalizing opts to an array): + if(eventName == "custom") return opts; + + var recognizerType = getRecognizerTypeFromeventName(eventName); + var specificOpts = recognizerOptsHash[eventName] || recognizerOptsHash[recognizerType]; + + //get the last opt provided that matches the type or eventName + //that we have. normalizing removes any eventnames we dont care about + //(everything but doubletap at the moment). + var foundOpt; + var isExactMatch = false; + var defaults = angular.extend({}, defaultRecognizerOpts || {}, specificOpts || {}); + opts.forEach(function(opt){ + + if(!opt.event && !opt.type){ + return angular.extend(defaults, opt); + } + if(isExactMatch){ + return; + } + + //more specific wins over less specific. + if(opt.event == eventName){ + foundOpt = opt; + isExactMatch = true; + } else if(!opt.event && opt.type == recognizerType){ + foundOpt = opt; + } + + }); + if(!foundOpt) foundOpt = defaultOptionsForEvent(eventName); + else foundOpt = normalizeRecognizerOptions(foundOpt); + + + return [angular.extend(defaults, foundOpt)]; + } + }; + }; + + }); /** * Iterates through each gesture type mapping and creates a directive for @@ -99,35 +267,24 @@ directiveName = directive[0], eventName = directive[1]; - angular.module('hmTouchEvents') - .directive(directiveName, ['$parse', '$window', function ($parse, $window) { + hmTouchEvents.directive(directiveName, ['$parse', '$window', NAME, function ($parse, $window, defaultEvents) { return { - 'restrict' : 'A', - 'link' : function (scope, element, attrs) { - - // Check for Hammer and required functionality - // If no Hammer, maybe bind tap and doubletap to click and dblclick + restrict: 'A', + scope: false, + link: function (scope, element, attrs) { + // Check for Hammer and required functionality. + // error if they arent found as unexpected behaviour otherwise if (!Hammer || !$window.addEventListener) { - if (directiveName === 'hmTap') { - element.bind('click', handler); - } - - if (directiveName === 'hmDoubletap') { - element.bind('dblclick', handler); - } - - return; + throw Error(NAME+": window.Hammer or window.addEventListener not found, can't add event "+directiveName); } var hammer = element.data('hammer'), - managerOpts = angular.fromJson(attrs.hmManagerOptions), - recognizerOpts = angular.fromJson(attrs.hmRecognizerOptions); - + managerOpts = defaultEvents.extendWithDefaultManagerOpts( scope.$eval(attrs.hmManagerOptions) ), + recognizerOpts = defaultEvents.extendWithDefaultRecognizerOpts( eventName, scope.$eval(attrs.hmRecognizerOptions) ); // Check for a manager, make one if needed and destroy it when // the scope is destroyed - if (!hammer) { hammer = new Hammer.Manager(element[0], managerOpts); element.data('hammer', hammer); @@ -136,139 +293,60 @@ }); } - // Instantiate the handler - - var handlerName = attrs[directiveName], - handlerExpr = $parse(handlerName), - handler = function (event) { - var phase = scope.$root.$$phase, - recognizer = hammer.get(event.type); - + // Obtain and wrap our handler function to do a couple of bits for + // us if options provided. + var handlerExpr = $parse(attrs[directiveName]).bind(null,scope); + var handler = function (event) { event.element = element; + var recognizer = hammer.get(event.type); if (recognizer) { if (recognizer.options.preventDefault) { event.preventDefault(); } - if (recognizer.options.stopPropagation) { event.srcEvent.stopPropagation(); } } - if (phase === '$apply' || phase === '$digest') { - callHandler(); - } else { - scope.$apply(callHandler); - } - - function callHandler () { - var fn = handlerExpr(scope, {'$event':event}); - - if (typeof fn === 'function') { - fn.call(scope, event); - } - } + scope.$apply(function(){ + handlerExpr({ '$event': event }); + }); }; - // Setting up the recognizers based on the supplied options - - if (angular.isArray(recognizerOpts)) { - // The recognizer options may be stored in an array. In this - // case, Angular Hammer iterates through the array of options - // trying to find an occurrence of the options.type in the event - // name. If it find the type in the event name, it applies those - // options to the recognizer for events with that name. If it - // does not find the type in the event name it moves on. - - angular.forEach(recognizerOpts, function (options) { - if (directiveName === 'hmCustom') { - eventName = options.event; - } else { - if (!options.type) { - options.type = getRecognizerTypeFromeventName(eventName); - } + // The recognizer options are normalized to an array. This array + // contains whatever events we wish to add (our prior extending + // takes care of that), but we do a couple of specific things + // depending on this directive so that events play nice together. + angular.forEach(recognizerOpts, function (options) { - if (options.event) { - delete options.event; - } - } + if(eventName !== 'custom'){ - if (directiveName === 'hmCustom' || - eventName.indexOf(options.type) > -1) { - setupRecognizerWithOptions( - hammer, - applyManagerOptions(managerOpts, options), - element); + if (eventName === 'doubletap' && hammer.get('tap')) { + options.recognizeWith = 'tap'; } - }); - } else if (angular.isObject(recognizerOpts)) { - // Recognizer options may be stored as an object. In this case, - // Angular Hammer checks to make sure that the options type - // property is found in the event name. If the options are - // designated for this general type of event, Angular Hammer - // applies the options directly to the manager instance for - // this element. - - if (directiveName === 'hmCustom') { - eventName = recognizerOpts.event; - } else { - if (!recognizerOpts.type) { - recognizerOpts.type = getRecognizerTypeFromeventName(eventName); - } - - if (recognizerOpts.event) { - delete recognizerOpts.event; - } - } - - if (directiveName === 'hmCustom' || - eventName.indexOf(recognizerOpts.type) > -1) { - setupRecognizerWithOptions( - hammer, - applyManagerOptions(managerOpts, recognizerOpts), - element); - } - } else if (directiveName !== 'hmCustom') { - // If no options are supplied, or the supplied options do not - // match any of the above conditions, Angular Hammer sets up - // the default options that a manager instantiated using - // Hammer() would have. - - recognizerOpts = { - 'type': getRecognizerTypeFromeventName(eventName) - }; - - if (directiveName === 'hmDoubletap') { - recognizerOpts.event = eventName; - recognizerOpts.taps = 2; - - if (hammer.get('tap')) { - recognizerOpts.recognizeWith = 'tap'; + else if (options.type == "pan" && hammer.get('swipe')) { + options.recognizeWith = 'swipe'; + } + else if (options.type == "pinch" && hammer.get('rotate')) { + options.recognizeWith = 'rotate'; } - } - - if (recognizerOpts.type.indexOf('pan') > -1 && - hammer.get('swipe')) { - recognizerOpts.recognizeWith = 'swipe'; - } - if (recognizerOpts.type.indexOf('pinch') > -1 && - hammer.get('rotate')) { - recognizerOpts.recognizeWith = 'rotate'; } + //add the recognizer with these options: setupRecognizerWithOptions( hammer, - applyManagerOptions(managerOpts, recognizerOpts), - element); - } else { - eventName = null; - } + applyManagerOptions(managerOpts, options), + element + ); - if (eventName) { + //if custom there may be multiple events to apply, which + //we do here. else, we'll only ever add one. hammer.on(eventName, handler); - } + + }); + } }; }]); @@ -285,20 +363,20 @@ * @return {Object} Reference to the new gesture recognizer, if * successful, null otherwise. */ - function addRecognizer (manager, type) { - if (manager === undefined || type === undefined) { return null; } + function addRecognizer (manager, name) { + if (manager === undefined || name === undefined) { return null; } var recognizer; - if (type.indexOf('pan') > -1) { + if (name.indexOf('pan') > -1) { recognizer = new Hammer.Pan(); - } else if (type.indexOf('pinch') > -1) { + } else if (name.indexOf('pinch') > -1) { recognizer = new Hammer.Pinch(); - } else if (type.indexOf('press') > -1) { + } else if (name.indexOf('press') > -1) { recognizer = new Hammer.Press(); - } else if (type.indexOf('rotate') > -1) { + } else if (name.indexOf('rotate') > -1) { recognizer = new Hammer.Rotate(); - } else if (type.indexOf('swipe') > -1) { + } else if (name.indexOf('swipe') > -1) { recognizer = new Hammer.Swipe(); } else { recognizer = new Hammer.Tap(); @@ -341,8 +419,10 @@ return 'rotate'; } else if (eventName.indexOf('swipe') > -1) { return 'swipe'; - } else { + } else if (eventName.indexOf('tap') > -1) { return 'tap'; + } else { + return "custom"; } } @@ -361,22 +441,21 @@ ' recognizer. Values of the passed manager and options: ', manager, options); } - var recognizer = manager.get(options.type); - + var recognizer = manager.get(options._name); if (!recognizer) { - recognizer = addRecognizer(manager, options.type); + recognizer = addRecognizer(manager, options._name); } if (!options.directions) { - if (options.type === 'pan' || options.type === 'swipe') { + if (options._name === 'pan' || options._name === 'swipe') { options.directions = 'DIRECTION_ALL'; - } else if (options.type.indexOf('left') > -1) { + } else if (options._name.indexOf('left') > -1) { options.directions = 'DIRECTION_LEFT'; - } else if (options.type.indexOf('right') > -1) { + } else if (options._name.indexOf('right') > -1) { options.directions = 'DIRECTION_RIGHT'; - } else if (options.type.indexOf('up') > -1) { + } else if (options._name.indexOf('up') > -1) { options.directions = 'DIRECTION_UP'; - } else if (options.type.indexOf('down') > -1) { + } else if (options._name.indexOf('down') > -1) { options.directions = 'DIRECTION_DOWN'; } else { options.directions = ''; diff --git a/angular.hammer.min.js b/angular.hammer.min.js index 5552b6c..89ace5d 100644 --- a/angular.hammer.min.js +++ b/angular.hammer.min.js @@ -1,5 +1,8 @@ // ---- Angular Hammer ---- -// Copyright (c) 2014 Ryan S Mullins +// Copyright (c) 2015 Ryan S Mullins // Licensed under the MIT Software License -!function(a,b,c){"use strict";function d(a,b){if(!a||!b||!b.type)return null;var d;return d=b.type.indexOf("pan")>-1?new c.Pan(b):b.type.indexOf("pinch")>-1?new c.Pinch(b):b.type.indexOf("press")>-1?new c.Press(b):b.type.indexOf("rotate")>-1?new c.Rotate(b):b.type.indexOf("swipe")>-1?new c.Swipe(b):new c.Tap(b),a.add(d),d}function e(a,b){return a&&(b.preventGhosts=a.preventGhosts),b}function f(a){return a.indexOf("pan")>-1?"pan":a.indexOf("pinch")>-1?"pinch":a.indexOf("press")>-1?"press":a.indexOf("rotate")>-1?"rotate":a.indexOf("swipe")>-1?"swipe":"tap"}function g(a,b,c){if(a&&b){var e=a.get(b.type);e||(e=d(a,b)),b.directions||(b.directions="pan"===b.type||"swipe"===b.type?"DIRECTION_ALL":b.type.indexOf("left")>-1?"DIRECTION_LEFT":b.type.indexOf("right")>-1?"DIRECTION_RIGHT":b.type.indexOf("up")>-1?"DIRECTION_UP":b.type.indexOf("down")>-1?"DIRECTION_DOWN":""),b.direction=h(b.directions),e.set(b),b.recognizeWith&&(a.get(b.recognizeWith)||d(a,{type:b.recognizeWith}),e.recognizeWith(a.get(b.recognizeWith))),b.dropRecognizeWith&&a.get(b.dropRecognizeWith)&&e.dropRecognizeWith(a.get(b.dropRecognizeWith)),b.requireFailure&&(a.get(b.requireFailure)||d(a,{type:b.requireFailure}),e.requireFailure(a.get(b.requireFailure))),b.dropRequireFailure&&a.get(b.dropRequireFailure)&&e.dropRequireFailure(a.get(b.dropRequireFailure)),b.preventGhosts&&c&&i(c)}}function h(a){var d=0;return b.forEach(a.split("|"),function(a){c.hasOwnProperty(a)&&(d|=c[a])}),d}function i(b){function c(a){for(var b=0;b-1)&&g(m,e(n,a),k)}):b.isObject(o)?("hmCustom"===h?i=o.event:(o.type||(o.type=f(i)),o.event&&delete o.event),("hmCustom"===h||i.indexOf(o.type)>-1)&&g(m,e(n,o),k)):"hmCustom"!==h?(o={type:f(i)},"hmDoubletap"===h&&(o.event=i,o.taps=2,m.get("tap")&&(o.recognizeWith="tap")),o.type.indexOf("pan")>-1&&m.get("swipe")&&(o.recognizeWith="swipe"),o.type.indexOf("pinch")>-1&&m.get("rotate")&&(o.recognizeWith="rotate"),g(m,e(n,o),k)):i=null,i&&m.on(i,r)}}}])})}(window,window.angular,window.Hammer); +// +// (fairly heavy) modifications by James Wilson +// +!function(a,b,c){"use strict";function d(a,b){if(void 0===a||void 0===b)return null;var d;return d=b.indexOf("pan")>-1?new c.Pan:b.indexOf("pinch")>-1?new c.Pinch:b.indexOf("press")>-1?new c.Press:b.indexOf("rotate")>-1?new c.Rotate:b.indexOf("swipe")>-1?new c.Swipe:new c.Tap,a.add(d),d}function e(a,b){return a&&(b.preventGhosts=a.preventGhosts),b}function f(a){return a.indexOf("pan")>-1?"pan":a.indexOf("pinch")>-1?"pinch":a.indexOf("press")>-1?"press":a.indexOf("rotate")>-1?"rotate":a.indexOf("swipe")>-1?"swipe":a.indexOf("tap")>-1?"tap":"custom"}function g(a,b,c){if(null==a||null==b||null==b.type)return console.error("ERROR: Angular Hammer could not setup the recognizer. Values of the passed manager and options: ",a,b);var e=a.get(b._name);if(e||(e=d(a,b._name)),b.directions||("pan"===b._name||"swipe"===b._name?b.directions="DIRECTION_ALL":b._name.indexOf("left")>-1?b.directions="DIRECTION_LEFT":b._name.indexOf("right")>-1?b.directions="DIRECTION_RIGHT":b._name.indexOf("up")>-1?b.directions="DIRECTION_UP":b._name.indexOf("down")>-1?b.directions="DIRECTION_DOWN":b.directions=""),b.direction=h(b.directions),e.set(b),"string"==typeof b.recognizeWith){var f;null==a.get(b.recognizeWith)&&(f=d(a,b.recognizeWith)),null!=f&&e.recognizeWith(f)}if("string"==typeof b.dropRecognizeWith&&null!=a.get(b.dropRecognizeWith)&&e.dropRecognizeWith(a.get(b.dropRecognizeWith)),"string"==typeof b.requireFailure){var g;null==a.get(b.requireFailure)&&(g=d(a,{type:b.requireFailure})),null!=g&&e.requireFailure(g)}"string"==typeof b.dropRequireFailure&&null!=a.get(b.dropRequireFailure)&&e.dropRequireFailure(a.get(b.dropRequireFailure)),b.preventGhosts===!0&&null!=c&&i(c)}function h(a){var d=0;return b.forEach(a.split("|"),function(a){c.hasOwnProperty(a)&&(d|=c[a])}),d}function i(b){function c(a){for(var b=0;b" + "Ryan S Mullins ", + "James Wilson " ], - "homepage": "https://github.com/RyanMullins/angular-hammer", + "homepage": "https://github.com/jsdw/angular-hammer", "main": "angular.hammer.js", "license": "MIT", "keywords": [ diff --git a/doc/angular.hammer.js.html b/doc/angular.hammer.js.html index 2bccc1b..05354e3 100644 --- a/doc/angular.hammer.js.html +++ b/doc/angular.hammer.js.html @@ -27,8 +27,11 @@

Source: angular.hammer.js

// ---- Angular Hammer ----
 
-// Copyright (c) 2014 Ryan S Mullins <ryan@ryanmullins.org>
+// Copyright (c) 2015 Ryan S Mullins <ryan@ryanmullins.org>
 // Licensed under the MIT Software License
+//
+// (fairly heavy) modifications by James Wilson <me@unbui.lt>
+//
 
 (function (window, angular, Hammer) {
   'use strict';
@@ -36,31 +39,10 @@ 

Source: angular.hammer.js

// Checking to make sure Hammer and Angular are defined if (typeof angular === 'undefined') { - if (typeof require !== 'undefined' && require) { - try { - angular = require('angular'); - } catch (e) { - return console.log('ERROR: Angular Hammer could not require() a reference to angular'); - } - } else if (typeof window.angular !== 'undefined') { - angular = window.angular; - } else { - return console.log('ERROR: Angular Hammer could not find or require() a reference to angular'); - } + throw Error("angular-hammer: AngularJS (window.angular) is undefined but is necessary."); } - if (typeof Hammer === 'undefined') { - if (typeof require !== 'undefined' && require) { - try { - Hammer = require('hammerjs'); - } catch (e) { - return console.log('ERROR: Angular Hammer could not require() a reference to Hammer'); - } - } else if (typeof window.Hammer !== 'undefined') { - Hammer = window.Hammer; - } else { - return console.log('ERROR: Angular Hammer could not find or require() a reference to Hammer'); - } + throw Error("angular-hammer: HammerJS (window.Hammer) is undefined but is necessary."); } /** @@ -112,7 +94,193 @@

Source: angular.hammer.js

* @requires angular * @requires hammer */ - angular.module('hmTouchEvents', []); + var NAME = 'hmTouchEvents'; + var hmTouchEvents = angular.module('hmTouchEvents', []); + + /** + * Provides a common interface for configuring global manager and recognizer + * options. Allows things like tap duration etc to be defaulted globally and + * overridden on a per-directive basis as needed. + * + * @return {Object} functions to add manager and recognizer options. + */ + hmTouchEvents.provider(NAME, function(){ + + var self = this; + var defaultRecognizerOpts = false; + var recognizerOptsHash = {}; + var managerOpts = {}; + + // + // In order to use the Hamme rpresets provided, we need + // to map the recognizer fn to some name: + // + var recognizerFnToName = {}; + recognizerFnToName[ Hammer.Tap.toString() ] = "tap"; + recognizerFnToName[ Hammer.Pan.toString() ] = "pan"; + recognizerFnToName[ Hammer.Pinch.toString() ] = "pinch"; + recognizerFnToName[ Hammer.Press.toString() ] = "press"; + recognizerFnToName[ Hammer.Rotate.toString() ] = "rotate"; + recognizerFnToName[ Hammer.Swipe.toString() ] = "swipe"; + + // + // normalize opts, setting its name as it should be keyed by + // and any must-have options. currently only doubletap is treated + // specially. each _name leads to a new recognizer. + // + function normalizeRecognizerOptions(opts){ + opts = angular.copy(opts); + + if(opts.event){ + + if(opts.event == "doubletap"){ + opts.type = "tap"; + if(!opts.taps) opts.taps = 2; + opts._name = "doubletap"; + } else { + opts._name = false; + } + + } else { + opts._name = opts.type || false; + } + + return opts; + } + // + // create default opts for some eventName. + // again, treat doubletap specially. + // + function defaultOptionsForEvent(eventName){ + if(eventName == "custom"){ + throw Error(NAME+"Provider: no defaults exist for custom events"); + } + var ty = getRecognizerTypeFromeventName(eventName); + return normalizeRecognizerOptions( + eventName == "doubletap" + ? {type:ty, event:"doubletap"} + : {type:ty} + ); + } + + // + // Make use of presets from Hammer.defaults.preset array + // in angular-hammer events. + // + self.applyHammerPresets = function(){ + var hammerPresets = Hammer.defaults.preset; + + //add every preset that, when normalized, has a _name. + //this precludes most custom events. + angular.forEach(hammerPresets, function(presetArr){ + + var data = presetArr[1]; + if(!data.type) data.type = recognizerFnToName[presetArr[0]]; + data = normalizeRecognizerOptions(data); + if(!data._name) return; + recognizerOptsHash[data._name] = data; + }); + } + + // + // Add a manager option (key/val to extend or object to set all): + // + self.addManagerOption = function(name, val){ + if(typeof name == "object"){ + angular.extend(managerOpts, name); + } + else { + managerOpts[name] = val; + } + } + + // + // Add a recognizer option: + // + self.addRecognizerOption = function(val){ + if(Array.isArray(val)){ + for(var i = 0; i < val.length; i++) self.addRecognizerOption(val[i]); + return; + } + if(typeof val !== "object"){ + throw Error(NAME+"Provider: addRecognizerOption: should be object or array of objects"); + } + val = normalizeRecognizerOptions(val); + + //hash by name if present, else if no event name, + //set as defaults. + if(val._name){ + recognizerOptsHash[val.type] = val; + } else if(!val.event){ + defaultRecognizerOpts = val; + } + + } + + //provide an interface to this that the hm-* directives use + //to extend their recognizer/manager opts. + self.$get = function(){ + return { + extendWithDefaultManagerOpts: function(opts){ + if(typeof opts != "object"){ + opts = {}; + } else { + opts = angular.copy(opts); + } + for(var name in managerOpts) { + if(!opts[name]) opts[name] = angular.copy(managerOpts[name]); + } + return opts; + }, + extendWithDefaultRecognizerOpts: function(eventName, opts){ + if(typeof opts !== "object"){ + opts = []; + } + if(!Array.isArray(opts)){ + opts = [opts]; + } + + //dont apply anything if this is custom event + //(beyond normalizing opts to an array): + if(eventName == "custom") return opts; + + var recognizerType = getRecognizerTypeFromeventName(eventName); + var specificOpts = recognizerOptsHash[eventName] || recognizerOptsHash[recognizerType]; + + //get the last opt provided that matches the type or eventName + //that we have. normalizing removes any eventnames we dont care about + //(everything but doubletap at the moment). + var foundOpt; + var isExactMatch = false; + var defaults = angular.extend({}, defaultRecognizerOpts || {}, specificOpts || {}); + opts.forEach(function(opt){ + + if(!opt.event && !opt.type){ + return angular.extend(defaults, opt); + } + if(isExactMatch){ + return; + } + + //more specific wins over less specific. + if(opt.event == eventName){ + foundOpt = opt; + isExactMatch = true; + } else if(!opt.event && opt.type == recognizerType){ + foundOpt = opt; + } + + }); + if(!foundOpt) foundOpt = defaultOptionsForEvent(eventName); + else foundOpt = normalizeRecognizerOptions(foundOpt); + + + return [angular.extend(defaults, foundOpt)]; + } + }; + }; + + }); /** * Iterates through each gesture type mapping and creates a directive for @@ -126,35 +294,24 @@

Source: angular.hammer.js

directiveName = directive[0], eventName = directive[1]; - angular.module('hmTouchEvents') - .directive(directiveName, ['$parse', '$window', function ($parse, $window) { + hmTouchEvents.directive(directiveName, ['$parse', '$window', NAME, function ($parse, $window, defaultEvents) { return { - 'restrict' : 'A', - 'link' : function (scope, element, attrs) { - - // Check for Hammer and required functionality - // If no Hammer, maybe bind tap and doubletap to click and dblclick + restrict: 'A', + scope: false, + link: function (scope, element, attrs) { + // Check for Hammer and required functionality. + // error if they arent found as unexpected behaviour otherwise if (!Hammer || !$window.addEventListener) { - if (directiveName === 'hmTap') { - element.bind('click', handler); - } - - if (directiveName === 'hmDoubletap') { - element.bind('dblclick', handler); - } - - return; + throw Error(NAME+": window.Hammer or window.addEventListener not found, can't add event "+directiveName); } var hammer = element.data('hammer'), - managerOpts = angular.fromJson(attrs.hmManagerOptions), - recognizerOpts = angular.fromJson(attrs.hmRecognizerOptions); - + managerOpts = defaultEvents.extendWithDefaultManagerOpts( scope.$eval(attrs.hmManagerOptions) ), + recognizerOpts = defaultEvents.extendWithDefaultRecognizerOpts( eventName, scope.$eval(attrs.hmRecognizerOptions) ); // Check for a manager, make one if needed and destroy it when // the scope is destroyed - if (!hammer) { hammer = new Hammer.Manager(element[0], managerOpts); element.data('hammer', hammer); @@ -163,139 +320,60 @@

Source: angular.hammer.js

}); } - // Instantiate the handler - - var handlerName = attrs[directiveName], - handlerExpr = $parse(handlerName), - handler = function (event) { - var phase = scope.$root.$$phase, - recognizer = hammer.get(event.type); - + // Obtain and wrap our handler function to do a couple of bits for + // us if options provided. + var handlerExpr = $parse(attrs[directiveName]).bind(null,scope); + var handler = function (event) { event.element = element; + var recognizer = hammer.get(event.type); if (recognizer) { if (recognizer.options.preventDefault) { event.preventDefault(); } - if (recognizer.options.stopPropagation) { event.srcEvent.stopPropagation(); } } - if (phase === '$apply' || phase === '$digest') { - callHandler(); - } else { - scope.$apply(callHandler); - } - - function callHandler () { - var fn = handlerExpr(scope, {'$event':event}); - - if (fn) { - fn.call(scope, event); - } - } + scope.$apply(function(){ + handlerExpr({ '$event': event }); + }); }; - // Setting up the recognizers based on the supplied options - - if (angular.isArray(recognizerOpts)) { - // The recognizer options may be stored in an array. In this - // case, Angular Hammer iterates through the array of options - // trying to find an occurrence of the options.type in the event - // name. If it find the type in the event name, it applies those - // options to the recognizer for events with that name. If it - // does not find the type in the event name it moves on. - - angular.forEach(recognizerOpts, function (options) { - if (directiveName === 'hmCustom') { - eventName = options.event; - } else { - if (!options.type) { - options.type = getRecognizerTypeFromeventName(eventName); - } + // The recognizer options are normalized to an array. This array + // contains whatever events we wish to add (our prior extending + // takes care of that), but we do a couple of specific things + // depending on this directive so that events play nice together. + angular.forEach(recognizerOpts, function (options) { - if (options.event) { - delete options.event; - } - } + if(eventName !== 'custom'){ - if (directiveName === 'hmCustom' || - eventName.indexOf(options.type) > -1) { - setupRecognizerWithOptions( - hammer, - applyManagerOptions(managerOpts, options), - element); + if (eventName === 'doubletap' && hammer.get('tap')) { + options.recognizeWith = 'tap'; } - }); - } else if (angular.isObject(recognizerOpts)) { - // Recognizer options may be stored as an object. In this case, - // Angular Hammer checks to make sure that the options type - // property is found in the event name. If the options are - // designated for this general type of event, Angular Hammer - // applies the options directly to the manager instance for - // this element. - - if (directiveName === 'hmCustom') { - eventName = recognizerOpts.event; - } else { - if (!recognizerOpts.type) { - recognizerOpts.type = getRecognizerTypeFromeventName(eventName); - } - - if (recognizerOpts.event) { - delete recognizerOpts.event; - } - } - - if (directiveName === 'hmCustom' || - eventName.indexOf(recognizerOpts.type) > -1) { - setupRecognizerWithOptions( - hammer, - applyManagerOptions(managerOpts, recognizerOpts), - element); - } - } else if (directiveName !== 'hmCustom') { - // If no options are supplied, or the supplied options do not - // match any of the above conditions, Angular Hammer sets up - // the default options that a manager instantiated using - // Hammer() would have. - - recognizerOpts = { - 'type': getRecognizerTypeFromeventName(eventName) - }; - - if (directiveName === 'hmDoubletap') { - recognizerOpts.event = eventName; - recognizerOpts.taps = 2; - - if (hammer.get('tap')) { - recognizerOpts.recognizeWith = 'tap'; + else if (options.type == "pan" && hammer.get('swipe')) { + options.recognizeWith = 'swipe'; + } + else if (options.type == "pinch" && hammer.get('rotate')) { + options.recognizeWith = 'rotate'; } - } - - if (recognizerOpts.type.indexOf('pan') > -1 && - hammer.get('swipe')) { - recognizerOpts.recognizeWith = 'swipe'; - } - if (recognizerOpts.type.indexOf('pinch') > -1 && - hammer.get('rotate')) { - recognizerOpts.recognizeWith = 'rotate'; } + //add the recognizer with these options: setupRecognizerWithOptions( hammer, - applyManagerOptions(managerOpts, recognizerOpts), - element); - } else { - eventName = null; - } + applyManagerOptions(managerOpts, options), + element + ); - if (eventName) { + //if custom there may be multiple events to apply, which + //we do here. else, we'll only ever add one. hammer.on(eventName, handler); - } + + }); + } }; }]); @@ -308,27 +386,27 @@

Source: angular.hammer.js

* add is determined by the value of the options.type property. * * @param {Object} manager Hammer.js manager object assigned to an element - * @param {Object} options Options that define the recognizer to add - * @return {Object} Reference to the new gesture recognizer, if successful, - * null otherwise. + * @param {String} type Options that define the recognizer to add + * @return {Object} Reference to the new gesture recognizer, if + * successful, null otherwise. */ - function addRecognizer (manager, options) { - if (!manager || !options || !options.type) { return null; } + function addRecognizer (manager, name) { + if (manager === undefined || name === undefined) { return null; } var recognizer; - if (options.type.indexOf('pan') > -1) { - recognizer = new Hammer.Pan(options); - } else if (options.type.indexOf('pinch') > -1) { - recognizer = new Hammer.Pinch(options); - } else if (options.type.indexOf('press') > -1) { - recognizer = new Hammer.Press(options); - } else if (options.type.indexOf('rotate') > -1) { - recognizer = new Hammer.Rotate(options); - } else if (options.type.indexOf('swipe') > -1) { - recognizer = new Hammer.Swipe(options); + if (name.indexOf('pan') > -1) { + recognizer = new Hammer.Pan(); + } else if (name.indexOf('pinch') > -1) { + recognizer = new Hammer.Pinch(); + } else if (name.indexOf('press') > -1) { + recognizer = new Hammer.Press(); + } else if (name.indexOf('rotate') > -1) { + recognizer = new Hammer.Rotate(); + } else if (name.indexOf('swipe') > -1) { + recognizer = new Hammer.Swipe(); } else { - recognizer = new Hammer.Tap(options); + recognizer = new Hammer.Tap(); } manager.add(recognizer); @@ -368,8 +446,10 @@

Source: angular.hammer.js

return 'rotate'; } else if (eventName.indexOf('swipe') > -1) { return 'swipe'; - } else { + } else if (eventName.indexOf('tap') > -1) { return 'tap'; + } else { + return "custom"; } } @@ -383,24 +463,26 @@

Source: angular.hammer.js

* @return None */ function setupRecognizerWithOptions (manager, options, element) { - if (!manager || !options) { return; } - - var recognizer = manager.get(options.type); + if (manager == null || options == null || options.type == null) { + return console.error('ERROR: Angular Hammer could not setup the' + + ' recognizer. Values of the passed manager and options: ', manager, options); + } + var recognizer = manager.get(options._name); if (!recognizer) { - recognizer = addRecognizer(manager, options); + recognizer = addRecognizer(manager, options._name); } if (!options.directions) { - if (options.type === 'pan' || options.type === 'swipe') { + if (options._name === 'pan' || options._name === 'swipe') { options.directions = 'DIRECTION_ALL'; - } else if (options.type.indexOf('left') > -1) { + } else if (options._name.indexOf('left') > -1) { options.directions = 'DIRECTION_LEFT'; - } else if (options.type.indexOf('right') > -1) { + } else if (options._name.indexOf('right') > -1) { options.directions = 'DIRECTION_RIGHT'; - } else if (options.type.indexOf('up') > -1) { + } else if (options._name.indexOf('up') > -1) { options.directions = 'DIRECTION_UP'; - } else if (options.type.indexOf('down') > -1) { + } else if (options._name.indexOf('down') > -1) { options.directions = 'DIRECTION_DOWN'; } else { options.directions = ''; @@ -410,31 +492,41 @@

Source: angular.hammer.js

options.direction = parseDirections(options.directions); recognizer.set(options); - if (options.recognizeWith) { - if (!manager.get(options.recognizeWith)){ - addRecognizer(manager, {type:options.recognizeWith}); + if (typeof options.recognizeWith === 'string') { + var recognizeWithRecognizer; + + if (manager.get(options.recognizeWith) == null){ + recognizeWithRecognizer = addRecognizer(manager, options.recognizeWith); } - recognizer.recognizeWith(manager.get(options.recognizeWith)); + if (recognizeWithRecognizer != null) { + recognizer.recognizeWith(recognizeWithRecognizer); + } } - if (options.dropRecognizeWith && manager.get(options.dropRecognizeWith)) { + if (typeof options.dropRecognizeWith === 'string' && + manager.get(options.dropRecognizeWith) != null) { recognizer.dropRecognizeWith(manager.get(options.dropRecognizeWith)); } - if (options.requireFailure) { - if (!manager.get(options.requireFailure)){ - addRecognizer(manager, {type:options.requireFailure}); + if (typeof options.requireFailure === 'string') { + var requireFailureRecognizer; + + if (manager.get(options.requireFailure) == null){ + requireFailureRecognizer = addRecognizer(manager, {type:options.requireFailure}); } - recognizer.requireFailure(manager.get(options.requireFailure)); + if (requireFailureRecognizer != null) { + recognizer.requireFailure(requireFailureRecognizer); + } } - if (options.dropRequireFailure && manager.get(options.dropRequireFailure)) { + if (typeof options.dropRequireFailure === 'string' && + manager.get(options.dropRequireFailure) != null) { recognizer.dropRequireFailure(manager.get(options.dropRequireFailure)); } - if (options.preventGhosts && element) { + if (options.preventGhosts === true && element != null) { preventGhosts(element); } } @@ -550,7 +642,7 @@

Index

Modules

  • diff --git a/doc/index.html b/doc/index.html index e38f450..addea13 100644 --- a/doc/index.html +++ b/doc/index.html @@ -54,7 +54,7 @@

    Index

    Modules

    • diff --git a/doc/module-hmTouchEvents.html b/doc/module-hmTouchEvents.html index c66fc0b..5490c70 100644 --- a/doc/module-hmTouchEvents.html +++ b/doc/module-hmTouchEvents.html @@ -65,7 +65,7 @@

      Source:
      @@ -122,7 +122,7 @@

      Index

      Modules

      • diff --git a/package.json b/package.json index 7d56b08..4da65db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-hammer", - "version": "2.1.10", + "version": "2.2-jsdw", "description": "Hammer.js support for Angular.js applications", "main": "angular.hammer.js", "directories": { @@ -20,10 +20,10 @@ "touch", "gesture" ], - "author": "Ryan S Mullins", + "author": "Ryan S Mullins & James Wilson", "license": "MIT", "bugs": { - "url": "https://github.com/RyanMullins/angular-hammer/issues" + "url": "https://github.com/jsdw/angular-hammer/issues" }, "homepage": "http://ryanmullins.github.io/angular-hammer/", "devDependencies": {