Skip to content

Commit e42da8c

Browse files
authored
Merge pull request #1173 from Patternslib/small-improvements
Small improvements
2 parents 27b0a3f + daab725 commit e42da8c

File tree

6 files changed

+145
-52
lines changed

6 files changed

+145
-52
lines changed

src/pat/close-panel/close-panel.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default Base.extend({
2525
await utils.timeout(0); // Wait for other patterns, like pat-validation.
2626

2727
if (
28+
e.target.matches(":not([formnovalidate])") &&
2829
e.target.matches("[type=submit], button:not([type=button])") &&
2930
this.el.closest("form")?.checkValidity() === false
3031
) {

src/pat/close-panel/close-panel.test.js

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe("pat close-panel", function () {
1010
document.body.innerHTML = "";
1111
});
1212

13-
it("Closes a modal's panel.", async function () {
13+
it("1 - Closes a modal's panel.", async function () {
1414
document.body.innerHTML = `
1515
<div id="pat-modal" class="pat-modal">
1616
<button id="close-modal" class="close-panel">close</button>
@@ -42,7 +42,7 @@ describe("pat close-panel", function () {
4242
expect(document.querySelectorAll(".pat-modal").length).toBe(0);
4343
});
4444

45-
it("Closes a dialog's panel.", async function () {
45+
it("2 - Closes a dialog's panel.", async function () {
4646
document.body.innerHTML = `
4747
<dialog open>
4848
<button class="close-panel">close</button>
@@ -61,32 +61,98 @@ describe("pat close-panel", function () {
6161
expect(dialog.open).toBe(false);
6262
});
6363

64-
it("Prevents closing a panel with an invalid form when submitting but allow to cancel and close.", async function () {
65-
const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy");
64+
describe("3 - Prevents closing a panel with an invalid form when submitting but allow to cancel and close.", function () {
65+
it("3.1 - ... when the cancel button is not a submit button", async function () {
66+
const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy");
67+
68+
document.body.innerHTML = `
69+
<div class="pat-modal">
70+
<form action="." class="pat-validation">
71+
<input name="ok" required />
72+
<button class="close-panel submit">submit</button>
73+
<button class="close-panel cancel" type="button">cancel</button>
74+
</form>
75+
</div>
76+
`;
77+
const el = document.querySelector("form");
6678

67-
document.body.innerHTML = `
68-
<div class="pat-modal">
69-
<form action="." class="pat-validation">
70-
<input name="ok" required />
71-
<button class="close-panel submit">submit</button>
72-
<button class="close-panel cancel" type="button">cancel</button>
73-
</form>
74-
</div>
75-
`;
76-
const el = document.querySelector("form");
79+
registry.scan(document.body);
80+
await utils.timeout(1); // wait a tick for async to settle.
81+
82+
el.querySelector("button.submit").click();
83+
await utils.timeout(1); // wait a tick for async to settle.
84+
85+
expect(spy_destroy_modal).not.toHaveBeenCalled();
86+
87+
// A non-submit close-panel button does not check for validity.
88+
el.querySelector("button.cancel").click();
89+
await utils.timeout(1); // wait a tick for async to settle.
90+
91+
expect(spy_destroy_modal).toHaveBeenCalled();
92+
93+
spy_destroy_modal.mockRestore();
94+
});
95+
96+
it("3.2 - ... when the cancel button is a submit button but has the formnovalidate attribute set", async function () {
97+
const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy");
98+
99+
document.body.innerHTML = `
100+
<div class="pat-modal">
101+
<form action="." class="pat-validation">
102+
<input name="ok" required />
103+
<button class="close-panel submit">submit</button>
104+
<button class="close-panel cancel" formnovalidate>cancel</button>
105+
</form>
106+
</div>
107+
`;
108+
const el = document.querySelector("form");
109+
110+
registry.scan(document.body);
111+
await utils.timeout(1); // wait a tick for async to settle.
112+
113+
el.querySelector("button.submit").click();
114+
await utils.timeout(1); // wait a tick for async to settle.
115+
116+
expect(spy_destroy_modal).not.toHaveBeenCalled();
117+
118+
// A non-submit close-panel button does not check for validity.
119+
el.querySelector("button.cancel").click();
120+
await utils.timeout(1); // wait a tick for async to settle.
121+
122+
expect(spy_destroy_modal).toHaveBeenCalled();
123+
124+
spy_destroy_modal.mockRestore();
125+
});
126+
127+
it("3.3 - ... when the cancel button is a submit input but has the formnovalidate attribute set", async function () {
128+
const spy_destroy_modal = jest.spyOn(pat_modal.prototype, "destroy");
129+
130+
document.body.innerHTML = `
131+
<div class="pat-modal">
132+
<form action="." class="pat-validation">
133+
<input name="ok" required />
134+
<button class="close-panel submit">submit</button>
135+
<input type="submit" value="cancel" class="close-panel cancel" formnovalidate />
136+
</form>
137+
</div>
138+
`;
139+
const el = document.querySelector("form");
140+
141+
registry.scan(document.body);
142+
await utils.timeout(1); // wait a tick for async to settle.
77143

78-
registry.scan(document.body);
79-
await utils.timeout(1); // wait a tick for async to settle.
144+
el.querySelector("button.submit").click();
145+
await utils.timeout(1); // wait a tick for async to settle.
80146

81-
el.querySelector("button.submit").click();
82-
await utils.timeout(1); // wait a tick for async to settle.
147+
expect(spy_destroy_modal).not.toHaveBeenCalled();
83148

84-
expect(spy_destroy_modal).not.toHaveBeenCalled();
149+
// A non-submit close-panel button does not check for validity.
150+
el.querySelector("input.cancel").click();
151+
await utils.timeout(1); // wait a tick for async to settle.
85152

86-
// A non-submit close-panel button does not check for validity.
87-
el.querySelector("button.cancel").click();
88-
await utils.timeout(1); // wait a tick for async to settle.
153+
expect(spy_destroy_modal).toHaveBeenCalled();
89154

90-
expect(spy_destroy_modal).toHaveBeenCalled();
155+
spy_destroy_modal.mockRestore();
156+
});
91157
});
92158
});

src/pat/collapsible/collapsible.js

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -106,35 +106,27 @@ class Pattern extends BasePattern {
106106
$(document).on("click", this.options.openTrigger, this.open.bind(this));
107107
}
108108

109-
// scroll debouncer for later use.
110-
this.debounce_scroll = utils.debounce(
111-
this._scroll.bind(this),
112-
10,
113-
debounce_scroll_timer
114-
);
115-
116109
// pat-scroll support
117-
if (this.options.scroll?.selector) {
110+
if (this.options.scroll?.selector && this.options.scroll.selector !== "none") {
118111
const Scroll = (await import("../scroll/scroll")).default;
119112
this.scroll = new Scroll(this.el, {
120113
trigger: "manual",
121114
selector: this.options.scroll.selector,
122115
offset: this.options.scroll?.offset,
123116
});
124117
await events.await_pattern_init(this.scroll);
118+
119+
// scroll debouncer for later use.
120+
this.debounce_scroll = utils.debounce(
121+
this.scroll.scrollTo.bind(this.scroll),
122+
10,
123+
debounce_scroll_timer
124+
);
125125
}
126126

127127
return $el;
128128
}
129129

130-
async _scroll() {
131-
const scroll_selector = this.options.scroll?.selector;
132-
if (!scroll_selector) {
133-
return;
134-
}
135-
await this.scroll.scrollTo();
136-
}
137-
138130
open() {
139131
if (!this.$el.hasClass("open")) {
140132
this.toggle();
@@ -196,7 +188,7 @@ class Pattern extends BasePattern {
196188
if (new_state === "open") {
197189
this.$el.trigger("patterns-collapsible-open");
198190
this._transit(this.$el, "closed", "open");
199-
this.debounce_scroll();
191+
this.debounce_scroll?.(); // debounce scroll, if available.
200192
} else {
201193
this.$el.trigger("patterns-collapsible-close");
202194
this._transit(this.$el, "open", "closed");

src/pat/collapsible/collapsible.test.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,12 @@ describe("pat-collapsible", function () {
147147
`;
148148
const collapsible = document.querySelector(".pat-collapsible");
149149
const instance = new Pattern(collapsible, { transition: "none" });
150-
const spy_scroll = jest.spyOn(instance, "_scroll");
151150
await events.await_pattern_init(instance);
152151

153152
instance.toggle();
154153
await utils.timeout(10);
155154

156-
expect(spy_scroll).toHaveBeenCalledTimes(1);
155+
expect(this.spy_scrollTo).toHaveBeenCalledTimes(1);
157156
});
158157

159158
it("8.2 - does not scroll when being closed.", async function () {
@@ -164,13 +163,12 @@ describe("pat-collapsible", function () {
164163
`;
165164
const collapsible = document.querySelector(".pat-collapsible");
166165
const instance = new Pattern(collapsible, { transition: "none" });
167-
const spy_scroll = jest.spyOn(instance, "_scroll");
168166
await events.await_pattern_init(instance);
169167

170168
instance.toggle();
171169
await utils.timeout(10);
172170

173-
expect(spy_scroll).not.toHaveBeenCalled();
171+
expect(this.spy_scrollTo).not.toHaveBeenCalled();
174172
});
175173

176174
it("8.3 - only scrolls once even if multiple collapsible are opened at once.", async function () {
@@ -238,6 +236,29 @@ describe("pat-collapsible", function () {
238236
const arg_1 = this.spy_scrollTo.mock.calls[0][0];
239237
expect(arg_1.top).toBe(40); // the offset is substracted from the scroll position, so a negative offset is added to the scroll position and stops AFTER the target position.
240238
});
239+
240+
it("8.6 - disables scrolling if a parent pat-collapsible has enabled it.", async function () {
241+
document.body.innerHTML = `
242+
<div id="id1" class="pat-collapsible closed" data-pat-collapsible="scroll-selector: self; transition: none">
243+
<p>Collapsible content</p>
244+
<div id="id2" class="pat-collapsible closed" data-pat-collapsible="scroll-selector: none">
245+
<p>Collapsible content</p>
246+
</div>
247+
</div>
248+
`;
249+
const collapsible_1 = document.querySelector("#id1");
250+
const instance_1 = new Pattern(collapsible_1);
251+
await events.await_pattern_init(instance_1);
252+
253+
const collapsible_2 = document.querySelector("#id2");
254+
const instance_2 = new Pattern(collapsible_2);
255+
await events.await_pattern_init(instance_2);
256+
257+
instance_2.toggle();
258+
await utils.timeout(10);
259+
260+
expect(this.spy_scrollTo).not.toHaveBeenCalled();
261+
});
241262
});
242263

243264
it("9 - triggers the pat-update event.", async function () {

src/pat/collapsible/documentation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,5 +169,5 @@ attribute. The available options are:
169169
| `transition` | `slide` | Transition effect when opening or closing a collapsinble. Must be one of `none`, `css`, `fade`, `slide` or `slide-horizontal`. |
170170
| `effect-duration` | `fast` | Duration of transition. This is ignored if the transition is `none` or `css`. |
171171
| `effect-easing` | `swing` | Easing to use for the open/close animation. This must be a known jQuery easing method. jQuery includes `swing` and `linear`, but more can be included via jQuery UI. |
172-
| `scroll-selector` | | CSS selector or `self`. Defines which element will be scrolled into view. `self` if it is the collapsible element itself. |
172+
| `scroll-selector` | | CSS selector, `self` or `none`. Defines which element will be scrolled into view. `self` if it is the collapsible element itself. `none` to disable scrolling if a scrolling selector is inherited from a parent pat-collapsible element. |
173173
| `scroll-offset` | | `offset` in pixels to stop scrolling before the target position defines by `scroll-selector`. Can also be a negative number. |

src/pat/validation/documentation.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
## Description
22

3-
This pattern provides a simple but powerful form validation beyond what HTML5 offers.
3+
This pattern provides form validation based on the HTML standard and offers extended functionality like custom error messages and extra validation rules.
4+
45

56
## Documentation
67

7-
The validation pattern is triggered by a single class `pat-validation` on the form tag. The rest is handled mostly with standard HTML5 validation attributes.
8+
The validation pattern is triggered by a single class `pat-validation` on the form tag.
9+
The rest is handled mostly with standard HTML validation attributes.
10+
11+
This patterns offers:
12+
13+
- extra validation rules like checking for equality or checking is one date it after another.
14+
- custom error messages.
815

9-
This pattern has several advantages over standard HTML 5 form validation:
16+
Since it is based on the HTML standard you can still use the `:valid`, `:invalid` and `:out-of-range` CSS pseudo classes.
1017

11-
- it supports older browsers
12-
- it uses simple documented HTML markup to allow non-browser-specific styling of error messages
13-
- it supports extra validation rules
18+
You can use any HTML form validation attributes but here are some examples:
1419

15-
### The following attributes may be used.
1620

1721
| Name | Syntax | Description |
1822
| ------------- | -------------------------- | ------------------------------------------------------------ |
@@ -23,6 +27,10 @@ This pattern has several advantages over standard HTML 5 form validation:
2327
| Maximum value | `type="number" max="10"` | Check if a number is less than or equal to a given value. |
2428
| Real number | `type="number" step="any"` | Check if a number is less than or equal to a given value. |
2529

30+
31+
> **_NOTE:_** The form inputs must have a `name` attribute, otherwise the validation would not happen.
32+
33+
2634
### Error messages
2735

2836
Error messages are inserted into the DOM as `em` elements with a `message warning` class.
@@ -65,6 +73,11 @@ Error messages can also be overridden on a per-field basis, for example:
6573

6674
### Options reference
6775

76+
> **_NOTE:_** The form inputs must have a `name` attribute, otherwise the validation would not happen.
77+
78+
> **_NOTE:_** If you need to exclude a submit button from form validation - like a cancel button which actually submits - add the `formnovalidate` attribute to the button.
79+
80+
6881
| Property | Description | Default | Type |
6982
| ---------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -------------------------------------- |
7083
| disable-selector | A selector for elements that should be disabled when there are errors in the form. | | CSS Selector |

0 commit comments

Comments
 (0)