Skip to content
This repository was archived by the owner on Aug 7, 2020. It is now read-only.

Commit dac5381

Browse files
cbourgoisAxelPeter
authored andcommitted
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
1 parent 62913df commit dac5381

File tree

4 files changed

+148
-18
lines changed

4 files changed

+148
-18
lines changed

packages/oui-popover/README.md

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
### Using value of `oui-popover` attribute
88

99
```html:preview
10-
<button type="button"
11-
class="oui-button oui-button_primary"
10+
<button type="button"
11+
class="oui-button oui-button_primary"
1212
oui-popover="This is an awesome popover content.">
1313
Click to toggle popover
1414
</button>
@@ -17,14 +17,38 @@
1717
### Using value of `title` attribute
1818

1919
```html:preview
20-
<button type="button"
21-
class="oui-button oui-button_primary"
22-
title="This is an awesome popover content."
20+
<button type="button"
21+
class="oui-button oui-button_primary"
22+
title="This is an awesome popover content."
2323
oui-popover>
2424
Click to toggle popover
2525
</button>
2626
```
2727

28+
### Using `open` attribute
29+
30+
<oui-message type="info">
31+
Use <code class="oui-doc-codespan">oui-popover-open</code> to control the open state of the popover.<br />
32+
No event handler will be registered from the trigger element.
33+
</oui-message>
34+
35+
```html:preview
36+
<p>
37+
<span title="This is an awesome popover content."
38+
ng-init="$ctrl.isOpen = true"
39+
oui-popover
40+
oui-popover-open="$ctrl.isOpen"
41+
oui-popover-on-close="$ctrl.isOpen = false">
42+
This is an awesome text
43+
</span>
44+
</p>
45+
<button type="button"
46+
class="oui-button oui-button_primary"
47+
ng-click="$ctrl.isOpen = !$ctrl.isOpen">
48+
Click to toggle popover
49+
</button>
50+
```
51+
2852
### Using a template with `oui-popover-template` attribute
2953

3054
<oui-message type="warning">
@@ -37,7 +61,7 @@
3761
ng-init="$ctrl.awesome = 'awesome'"
3862
ng-model="$ctrl.awesome">
3963
<button type="button"
40-
class="oui-button oui-button_primary"
64+
class="oui-button oui-button_primary"
4165
oui-popover
4266
oui-popover-scope="$ctrl"
4367
oui-popover-template="popover.html">
@@ -94,14 +118,14 @@
94118
### Alignments
95119

96120
```html:preview
97-
<button type="button" class="oui-button oui-button_primary"
98-
oui-popover="This is an awesome popover content."
121+
<button type="button" class="oui-button oui-button_primary"
122+
oui-popover="This is an awesome popover content."
99123
oui-popover-placement="bottom-start">
100124
Align start
101125
</button>
102126
103-
<button type="button" class="oui-button oui-button_primary"
104-
oui-popover="This is an awesome popover content."
127+
<button type="button" class="oui-button oui-button_primary"
128+
oui-popover="This is an awesome popover content."
105129
oui-popover-placement="bottom-end">
106130
Align end
107131
</button>
@@ -117,19 +141,40 @@
117141

118142
```html:preview
119143
<input type="text" class="oui-input oui-input_inline"
120-
ng-init="$ctrl.popoverText = 'Lorem ipsum'"
144+
ng-init="$ctrl.popoverText = 'Lorem ipsum'"
121145
ng-model="$ctrl.popoverText"
122146
oui-popover="{{ $ctrl.popoverText }}">
123147
```
124148

149+
### Using `on-open` & `on-close`
150+
151+
```html:preview
152+
<div class="oui-doc-preview-only">
153+
<p><strong>onOpen counter:</strong> {{$ctrl.numOpen }}</p>
154+
<p><strong>onClose counter:</strong> {{$ctrl.numClose }}</p>
155+
</div>
156+
157+
<button type="button" class="oui-button oui-button_primary"
158+
ng-init="$ctrl.numOpen = 0; $ctrl.numClose = 0;"
159+
title="This is an awesome popover content."
160+
oui-popover
161+
oui-popover-on-open="$ctrl.numOpen = $ctrl.numOpen + 1"
162+
oui-popover-on-close="$ctrl.numClose = $ctrl.numClose + 1">
163+
Click to toggle popover
164+
</button>
165+
```
166+
125167
## API
126168

127169
| Attribute | Type | Binding | One-time Binding | Values | Default | Description
128170
| ---- | ---- | ---- | ---- | ---- | ---- | ----
129171
| `oui-popover` | string | @ | no | n/a | `title` attribute | popover content
172+
| `oui-popover-open` | boolean | <? | no | `true`, `false` | `false` | open or close the popover
130173
| `oui-popover-placement` | string | @? | yes | See [Popper placements](https://popper.js.org/popper-documentation.html#Popper.placements) | `right` | modifier for alignment
131174
| `oui-popover-scope` | string | <? | no | n/a | n/a | scope of the popover template
132175
| `oui-popover-template` | string | @? | no | n/a | n/a | id of the popover template
176+
| `oui-popover-on-open` | function | & | no | n/a | n/a | called when popover is opened
177+
| `oui-popover-on-close` | function | & | no | n/a | n/a | called when popover is closed
133178

134179
## Deprecated
135180

packages/oui-popover/src/index.spec.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,63 @@ describe("ouiPopover", () => {
103103
trigger.triggerHandler("click");
104104
expect(trigger.attr("aria-expanded")).toBe("false");
105105
});
106+
107+
it("should open the popover if specified", () => {
108+
const component = testUtils.compileTemplate(
109+
`<div>
110+
<button class="trigger" oui-popover="foo" oui-popover-open="$ctrl.open"></button>
111+
</div>`, {
112+
open: true
113+
}
114+
);
115+
116+
$timeout.flush();
117+
118+
const trigger = angular.element(component[0].querySelector(".trigger"));
119+
expect(trigger.attr("aria-expanded")).toBe("true");
120+
121+
component.scope().$ctrl.open = false;
122+
component.scope().$apply();
123+
124+
expect(trigger.attr("aria-expanded")).toBe("false");
125+
});
126+
127+
it("should not register event handler on trigger element if open is specified", () => {
128+
const component = testUtils.compileTemplate('<div><button class="trigger" oui-popover="foo" oui-popover-open="false"></button></div>');
129+
130+
$timeout.flush();
131+
132+
const trigger = angular.element(component[0].querySelector(".trigger")).triggerHandler("click");
133+
const popover = trigger.next();
134+
135+
expect(popover.attr("x-placement")).toBe(undefined);
136+
});
137+
138+
it("should trigger on-open and on-close", () => {
139+
const openSpy = jasmine.createSpy("open");
140+
const closeSpy = jasmine.createSpy("close");
141+
const component = testUtils.compileTemplate(
142+
`<div>
143+
<button class="trigger" oui-popover="foo" oui-popover-on-open="$ctrl.open()" oui-popover-on-close="$ctrl.close()"></button>
144+
</div>`, {
145+
open: openSpy,
146+
close: closeSpy
147+
}
148+
);
149+
150+
$timeout.flush();
151+
angular.element(component[0].querySelector(".trigger")).triggerHandler("click");
152+
$timeout.flush();
153+
154+
expect(openSpy).toHaveBeenCalled();
155+
expect(openSpy.calls.count()).toEqual(1);
156+
157+
angular.element(component[0].querySelector(".trigger")).triggerHandler("click");
158+
$timeout.flush();
159+
160+
expect(closeSpy).toHaveBeenCalled();
161+
expect(closeSpy.calls.count()).toEqual(1);
162+
});
106163
});
107164

108165
describe("Deprecated support", () => {

packages/oui-popover/src/popover.controller.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,31 @@ export default class PopoverController {
3131
}
3232

3333
$postLink () {
34-
this.setPopover();
35-
this.setTrigger();
34+
this.setPopover()
35+
.then(() => this.setTrigger())
36+
.then(() => {
37+
if (this.open) {
38+
this.openPopover();
39+
}
40+
});
41+
}
42+
43+
$onChanges (changes) {
44+
if (angular.isDefined(changes.open) && this.triggerElement) {
45+
if (changes.open.currentValue) {
46+
this.openPopover();
47+
} else {
48+
this.closePopover();
49+
}
50+
}
3651
}
3752

3853
$onDestroy () {
3954
this.closePopover();
4055
}
4156

4257
setPopover () {
43-
this.$timeout(() => {
58+
return this.$timeout(() => {
4459
// Deprecated: Support for component `oui-popover-content`
4560
if (this.isComponent) {
4661
this.popperElement = this.$element[0].querySelector(".oui-popover");
@@ -67,7 +82,7 @@ export default class PopoverController {
6782
}
6883

6984
setTrigger () {
70-
this.$timeout(() => {
85+
return this.$timeout(() => {
7186
// Deprecated: Support for component `oui-popover-trigger`
7287
if (this.isComponent) {
7388
this.triggerElement = this.$element[0].querySelector(".oui-popover__trigger");
@@ -84,8 +99,12 @@ export default class PopoverController {
8499
.attr({
85100
"aria-haspopup": true,
86101
"aria-expanded": false
87-
})
88-
.on("click", () => this.onTriggerClick());
102+
});
103+
104+
if (angular.isUndefined(this.$attrs.ouiPopoverOpen)) {
105+
this.$triggerElement
106+
.on("click", () => this.onTriggerClick());
107+
}
89108
});
90109
}
91110

@@ -119,6 +138,9 @@ export default class PopoverController {
119138

120139
// Support for attribute `oui-popover`
121140
this.$element.attr("aria-expanded", true);
141+
142+
// force the digest because the popover is outside the angular digest loop
143+
this.$timeout(() => this.onOpen(), 0);
122144
}
123145

124146
closePopover () {
@@ -134,6 +156,9 @@ export default class PopoverController {
134156

135157
// Support for attribute `oui-popover`
136158
this.$element.attr("aria-expanded", false);
159+
160+
// force the digest because the popover is outside the angular digest loop
161+
this.$timeout(() => this.onClose(), 0);
137162
}
138163

139164
createPopper () {

packages/oui-popover/src/popover.directive.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ export default () => {
1010
title: "@?",
1111
placement: "@?ouiPopoverPlacement",
1212
scope: "<?ouiPopoverScope",
13-
template: "@?ouiPopoverTemplate"
13+
template: "@?ouiPopoverTemplate",
14+
open: "<?ouiPopoverOpen",
15+
onOpen: "&ouiPopoverOnOpen",
16+
onClose: "&ouiPopoverOnClose"
1417
},
1518
controller,
1619
controllerAs: "$popoverCtrl"

0 commit comments

Comments
 (0)