DEV: Support for onChange on {{text-field}} (#9362)

* DEV: Support for `onChange` on `{{text-field}}`

This will automatically be debounced and only fired when the value
changes.

There is also `onChangeImmediate` which is not debounced in case you
need that, but in almost all cases when observing text in an element you
should debounce.

* Add cancel for timer
This commit is contained in:
Robin Ward 2020-04-07 11:41:21 -04:00 committed by GitHub
parent 09145e68cd
commit 4f42bb1fd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 73 additions and 0 deletions

View File

@ -1,8 +1,14 @@
import { TextField } from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import { siteDir, isRTL, isLTR } from "discourse/lib/text-direction";
import { next, debounce, cancel } from "@ember/runloop";
const DEBOUNCE_MS = 500;
export default TextField.extend({
_prevValue: null,
_timer: null,
attributeBindings: [
"autocorrect",
"autocapitalize",
@ -11,6 +17,28 @@ export default TextField.extend({
"dir"
],
didReceiveAttrs() {
this._super(...arguments);
this._prevValue = this.value;
},
didUpdateAttrs() {
this._super(...arguments);
if (this._prevValue !== this.value) {
if (this.onChangeImmediate) {
next(() => this.onChangeImmediate(this.value));
}
if (this.onChange) {
cancel(this._timer);
this._timer = debounce(this, this._debouncedChange, DEBOUNCE_MS);
}
}
},
_debouncedChange() {
next(() => this.onChange(this.value));
},
@discourseComputed
dir() {
if (this.siteSettings.support_mixed_text_direction) {
@ -23,6 +51,11 @@ export default TextField.extend({
}
},
willDestroyElement() {
this._super(...arguments);
cancel(this._timer);
},
keyUp(event) {
this._super(event);

View File

@ -44,3 +44,43 @@ componentTest("sets the dir attribute to ltr for English text", {
assert.equal(find("input").attr("dir"), "ltr");
}
});
componentTest("supports onChange", {
template: `{{text-field class="tf-test" value=value onChange=changed}}`,
beforeEach() {
this.called = false;
this.newValue = null;
this.set("value", "hello");
this.set("changed", v => {
this.newValue = v;
this.called = true;
});
},
async test(assert) {
await fillIn(".tf-test", "hello");
assert.ok(!this.called);
await fillIn(".tf-test", "new text");
assert.ok(this.called);
assert.equal(this.newValue, "new text");
}
});
componentTest("supports onChangeImmediate", {
template: `{{text-field class="tf-test" value=value onChangeImmediate=changed}}`,
beforeEach() {
this.called = false;
this.newValue = null;
this.set("value", "old");
this.set("changed", v => {
this.newValue = v;
this.called = true;
});
},
async test(assert) {
await fillIn(".tf-test", "old");
assert.ok(!this.called);
await fillIn(".tf-test", "no longer old");
assert.ok(this.called);
assert.equal(this.newValue, "no longer old");
}
});