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

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
diff --git a/app/assets/javascripts/discourse/components/text-field.js b/app/assets/javascripts/discourse/components/text-field.js
index 23440b2..bb57f25 100644
--- a/app/assets/javascripts/discourse/components/text-field.js
+++ b/app/assets/javascripts/discourse/components/text-field.js
@@ -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);
 
diff --git a/test/javascripts/components/text-field-test.js b/test/javascripts/components/text-field-test.js
index dae5139..7b26ddd 100644
--- a/test/javascripts/components/text-field-test.js
+++ b/test/javascripts/components/text-field-test.js
@@ -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");
+  }
+});

GitHub sha: 4f42bb1f

This commit appears in #9362 which was merged by eviltrout.