From fe632026470da3aee458fc837d516d64cdefe350 Mon Sep 17 00:00:00 2001 From: gcca Date: Sat, 18 Feb 2017 10:03:44 -0500 Subject: [PATCH] feat(uiSref): Bind ui-sref to DOM events --- src/directives/stateDirectives.ts | 41 ++++++-- test/stateDirectivesSpec.js | 160 ++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 11 deletions(-) diff --git a/src/directives/stateDirectives.ts b/src/directives/stateDirectives.ts index f58f2247b..89a4af07f 100644 --- a/src/directives/stateDirectives.ts +++ b/src/directives/stateDirectives.ts @@ -12,7 +12,7 @@ import { ng as angular } from "../angular"; import { IAugmentedJQuery, ITimeoutService, IScope, IInterpolateService } from "angular"; import { - Obj, extend, forEach, tail, isString, isObject, parse, noop, unnestR, identity, uniqR, inArray, removeFrom, + Obj, extend, forEach, tail, isString, isObject, isArray, parse, noop, unnestR, identity, uniqR, inArray, removeFrom, RawParams, PathNode, StateOrName, StateService, StateDeclaration, UIRouter } from "ui-router-core"; import { UIViewData } from "./viewDirective"; @@ -96,6 +96,31 @@ function defaultOpts(el: IAugmentedJQuery, $state: StateService) { }; } +/** @hidden */ +function bindEvents(element: IAugmentedJQuery, scope: IScope, hookFn: (e: JQueryMouseEventObject) => void, uiStateOpts: any): void { + let events; + + if (uiStateOpts) { + events = uiStateOpts.event; + } + + if (!isArray(events)) { + events = ['click']; + } + + let on = element.on ? 'on' : 'bind'; + for (let event of events) { + element[on](event, hookFn); + } + + scope.$on('$destroy', function() { + let off = element.off ? 'off' : 'unbind'; + for (let event of events) { + element[off](event, hookFn); + } + }); +} + /** * `ui-sref`: A directive for linking to a state * @@ -157,7 +182,7 @@ function defaultOpts(el: IAugmentedJQuery, $state: StateService) { * * ### Transition Options * You can specify [[TransitionOptions]] to pass to [[StateService.go]] by using the `ui-sref-opts` attribute. - * Options are restricted to `location`, `inherit`, and `reload`. + * Options are restricted to `location`, `inherit`, `reload`, and `event`. * * #### Example: * ```html @@ -262,10 +287,7 @@ uiSref = ['$uiRouter', '$timeout', if (!type.clickable) return; hookFn = clickHook(element, $state, $timeout, type, getDef); - element[element.on ? 'on' : 'bind']("click", hookFn); - scope.$on('$destroy', function () { - element[element.off ? 'off' : 'unbind']("click", hookFn); - }); + bindEvents(element, scope, hookFn, rawDef.uiStateOpts); } }; }]; @@ -318,7 +340,7 @@ uiSref = ['$uiRouter', '$timeout', * * ### Transition Options * You can specify [[TransitionOptions]] to pass to [[StateService.go]] by using the `ui-state-opts` attribute. - * Options are restricted to `location`, `inherit`, and `reload`. + * Options are restricted to `location`, `inherit`, `reload`, and `event`. * The value of the `ui-state-opts` is `$watch`ed and evaluated as an expression. * * #### Example: @@ -390,10 +412,7 @@ uiState = ['$uiRouter', '$timeout', if (!type.clickable) return; hookFn = clickHook(element, $state, $timeout, type, getDef); - element[element.on ? 'on' : 'bind']("click", hookFn); - scope.$on('$destroy', function () { - element[element.off ? 'off' : 'unbind']("click", hookFn); - }); + bindEvents(element, scope, hookFn, rawDef.uiStateOpts); } }; }]; diff --git a/test/stateDirectivesSpec.js b/test/stateDirectivesSpec.js index 5541578bd..2f96d992c 100644 --- a/test/stateDirectivesSpec.js +++ b/test/stateDirectivesSpec.js @@ -72,6 +72,18 @@ describe('uiStateRef', function() { el[0].dispatchEvent(e); } + function triggerHTMLEvent(name) { + var event = document.createEvent('HTMLEvents'); + event.initEvent(name, false, true); + el[0].dispatchEvent(event); + } + + function triggerMouseEvent(name) { + var event = document.createEvent('MouseEvents'); + event.initEvent(name, true, true); + el[0].dispatchEvent(event); + } + describe('links with promises', function() { it('should update the href when promises on parameters change before scope is applied', inject(function($rootScope, $compile, $q) { @@ -523,6 +535,84 @@ describe('uiStateRef', function() { expect(transitionOptions.reload).toEqual(true); expect(transitionOptions.absolute).toBeUndefined(); })); + + describe('option event', function() { + it('should bind click event by default', inject(function($compile, $state, $timeout) { + expect($state.current.name).toBe('top'); + + el = angular.element(''); + + scope.state = 'contacts'; + $compile(el)(scope); + scope.$digest(); + + triggerClick(el); + $timeout.flush(); + + expect($state.current.name).toBe('contacts'); + })); + + it('should bind single HTML events', inject(function($compile, $state, $timeout) { + expect($state.current.name).toEqual('top'); + + el = angular.element(''); + + scope.state = 'contacts'; + $compile(el)(scope); + scope.$digest(); + + triggerHTMLEvent('change'); + $timeout.flush(); + + expect($state.current.name).toEqual('contacts'); + })); + + it('should bind multiple HTML events', inject(function($compile, $state, $timeout) { + expect($state.current.name).toEqual('top'); + + el = angular.element(''); + + scope.state = 'contacts'; + $compile(el)(scope); + scope.$digest(); + + triggerHTMLEvent('change'); + $timeout.flush(); + expect($state.current.name).toEqual('contacts'); + + $state.go('top'); + scope.$digest(); + + expect($state.current.name).toEqual('top'); + + triggerHTMLEvent('blur'); + $timeout.flush(); + expect($state.current.name).toEqual('contacts'); + })); + + it('should bind multiple Mouse events', inject(function($compile, $state, $timeout) { + expect($state.current.name).toEqual('top'); + + el = angular.element(''); + + scope.state = 'contacts'; + $compile(el)(scope); + scope.$digest(); + + triggerMouseEvent('mouseover'); + $timeout.flush(); + expect($state.current.name).toEqual('contacts'); + + $state.go('top'); + scope.$digest(); + + expect($state.current.name).toEqual('top'); + + triggerMouseEvent('mousedown'); + $timeout.flush(); + expect($state.current.name).toEqual('contacts'); + })); + }); }); describe('forms', function() { @@ -597,6 +687,76 @@ describe('uiStateRef', function() { expect($state.$current.name).toBe("contacts"); })); }); + + describe('option event', function() { + it('should bind click event by default', inject(function($rootScope, $compile, $state, $timeout) { + el = angular.element(''); + $compile(el)($rootScope); + $rootScope.$digest(); + + expect($state.current.name).toEqual('top'); + + triggerClick(el); + $timeout.flush(); + + expect($state.current.name).toEqual('contacts'); + })); + + it('should bind single HTML events', inject(function($rootScope, $compile, $state, $timeout) { + el = angular.element(''); + $compile(el)($rootScope); + $rootScope.$digest(); + + expect($state.current.name).toEqual('top'); + + triggerHTMLEvent('change'); + $timeout.flush(); + + expect($state.current.name).toEqual('contacts'); + })); + + it('should bind multiple HTML events', inject(function($rootScope, $compile, $state, $timeout) { + el = angular.element(''); + $compile(el)($rootScope); + $rootScope.$digest(); + + expect($state.current.name).toEqual('top'); + + triggerHTMLEvent('change'); + $timeout.flush(); + expect($state.current.name).toEqual('contacts'); + + $state.go('top'); + $rootScope.$digest(); + + expect($state.current.name).toEqual('top'); + + triggerHTMLEvent('blur'); + $timeout.flush(); + expect($state.current.name).toEqual('contacts'); + })); + + it('should bind multiple Mouse events', inject(function($rootScope, $compile, $state, $timeout) { + el = angular.element(''); + $compile(el)($rootScope); + $rootScope.$digest(); + + expect($state.current.name).toEqual('top'); + + triggerMouseEvent('mouseover'); + $timeout.flush(); + expect($state.current.name).toEqual('contacts'); + + $state.go('top'); + $rootScope.$digest(); + + expect($state.current.name).toEqual('top'); + + triggerMouseEvent('mousedown'); + $timeout.flush(); + expect($state.current.name).toEqual('contacts'); + })); + }); }); describe('uiSrefActive', function() {