From dac5381a32cd58645ef6032ba73e8e258a0b1230 Mon Sep 17 00:00:00 2001 From: Cyrille Bourgois Date: Tue, 9 Apr 2019 13:38:14 +0200 Subject: [PATCH] feat(oui-popover): add open, on-open and on-close bindings (#385) * allow to control popover open state with `open` attribute * add `on-open` and `on-close` triggers --- packages/oui-popover/README.md | 67 ++++++++++++++++--- packages/oui-popover/src/index.spec.js | 57 ++++++++++++++++ .../oui-popover/src/popover.controller.js | 37 ++++++++-- packages/oui-popover/src/popover.directive.js | 5 +- 4 files changed, 148 insertions(+), 18 deletions(-) diff --git a/packages/oui-popover/README.md b/packages/oui-popover/README.md index 90712e60..82aee11b 100644 --- a/packages/oui-popover/README.md +++ b/packages/oui-popover/README.md @@ -7,8 +7,8 @@ ### Using value of `oui-popover` attribute ```html:preview - @@ -17,14 +17,38 @@ ### Using value of `title` attribute ```html:preview - ``` +### Using `open` attribute + + + Use oui-popover-open to control the open state of the popover.
+ No event handler will be registered from the trigger element. +
+ +```html:preview +

+ + This is an awesome text + +

+ +``` + ### Using a template with `oui-popover-template` attribute @@ -37,7 +61,7 @@ ng-init="$ctrl.awesome = 'awesome'" ng-model="$ctrl.awesome"> - @@ -117,19 +141,40 @@ ```html:preview ``` +### Using `on-open` & `on-close` + +```html:preview +
+

onOpen counter: {{$ctrl.numOpen }}

+

onClose counter: {{$ctrl.numClose }}

+
+ + +``` + ## API | Attribute | Type | Binding | One-time Binding | Values | Default | Description | ---- | ---- | ---- | ---- | ---- | ---- | ---- | `oui-popover` | string | @ | no | n/a | `title` attribute | popover content +| `oui-popover-open` | boolean | { trigger.triggerHandler("click"); expect(trigger.attr("aria-expanded")).toBe("false"); }); + + it("should open the popover if specified", () => { + const component = testUtils.compileTemplate( + `
+ +
`, { + open: true + } + ); + + $timeout.flush(); + + const trigger = angular.element(component[0].querySelector(".trigger")); + expect(trigger.attr("aria-expanded")).toBe("true"); + + component.scope().$ctrl.open = false; + component.scope().$apply(); + + expect(trigger.attr("aria-expanded")).toBe("false"); + }); + + it("should not register event handler on trigger element if open is specified", () => { + const component = testUtils.compileTemplate('
'); + + $timeout.flush(); + + const trigger = angular.element(component[0].querySelector(".trigger")).triggerHandler("click"); + const popover = trigger.next(); + + expect(popover.attr("x-placement")).toBe(undefined); + }); + + it("should trigger on-open and on-close", () => { + const openSpy = jasmine.createSpy("open"); + const closeSpy = jasmine.createSpy("close"); + const component = testUtils.compileTemplate( + `
+ +
`, { + open: openSpy, + close: closeSpy + } + ); + + $timeout.flush(); + angular.element(component[0].querySelector(".trigger")).triggerHandler("click"); + $timeout.flush(); + + expect(openSpy).toHaveBeenCalled(); + expect(openSpy.calls.count()).toEqual(1); + + angular.element(component[0].querySelector(".trigger")).triggerHandler("click"); + $timeout.flush(); + + expect(closeSpy).toHaveBeenCalled(); + expect(closeSpy.calls.count()).toEqual(1); + }); }); describe("Deprecated support", () => { diff --git a/packages/oui-popover/src/popover.controller.js b/packages/oui-popover/src/popover.controller.js index 68c32b06..bbcfcf72 100644 --- a/packages/oui-popover/src/popover.controller.js +++ b/packages/oui-popover/src/popover.controller.js @@ -31,8 +31,23 @@ export default class PopoverController { } $postLink () { - this.setPopover(); - this.setTrigger(); + this.setPopover() + .then(() => this.setTrigger()) + .then(() => { + if (this.open) { + this.openPopover(); + } + }); + } + + $onChanges (changes) { + if (angular.isDefined(changes.open) && this.triggerElement) { + if (changes.open.currentValue) { + this.openPopover(); + } else { + this.closePopover(); + } + } } $onDestroy () { @@ -40,7 +55,7 @@ export default class PopoverController { } setPopover () { - this.$timeout(() => { + return this.$timeout(() => { // Deprecated: Support for component `oui-popover-content` if (this.isComponent) { this.popperElement = this.$element[0].querySelector(".oui-popover"); @@ -67,7 +82,7 @@ export default class PopoverController { } setTrigger () { - this.$timeout(() => { + return this.$timeout(() => { // Deprecated: Support for component `oui-popover-trigger` if (this.isComponent) { this.triggerElement = this.$element[0].querySelector(".oui-popover__trigger"); @@ -84,8 +99,12 @@ export default class PopoverController { .attr({ "aria-haspopup": true, "aria-expanded": false - }) - .on("click", () => this.onTriggerClick()); + }); + + if (angular.isUndefined(this.$attrs.ouiPopoverOpen)) { + this.$triggerElement + .on("click", () => this.onTriggerClick()); + } }); } @@ -119,6 +138,9 @@ export default class PopoverController { // Support for attribute `oui-popover` this.$element.attr("aria-expanded", true); + + // force the digest because the popover is outside the angular digest loop + this.$timeout(() => this.onOpen(), 0); } closePopover () { @@ -134,6 +156,9 @@ export default class PopoverController { // Support for attribute `oui-popover` this.$element.attr("aria-expanded", false); + + // force the digest because the popover is outside the angular digest loop + this.$timeout(() => this.onClose(), 0); } createPopper () { diff --git a/packages/oui-popover/src/popover.directive.js b/packages/oui-popover/src/popover.directive.js index f18fc57e..0d89c53a 100644 --- a/packages/oui-popover/src/popover.directive.js +++ b/packages/oui-popover/src/popover.directive.js @@ -10,7 +10,10 @@ export default () => { title: "@?", placement: "@?ouiPopoverPlacement", scope: "