diff --git a/hsp/rt/eltnode.js b/hsp/rt/eltnode.js index 4d84dec..2b41aec 100644 --- a/hsp/rt/eltnode.js +++ b/hsp/rt/eltnode.js @@ -20,6 +20,7 @@ var doc = require("../document"); var TNode = require("./tnode").TNode; var hsp = require("../rt"); var gestures = require("../gestures/gestures"); +//var log = require("./log"); var booleanAttributes = { async: true, @@ -71,6 +72,7 @@ var EltNode = klass({ } this.gesturesEventHandlers = null; this.needSubScope = (needSubScope===1); + this.preventRefresh=false; // if true will prevent the field from being refreshed during the typing sequence }, $dispose : function () { @@ -179,8 +181,8 @@ var EltNode = klass({ } if (this.isInput) { - // ensure we listen to click, keydown and keyup - var et, inputEvts = ["click", "keydown", "keyup"]; + // ensure we listen to click, focus and keyup + var et, inputEvts = ["click","focus","input","keyup"]; for (var idx in inputEvts) { et = inputEvts[idx]; if (!evts[et]) { @@ -204,15 +206,32 @@ var EltNode = klass({ // if the element is an input tag we synchronize the value if (this.isInput && this.inputModelExpIdx) { + this.preventRefresh=false; var exp = this.eh.getExpr(this.inputModelExpIdx); if (exp.setValue) { - var v = this.node.value, tp = this.node.type; - if (tp === "checkbox") { - v = this.node.checked; + if (et==="keydown") { + // value is updated on keyup - so we must no refresh the field if the model + // is updated during keydown, otherwise the value is lost + this.preventRefresh=true; + } else if (et==="input" || et==="keyup" || et==="click" || et==="focus") { + // push the field value to the data model + // note: when the input event is properly implemented we don't need to listen to keyup + // but IE8 and IE9 don't implement it completely - thus the need for keyup + var v = this.node.value, tp = this.node.type; + if (tp === "checkbox") { + v = this.node.checked; + } + + this._lastValue = v; // to avoid refreshing the field and move the cursor + var currentValue=exp.getValue(this.vscope,this.eh); + //log("[EltNode] handleEvent("+et+"): previous model value:["+currentValue+"] new value (from input):["+v+"]"); + // if the value is already set no need to set it again and force a resync + if (v!==currentValue) { + exp.setValue(this.vscope, v); + // force refresh to resync other fields linked to the same data immediately + hsp.refresh(); + } } - this._lastValue = v; // to avoid refreshing the field and move the cursor - exp.setValue(this.vscope, v); - hsp.refresh(); // to force synchronous change } } @@ -320,12 +339,17 @@ var EltNode = klass({ } } else { if (this._lastValue !== v1) { - nd.value = v1; + // value change has not been triggered by typing in this field + + if (!this.preventRefresh && v1!=nd.value) { + //only update if value is changing and if we are not between 'onkeydown' and 'onkeyup' + //log("[EltNode] Node value update: current value:["+nd.value+"] new value:["+v1+"]"); + nd.value = v1; + } } this._lastValue = null; } } - } }); diff --git a/hsp/rt/exphandler.js b/hsp/rt/exphandler.js index 09ec505..c958013 100644 --- a/hsp/rt/exphandler.js +++ b/hsp/rt/exphandler.js @@ -213,11 +213,17 @@ var DataRefExpr = klass({ if (ppl < 1) { return; // this case should not occur } - for (var i = 0; ppl - 1 > i; i++) { - v = v[this.path[i]]; - if (v === undefined) { - goahead = false; - break; + if (ppl===1) { + if (!this.isLiteral) { + v=ExpHandler.getScopeOwner(this.path[0], vscope); + } + } else { + for (var i = 0; ppl - 1 > i; i++) { + v = v[this.path[i]]; + if (v === undefined) { + goahead = false; + break; + } } } if (goahead) { diff --git a/public/samples/inputsample/description.md b/public/samples/inputsample/description.md index f00ae50..e20bac3 100644 --- a/public/samples/inputsample/description.md +++ b/public/samples/inputsample/description.md @@ -1,12 +1,12 @@ -Hashspace automatically listens to the main change events of its input elements (*click*, *keypress* and *keyup*) in order to transparently synchronize the input values with the data referenced through the value expression. +Hashspace automatically listens to the main change events of its input elements (*click*, *focus* and *keyup*) in order to transparently synchronize the input values with the data referenced through the value expression. The following example shows the same value referenced by two several text fields and a read-only span: [#output] -For the time being, only simple path expressions are supported to reference input values in a bi-directional way. - You can note that *radio* inputs have to use a **model** attribute in order to be bind their selection to the data-model. All radio buttons referencing the same model property will automatically belong the same group - and they don't need to have the same *name* attribute as in classical HTML forms. For the sake of consistency the **model** attribute can also be used on all input types, even if the *value* attribute can be used as well, as shown in the previous example. + +**Note:** When users type in an input field, **hashspace synchronizes the data model on the keyup event**. As a consequence, applications that need to validate (and potentially change) user inputs should listen to this **keyup** event, and not to the *keydown* event as text fields are not updated yet at this stage. diff --git a/public/test/rt/input.spec.hsp b/public/test/rt/input.spec.hsp index 48dfb02..4ffb691 100644 --- a/public/test/rt/input.spec.hsp +++ b/public/test/rt/input.spec.hsp @@ -67,9 +67,9 @@ describe("Input Elements", function () { expect(input2.node.value).to.equal(v2); // change the value from input1 (value attribute) - var v3 = "bar"; + var v3 = "bar2"; input1.node.value = v3; - fireEvent("click",input1.node); // to simulate change + fireEvent("keyup",input1.node); // to simulate change expect(input1.node.value).to.equal(v3); expect(input2.node.value).to.equal(v3); expect(d.comment).to.equal(v3); @@ -77,7 +77,7 @@ describe("Input Elements", function () { // change the value from input2 (model attribute) var v4 = "blah"; input2.node.value = v4; - fireEvent("click",input2.node); // to simulate change + fireEvent("keyup",input2.node); // to simulate change expect(input1.node.value).to.equal(v4); expect(input2.node.value).to.equal(v4); expect(d.comment).to.equal(v4); diff --git a/public/test/rt/subtemplates1.spec.hsp b/public/test/rt/subtemplates1.spec.hsp index bc73ea6..12e01b7 100644 --- a/public/test/rt/subtemplates1.spec.hsp +++ b/public/test/rt/subtemplates1.spec.hsp @@ -200,7 +200,7 @@ describe("Sub-template insertion", function () { expect(input.value).to.equal("some text"); input.value = "foo"; - fireEvent("keydown",input); // triggers change propagation + fireEvent("keyup",input); // triggers change propagation // change must have been propagated to parent scope expect(dm.text).to.equal("foo"); @@ -223,7 +223,7 @@ describe("Sub-template insertion", function () { expect(input.value).to.equal("some text"); input.value = "bar"; - fireEvent("keydown",input); + fireEvent("keyup",input); // change must have been propagated to parent scope expect(dm.attributes.value).to.equal("bar"); @@ -269,7 +269,7 @@ describe("Sub-template insertion", function () { expect(input.value).to.equal("new text"); input.value = "bar"; - fireEvent("keydown",input); + fireEvent("keyup",input); expect(dm.value).to.equal("bar"); n.$dispose();