Skip to content

Commit 7a8c318

Browse files
author
Ryan A. Johnson
committed
feat(HXSearchElement): add declarative functionality
* Add `focus()` * Add `clear()` * Update documentation
1 parent e62d6c9 commit 7a8c318

File tree

3 files changed

+124
-52
lines changed

3 files changed

+124
-52
lines changed

docs/components/search/test.html

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
title: Testing - Search
3+
---
4+
{% extends 'test.njk' %}
5+
{% block content %}
6+
<section>
7+
<h2>Declarative APIs</h2>
8+
<div class="hxRow">
9+
<div class="hxCol hxSpan-4">
10+
<p>
11+
<hx-search id="testSearch"></hx-search>
12+
</p>
13+
<p>
14+
<button class="hxBtn" type="button" id="btnFocus">focus()</button>
15+
<button class="hxBtn" type="button" id="btnClear">clear()</button>
16+
<button class="hxBtn" type="button" id="btnFocusThenBlur">focus() (blur() after 2s)</button>
17+
</p>
18+
</div>
19+
</div>
20+
</section>
21+
22+
<script>
23+
(function () {
24+
let elSearch = document.getElementById('testSearch');
25+
26+
document.getElementById('btnFocus').addEventListener('click', function () {
27+
elSearch.focus();
28+
});
29+
30+
document.getElementById('btnClear').addEventListener('click', function () {
31+
elSearch.clear();
32+
});
33+
34+
document.getElementById('btnFocusThenBlur').addEventListener('click', function () {
35+
elSearch.focus();
36+
37+
setTimeout(function () {
38+
elSearch.blur();
39+
}, 2000);
40+
});
41+
})();
42+
</script>
43+
{% endblock %}

docs/elements/hx-search/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@
3535
</hx-def>
3636
</hx-dl>
3737
</section>
38+
39+
<section>
40+
<h2 id="methods">Methods</h2>
41+
<dl>
42+
<dt>focus()</dt>
43+
<dd>Overrides default HTMLElement behavior to focus the input in the Shadow DOM.</dd>
44+
45+
<dt>clear()</dt>
46+
<dd>Simulates pressing "x" to clear the value of the input.</dd>
47+
</dl>
48+
</section>
3849
{% endblock %}
3950

4051
{% block attributes %}

src/helix-ui/elements/HXSearchElement.js

Lines changed: 70 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,24 @@ export class HXSearchElement extends HXElement {
5151
}
5252

5353
$onCreate () {
54-
this._clearValue = this._clearValue.bind(this);
55-
this._onInput = this._onInput.bind(this);
54+
this._onClearClick = this._onClearClick.bind(this);
55+
this._onSearchInput = this._onSearchInput.bind(this);
5656
}
5757

5858
$onConnect () {
59-
this.$upgradeProperty('disabled');
6059
this.$upgradeProperty('invalid');
6160
this.$upgradeProperty('placeholder');
6261
this.$upgradeProperty('value');
6362

64-
this._btnClear.addEventListener('click', this._clearValue);
65-
this._elSearch.addEventListener('input', this._onInput);
63+
this._btnClear.addEventListener('click', this._onClearClick);
64+
this._elSearch.addEventListener('input', this._onSearchInput);
6665

6766
this.$relayNonBubblingEvents(this._elSearch);
6867
}
6968

7069
$onDisconnect () {
71-
this._btnClear.removeEventListener('click', this._clearValue);
72-
this._elSearch.removeEventListener('input', this._onInput);
70+
this._btnClear.removeEventListener('click', this._onClearClick);
71+
this._elSearch.removeEventListener('input', this._onSearchInput);
7372

7473
this.$removeNonBubblingRelays(this._elSearch);
7574
}
@@ -86,15 +85,17 @@ export class HXSearchElement extends HXElement {
8685
const hasValue = (newVal !== null);
8786

8887
switch (attr) {
89-
case 'disabled':
88+
case 'disabled': {
9089
this._elSearch.disabled = hasValue;
9190
break;
91+
}
9292

93-
case 'placeholder':
93+
case 'placeholder': {
9494
this._elSearch.placeholder = newVal;
9595
break;
96+
}
9697

97-
case 'value':
98+
case 'value': {
9899
if (this._elSearch.value !== newVal) {
99100
this._elSearch.value = newVal;
100101
}
@@ -105,35 +106,17 @@ export class HXSearchElement extends HXElement {
105106
this._btnClear.hidden = true;
106107
}
107108
break;
109+
}
108110
}
109111
}
110112

111-
// GETTERS
112-
get disabled () {
113-
return this.hasAttribute('disabled');
114-
}
115-
113+
/**
114+
* @default [false]
115+
* @type {Boolean}
116+
*/
116117
get invalid () {
117118
return this.hasAttribute('invalid');
118119
}
119-
120-
get placeholder () {
121-
return this.getAttribute('placeholder');
122-
}
123-
124-
get value () {
125-
return this.getAttribute('value');
126-
}
127-
128-
// SETTERS
129-
set disabled (isDisabled) {
130-
if (isDisabled) {
131-
this.setAttribute('disabled', '');
132-
} else {
133-
this.removeAttribute('disabled');
134-
}
135-
}
136-
137120
set invalid (isInvalid) {
138121
if (isInvalid) {
139122
this.setAttribute('invalid', '');
@@ -142,6 +125,13 @@ export class HXSearchElement extends HXElement {
142125
}
143126
}
144127

128+
/**
129+
* @default ['']
130+
* @type {String}
131+
*/
132+
get placeholder () {
133+
return this.getAttribute('placeholder');
134+
}
145135
set placeholder (newVal) {
146136
if (newVal) {
147137
this.setAttribute('placeholder', newVal);
@@ -150,6 +140,13 @@ export class HXSearchElement extends HXElement {
150140
}
151141
}
152142

143+
/**
144+
* @default ['']
145+
* @type {String}
146+
*/
147+
get value () {
148+
return this.getAttribute('value');
149+
}
153150
set value (newVal) {
154151
if (newVal) {
155152
this.setAttribute('value', newVal);
@@ -158,34 +155,55 @@ export class HXSearchElement extends HXElement {
158155
}
159156
}
160157

161-
/** @private */
162-
get _elSearch () {
163-
return this.shadowRoot.getElementById('hxNativeControl');
158+
/**
159+
* Simulate pressing "X" to clear input value
160+
*/
161+
clear () {
162+
if (this.value !== '') {
163+
this.value = '';
164+
this.$emit('clear');
165+
}
166+
}
167+
168+
/**
169+
* Override HTMLElement#focus(), because we need to focus the
170+
* inner `<input>` instead of the outer `<hx-search>`.
171+
*/
172+
focus () {
173+
this._elSearch.focus();
164174
}
165175

166176
/** @private */
167177
get _btnClear () {
168178
return this.shadowRoot.getElementById('hxClear');
169179
}
170180

171-
// PRIVATE FUNCTIONS
172-
_onInput (evt) {
173-
this.value = evt.target.value;
174-
if (evt.target.value === '') {
175-
this._btnClear.hidden = true;
176-
} else {
177-
this._btnClear.hidden = false;
178-
}
181+
/** @private */
182+
get _elSearch () {
183+
return this.shadowRoot.getElementById('hxNativeControl');
179184
}
180185

181-
_clearValue (evt) {
186+
/**
187+
* Clear value and focus input when user presses "X" via the UI.
188+
* @private
189+
*/
190+
_onClearClick (evt) {
182191
evt.preventDefault();
183-
184-
this.value = '';
185-
186-
// Emit a 'clear' event to communicate state change.
187-
this.$emit('clear');
188-
189-
this._elSearch.focus();
192+
this.clear();
193+
this.focus();
194+
}
195+
196+
/**
197+
* Keep state in sync with `<input>`
198+
*
199+
* 1. synchronize `value`
200+
* 2. determine whether to reveal the clear button
201+
*
202+
* @private
203+
*/
204+
_onSearchInput (evt) {
205+
this.value = evt.target.value;
206+
let hasValue = (evt.target.value !== '');
207+
this._btnClear.hidden = !hasValue;
190208
}
191209
}

0 commit comments

Comments
 (0)