Skip to content

Commit f820445

Browse files
committed
fix(pat autofocus): Scoped autofocus.
Do not operate on whole DOM tree but only on the scoped element while still working with multiple pat-autofocus instances. This fixes a problem where autofocus was set on the wrong element after injection.
1 parent 8ca2acf commit f820445

File tree

5 files changed

+91
-67
lines changed

5 files changed

+91
-67
lines changed

src/pat/autofocus/autofocus.js

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import $ from "jquery";
21
import Base from "../../core/base";
2+
import dom from "../../core/dom";
33

44
let scheduled_task = null;
5-
let registered_event_handler = false;
65

76
export default Base.extend({
87
name: "autofocus",
@@ -22,32 +21,25 @@ export default Base.extend({
2221
// Do not autofocus in iframes.
2322
return;
2423
}
25-
26-
this.setFocus(this.trigger);
27-
28-
if (!registered_event_handler) {
29-
// Register the event handler only once.
30-
$(document).on("patterns-injected pat-update", (e) => {
31-
this.setFocus($(e.target).find(this.trigger));
32-
});
33-
registered_event_handler = true;
34-
}
24+
this.set_focus();
3525
},
3626

37-
setFocus(target) {
38-
// Exit if task is scheduled. setFocus operates on whole DOM anyways.
39-
if (scheduled_task) {
40-
return;
41-
}
42-
const $all = $(target);
43-
const visible = [...$all].filter((it) => $(it).is(":visible"));
44-
const empty = visible.filter((it) => it.value === "");
45-
const el = empty[0] || visible[0];
46-
if (el) {
27+
set_focus() {
28+
if (dom.is_visible(this.el) && this.el.value === "") {
29+
// Set autofocus only for visible and empty inputs.
30+
31+
// Clear scheduled tasks if there are any.
32+
// Note: Patterns scanning initizlizes patterns "inside-out", so
33+
// DOM nodes later in the tree are initizlized first.
34+
// With multiple pattern instantiations and then module-
35+
// globally clearing and re-scheduling tasks we are
36+
// initializing in the end the first pattern which matches
37+
// the conditions.
38+
clearTimeout(scheduled_task);
4739
scheduled_task = setTimeout(() => {
48-
el.focus();
40+
this.el.focus();
4941
scheduled_task = null;
50-
}, 10);
42+
}, 100);
5143
}
5244
},
5345
});
Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,45 @@
1-
import pattern from "./autofocus";
1+
import "regenerator-runtime/runtime"; // needed for ``await`` support
2+
import "./autofocus";
3+
import registry from "../../core/registry";
24
import utils from "../../core/utils";
35

46
describe("pat-autofocus", function () {
5-
beforeEach(function () {
6-
const el = document.createElement("div");
7-
el.setAttribute("id", "lab");
8-
document.body.append(el);
9-
});
10-
11-
afterEach(function () {
12-
document.body.innerHTML = "";
13-
});
14-
157
it("Focus the first element.", async () => {
16-
const container = document.querySelector("#lab");
17-
container.innerHTML = `
8+
document.body.innerHTML = `
189
<input name="i1" type="text" class="pat-autofocus"/>
1910
<input name="i2" type="text" class="pat-autofocus"/>
2011
<input name="i3" type="text" class="pat-autofocus"/>
2112
`;
22-
pattern.init(container);
23-
await utils.timeout(20);
13+
registry.scan(document.body);
14+
await utils.timeout(100);
2415

2516
const should_be_active = document.querySelector("input[name=i1]");
2617
expect(document.activeElement).toBe(should_be_active);
2718
});
2819

29-
it("Focus the non-empty element, if available.", async () => {
30-
const container = document.querySelector("#lab");
31-
container.innerHTML = `
20+
it("Focus the first empty element, if available.", async () => {
21+
document.body.innerHTML = `
3222
<input name="i1" type="text" class="pat-autofocus" value="okay"/>
3323
<input name="i2" type="text" class="pat-autofocus"/>
3424
<input name="i3" type="text" class="pat-autofocus"/>
3525
`;
36-
pattern.init(container);
37-
await utils.timeout(20);
26+
registry.scan(document.body);
27+
await utils.timeout(100);
3828

3929
const should_be_active = document.querySelector("input[name=i2]");
4030
expect(document.activeElement).toBe(should_be_active);
4131
});
32+
33+
it("Don't focus hidden elements.", async () => {
34+
document.body.innerHTML = `
35+
<input name="i1" type="text" class="pat-autofocus" value="okay"/>
36+
<input name="i2" type="text" class="pat-autofocus" hidden/>
37+
<input name="i3" type="text" class="pat-autofocus"/>
38+
`;
39+
registry.scan(document.body);
40+
await utils.timeout(100);
41+
42+
const should_be_active = document.querySelector("input[name=i3]");
43+
expect(document.activeElement).toBe(should_be_active);
44+
});
4245
});

src/pat/autofocus/documentation.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
## Description
22

3-
With the Autofocus pattern you may choose which input field gets the focus when a page load, or after it was injected.
3+
With the Autofocus pattern you may choose which input field gets the focus when a page is loaded or injected.
4+
45

56
## Documentation
67

7-
Patterns augments the standard HTML5 autofocus behaviour, and implements it for browsers that do not support it natively.
8+
Patterns augments the standard HTML5 autofocus behaviour.
89

910
On initial page load or when new content is injected it is scanned for input elements with an `autofocus` attribute or `pat-autofocus` class.
1011

src/pat/autofocus/index-iframed.html

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4-
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5-
<title>Focus demo page</title>
6-
<link rel="stylesheet" href="/style/common.css" type="text/css" />
7-
<script src="/dist/bundle.js" type="text/javascript"></script>
8-
<style type="text/css" media="screen">
9-
form {
10-
margin-top: 400px;
11-
}
12-
</style>
4+
<meta http-equiv="Content-Type"
5+
content="text/html; charset=UTF-8"
6+
/>
7+
<title>pat-autofocus iframe'd demo page</title>
8+
<link href="/style/common.css"
9+
rel="stylesheet"
10+
type="text/css"
11+
/>
12+
<script src="/dist/bundle.js"
13+
type="text/javascript"
14+
></script>
1315
</head>
1416
<body>
15-
<form action="">
17+
<form>
1618
<label>Title
17-
<input class="pat-autofocus" type="text" />
19+
<input class="pat-autofocus"
20+
type="text"
21+
/>
1822
</label>
1923
</form>
2024
</body>

src/pat/autofocus/index.html

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,48 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4-
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5-
<title>Focus demo page</title>
6-
<link rel="stylesheet" href="/style/common.css" type="text/css" />
7-
<script src="/dist/bundle.js" type="text/javascript"></script>
4+
<meta http-equiv="Content-Type"
5+
content="text/html; charset=UTF-8"
6+
/>
7+
<title>pat-autofocus demo page</title>
8+
<link href="/style/common.css"
9+
rel="stylesheet"
10+
type="text/css"
11+
/>
12+
<script src="/dist/bundle.js"
13+
type="text/javascript"
14+
></script>
815
</head>
916
<body>
10-
<form action="">
17+
<form>
1118
<fieldset class="horizontal">
12-
<label>Title
13-
<input class="pat-autofocus" type="text" value="Prefilled title" />
19+
<label>Input 1 (has value)
20+
<input class="pat-autofocus"
21+
type="text"
22+
value="Prefilled title"
23+
/>
1424
</label>
1525
<br />
16-
<label>Keywords
17-
<input class="pat-autofocus" type="text" placeholder="This field should get the focus" />
26+
<label>Input 2 (hidden)
27+
<input class="pat-autofocus"
28+
hidden
29+
type="text"
30+
/>
31+
</label>
32+
<br />
33+
<label>Input 3 (should get focus)
34+
<input class="pat-autofocus"
35+
placeholder="This field should get the focus"
36+
type="text"
37+
/>
1838
</label>
1939
</fieldset>
2040
</form>
41+
2142
<p>Elements in the iframe below should not get the focus.</p>
22-
<iframe src="./index-iframed.html" frameborder="0" style="width: 400px; height: 300px; border: 1px solid black;"></iframe>
43+
<iframe frameborder="0"
44+
src="./index-iframed.html"
45+
style="width: 400px; height: 300px; border: 1px solid black"
46+
></iframe>
2347
</body>
2448
</html>

0 commit comments

Comments
 (0)