Skip to content

Commit 55fa533

Browse files
authored
fix(ui5-input): fire change in sync with the native input (#168)
Bind for the native input change to fire the ui5-input semantic one. Make sure change event is fired upon suggestion item selection. Still, for IE, a manually work is needed to detect if change should be fired on Enter as the native input does not react. Fixes: #154
1 parent db88b8c commit 55fa533

File tree

7 files changed

+115
-85
lines changed

7 files changed

+115
-85
lines changed

packages/main/src/Input.hbs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
.value="{{ctr.value}}"
1616
placeholder="{{ctr.placeholder}}"
1717
@input="{{ctr._input.onInput}}"
18+
@change="{{ctr._input.change}}"
1819
data-sap-no-tab-ref
1920
data-sap-focus-ref
2021
/>

packages/main/src/Input.js

+16-26
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import WebComponent from "@ui5/webcomponents-base/src/sap/ui/webcomponents/base/WebComponent";
22
import Bootstrap from "@ui5/webcomponents-base/src/sap/ui/webcomponents/base/Bootstrap";
3+
import { isIE } from "@ui5/webcomponents-core/dist/sap/ui/Device";
34
import ValueState from "@ui5/webcomponents-base/src/sap/ui/webcomponents/base/types/ValueState";
45
import ShadowDOM from "@ui5/webcomponents-base/src/sap/ui/webcomponents/base/compatibility/ShadowDOM";
56
import {
@@ -285,17 +286,14 @@ class Input extends WebComponent {
285286
// Indicates if there is selected suggestionItem.
286287
this.hasSuggestionItemSelected = false;
287288

288-
// Indicates if there is focused suggestionItem.
289-
// Used to ignore the Input "focusedOut" and thus preventing firing "change" event.
290-
this.hasSuggestionItemFocused = false;
291-
292-
this.previousValue = undefined;
293-
294289
// Represents the value before user moves selection between the suggestion items.
295290
// Used to register and fire "input" event upon [SPACE] or [ENTER].
296291
// Note: the property "value" is updated upon selection move and can`t be used.
297292
this.valueBeforeItemSelection = "";
298293

294+
// tracks the value between focus in and focus out to detect that change event should be fired.
295+
this.previousValue = undefined;
296+
299297
// Indicates, if the component is rendering for first time.
300298
this.firstRendering = true;
301299

@@ -307,11 +305,13 @@ class Input extends WebComponent {
307305

308306
// all user interactions
309307
this.ACTION_ENTER = "enter";
310-
this.ACTION_FOCUSOUT = "focusOut";
311308
this.ACTION_USER_INPUT = "input";
312309

313310
this._input = {
314311
onInput: this._onInput.bind(this),
312+
change: event => {
313+
this.fireEvent(this.EVENT_CHANGE);
314+
},
315315
};
316316

317317
this._whenShadowRootReady().then(this.attachFocusHandlers.bind(this));
@@ -327,7 +327,6 @@ class Input extends WebComponent {
327327
if (!this.firstRendering && this.Suggestions) {
328328
this.Suggestions.toggle(this.shouldOpenSuggestions());
329329
}
330-
this.checkFocusOut();
331330
this.firstRendering = false;
332331
}
333332

@@ -392,13 +391,13 @@ class Input extends WebComponent {
392391
}
393392

394393
onfocusin() {
395-
this.previousValue = this.value;
396-
this.hasSuggestionItemFocused = false;
397394
this._focused = true; // invalidating property
395+
this.previousValue = this.value;
398396
}
399397

400398
onfocusout() {
401399
this._focused = false; // invalidating property
400+
this.previousValue = "";
402401
}
403402

404403
_onInput(event) {
@@ -454,6 +453,7 @@ class Input extends WebComponent {
454453
this.value = itemText;
455454
this.valueBeforeItemSelection = itemText;
456455
this.fireEvent(this.EVENT_INPUT);
456+
this.fireEvent(this.EVENT_CHANGE);
457457
}
458458
}
459459

@@ -469,35 +469,27 @@ class Input extends WebComponent {
469469

470470
const inputValue = this.getInputValue();
471471
const isSubmit = action === this.ACTION_ENTER;
472-
const isFocusOut = action === this.ACTION_FOCUSOUT;
473472
const isUserInput = action === this.ACTION_USER_INPUT;
474473

475474
this.value = inputValue;
476475

477-
const valueChanged = (this.previousValue !== undefined) && (this.previousValue !== this.value);
478-
479476
if (isUserInput) { // input
480477
this.fireEvent(this.EVENT_INPUT);
481478
return;
482479
}
483480

484-
if ((isSubmit || isFocusOut) && valueChanged) { // change
485-
this.previousValue = this.value;
486-
this.fireEvent(this.EVENT_CHANGE);
487-
}
488-
489481
if (isSubmit) { // submit
490482
this.fireEvent(this.EVENT_SUBMIT);
491483
}
492-
}
493484

494-
checkFocusOut() {
495-
if (!this._focused && !this.hasSuggestionItemFocused) {
496-
this.fireEventByAction(this.ACTION_FOCUSOUT);
497-
this.previousValue = "";
485+
// In IE, pressing the ENTER does not fire change
486+
const valueChanged = (this.previousValue !== undefined) && (this.previousValue !== this.value);
487+
if (isIE() && isSubmit && valueChanged) {
488+
this.fireEvent(this.EVENT_CHANGE);
498489
}
499490
}
500491

492+
501493
getInputValue() {
502494
const inputDOM = this.getDomRef();
503495
if (inputDOM) {
@@ -519,9 +511,7 @@ class Input extends WebComponent {
519511
}
520512

521513
/* Suggestions interface */
522-
onItemFocused() {
523-
this.hasSuggestionItemFocused = true;
524-
}
514+
onItemFocused() {}
525515

526516
onItemSelected(item, keyboardUsed) {
527517
this.selectSuggestion(item, keyboardUsed);

packages/main/test/sap/ui/webcomponents/main/pages/DatePicker_test_page.html

+12-5
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,22 @@
3232
<ui5-datepicker id="dp10" disabled></ui5-datepicker>
3333
<ui5-datepicker id="dp11"></ui5-datepicker>
3434
<ui5-datepicker id="dp12"></ui5-datepicker>
35+
<ui5-datepicker id="dp13"></ui5-datepicker>
3536
<label id='lbl'>0</label>
3637
<label for="inputTimezone">Timezone:</label>
3738
<input id="inputTimezone" type="number"/>
3839
<button id="btnApplyTimezone" onclick="stubTimezone();">Apply</button>
3940
<button id="btnRestoreTimezone" onclick="restore();">Restore</button>
4041
<button id="downThere" style="position:absolute; top:3000px;"></button>
41-
42+
<label id='lblTomorrow'>0</label>
43+
<label id='lblTomorrowDate'></label>
4244
<script>
43-
const originalGetDate = Date.prototype.getDate;
45+
var originalGetDate = Date.prototype.getDate;
4446

4547
function stubTimezone() {
46-
const timezone = parseInt(document.querySelector("#inputTimezone").value, 10);
48+
var timezone = parseInt(document.querySelector("#inputTimezone").value, 10);
4749
Date.prototype.getDate = function() {
48-
const stubedDate = new Date(this.getTime() + timezone * 60 * 60 * 1000);
50+
var stubedDate = new Date(this.getTime() + timezone * 60 * 60 * 1000);
4951
return stubedDate.getUTCDate();
5052
};
5153
}
@@ -55,13 +57,18 @@
5557
document.querySelector("#inputTimezone").value = "";
5658
};
5759

58-
let counter = 0;
60+
var counter = 0;
61+
var counterTomorrow = 0;
5962
function increaseCounter() {
6063
document.querySelector('#lbl').innerHTML = ++counter;
6164
}
6265

6366
document.querySelector("#dp5").addEventListener("change", increaseCounter);
6467
document.querySelector("#dp8").addEventListener("change", increaseCounter);
68+
document.querySelector("#dp13").addEventListener("change", function(e) {
69+
document.querySelector('#lblTomorrow').innerHTML = ++counterTomorrow;
70+
document.querySelector('#lblTomorrowDate').innerHTML = e.target.value;
71+
});
6572
</script>
6673
</body>
6774
</html>

packages/main/test/sap/ui/webcomponents/main/pages/Input.html

+26-10
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,31 @@ <h3> Input disabled</h3>
3737
<ui5-icon data-ui5-slot="icon" src="sap-icon://appointment-2"></ui5-icon>
3838
</ui5-input>
3939

40-
<h3> Input valueState Warning</h3>
40+
<h3> Input valueState Success</h3>
41+
<ui5-input id="input3" style="width:100%" value-state="Success" placeholder="Success state ...">
42+
<ui5-icon data-ui5-slot="icon" src="sap-icon://message-success"></ui5-icon>
43+
</ui5-input>
44+
45+
<h3> Test 'change' event</h3>
4146
<ui5-input id="input1" style="width:100%" value-state="Warning" placeholder="Warning state ...">
4247
<ui5-icon data-ui5-slot="icon" src="sap-icon://message-warning" functional></ui5-icon>
4348
</ui5-input>
49+
<h3> 'change' event result</h3>
50+
<ui5-input id="inputResult" style="width:100%"></ui5-input>
4451

45-
<h3> Input valueState Error</h3>
52+
<h3> Test 'input' event</h3>
4653
<ui5-input id="input2" style="width:100%" value-state="Error" placeholder="Error state ...">
4754
<ui5-icon data-ui5-slot="icon" src="sap-icon://message-error"></ui5-icon>
4855
</ui5-input>
56+
<h3> 'input' test result</h3>
57+
<ui5-input id="inputLiveChangeResult" style="width:100%"></ui5-input>
4958

50-
<h3> Input valueState Success</h3>
51-
<ui5-input id="input3" style="width:100%" value-state="Success" placeholder="Success state ...">
52-
<ui5-icon data-ui5-slot="icon" src="sap-icon://message-success"></ui5-icon>
53-
</ui5-input>
59+
5460

55-
<h3> Input</h3>
56-
<ui5-input id="inputResult" style="width:100%">
57-
</ui5-input>
61+
<h3> Input test change</h3>
62+
<ui5-input id="inputChange" style="width:100%"> </ui5-input>
63+
<h3> Input test change result</h3>
64+
<ui5-input id="inputChangeResult" style="width:100%"></ui5-input>
5865

5966
<h3> Input readonly</h3>
6067
<ui5-input style="width:100%" value="Value can`t be changed" readonly></ui5-input>
@@ -139,14 +146,23 @@ <h3> Input type 'URL'</h3>
139146

140147
input2.addEventListener("input", function (event) {
141148
inputCounter += 1;
142-
inputResult.value = inputCounter;
149+
inputLiveChangeResult.value = inputCounter;
143150
});
144151

145152
myInput2.addEventListener("suggestionItemSelect", function (event) {
146153
var item = event.detail.item;
147154
suggestionSelectedCounter += 1;
148155
inputResult.value = suggestionSelectedCounter;
149156
});
157+
158+
var inputChangeResultCounter = 0;
159+
160+
inputChange.addEventListener("submit", function (event) {
161+
inputChange.value = "Changed via API";
162+
});
163+
inputChange.addEventListener("change", function (event) {
164+
inputChangeResult.value = ++inputChangeResultCounter;
165+
});
150166
</script>
151167
</body>
152168
</html>

packages/main/test/sap/ui/webcomponents/main/qunit/Input.qunit.js

-35
Original file line numberDiff line numberDiff line change
@@ -269,40 +269,5 @@ TestHelper.ready(function() {
269269
fixture.innerHTML = "";
270270
this.input = null;
271271
});
272-
273-
QUnit.test("type in input fires change onfocusout", function(assert) {
274-
assert.expect(1);
275-
276-
var input = this.getInput();
277-
var done = assert.async();
278-
279-
input.addEventListener("change", function(e){
280-
assert.ok(true, "change fired, when onfocusout");
281-
done();
282-
});
283-
284-
input.value = "abc";
285-
input.onfocusout();
286-
});
287-
288-
QUnit.test("type in input does not fire change when the initial value remains the same", function(assert) {
289-
assert.expect(1);
290-
291-
var input = this.getInput();
292-
var done = assert.async();
293-
294-
input.addEventListener("change", function(e){
295-
assert.ok(false, "change fired incorreclty, when onfocusout");
296-
});
297-
298-
input.value = "abc";
299-
input.value = "";
300-
input.onfocusout();
301-
302-
setTimeout(function() {
303-
assert.ok(true, "change event not fired on onfocusout, when the value did not changed");
304-
done();
305-
}, 200)
306-
});
307272
});
308273
});

packages/main/test/specs/DatePicker.spec.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -228,19 +228,44 @@ describe("Date Picker Tests", () => {
228228

229229
datepicker.innerInput.click();
230230
browser.keys("\b\b\b\b\b\b\b\b\b\b\b");
231-
datepicker.innerInput.setValue("Jan 8, 2015");
231+
datepicker.innerInput.keys("Jan 8, 2015");
232232
browser.findElementDeep("#dp1 >>> ui5-input >>> input").click(); //click elsewhere to focusout
233233

234234
assert.equal(browser.findElementDeep("#lbl").getHTML(false), "1", 'change has fired once');
235235

236236
datepicker.innerInput.click();
237237
browser.keys("\b\b\b\b\b\b\b\b\b\b\b");
238-
datepicker.innerInput.setValue("Jan 6, 2015");
238+
datepicker.innerInput.keys("Jan 6, 2015");
239239
browser.findElementDeep("#dp1 >>> ui5-input >>> input").click(); //click elsewhere to focusout
240240

241241
assert.equal(browser.findElementDeep("#lbl").getHTML(false), "2", 'change has fired once');
242242
});
243243

244+
it("change fires every time tomorrow is typed and normalized", () => {
245+
let tomorrowDate;
246+
const lblChangeCounter = browser.$("#lblTomorrow");
247+
const lblTomorrowDate = browser.$("#lblTomorrowDate");
248+
249+
datepicker.id = "#dp13";
250+
251+
// Type tomorrow.
252+
datepicker.innerInput.click();
253+
datepicker.innerInput.keys("tomorrow");
254+
255+
// Press Enter, store the date and delete it.
256+
datepicker.innerInput.keys("Enter");
257+
tomorrowDate = lblTomorrowDate.getHTML(false);
258+
browser.keys("\b\b\b\b\b\b\b\b\b\b\b\b\b");
259+
260+
// Type tomorrow and press Enter for the second time.
261+
datepicker.innerInput.keys("tomorrow");
262+
datepicker.innerInput.keys("Enter");
263+
264+
// Two change events should be fired and the date should twice normalized
265+
assert.equal(lblChangeCounter.getHTML(false), "2", 'change event is being fired twice');
266+
assert.equal(lblTomorrowDate.getHTML(false), tomorrowDate, 'tomorrow is normalazid to date twice as well');
267+
});
268+
244269
it("today value is normalized and correctly rounded to 00:00:00", () => {
245270
datepicker.id = "#dp9";
246271

0 commit comments

Comments
 (0)