Tuesday, February 3, 2015

Sneaky JavaScript Technics I

As a technical support, I had to deal with situations where a quick and dirty fix was necessary to get things done while waiting for the official patch.
I learned a lot by doing so and I would like to share some of these stories with you.

The context

Maybe you already know it, but a input type=hidden field does not generate any change event.

For some reasons, I had to detect when the value of one specific hidden field was changed in order to trigger the execution of a custom function. I had no way to change the code that was setting the value of the field so I had to find a solution that was field-centric.

A quick note before going any further: I don't pretend that any of the solutions below is the right thing to do but, as a matter of fact, I learned a lot by trying and doing mistakes. The key here is that I tried several ways to solve my problem and I finally kept the one that suited my needs at that moment (knowing that a more stable solution would come).

First attempt: active monitoring

My first thought was to monitor the field using a timer in order to check if the field changed.

(function () { "use strict"; var _fieldHandle = document.getElementById("hiddenFieldID"), lastValue = _fieldHandle.value; function actionToBeTriggered(/*value*/) { // What needs to be done when the value changed } function didTheHiddenFieldChanged() { if (_fieldHandle.value !== lastValue) { lastValue = _fieldHandle.value; actionToBeTriggered(lastValue); } } window.setTimer(didTheHiddenFieldChanged, 10); })();

Considering that this script is loaded (and evaluated) after the field has been created, it has two big disadvantages:

  • As for any polling mechanism, it uselessly consumes CPU on a regular basis.
  • Because of the timer use, the detection occurs only when the JavaScript engine has some free CPU time to executes the function.

Indeed, the following code will generate trouble: document.getElementById("hiddenFieldID").value = 1; // Some code document.getElementById("hiddenFieldID").value = 2;

Only the value "2" would be seen.

Worst case, let say that the last value detected was "2", then no change would be detected.

Second attempt: hooking "set"

After reading an interesting article about one of the new features of the ECMAScript 5.1: Object.defineProperty, I wanted to see if Chrome would allow me to redefine the value property of the hidden field.

I tried the following:

(function () { "use strict"; function actionToBeTriggered(/*value*/) { // What needs to be done when the value changed } Object.defineProperty(document.getElementById("hiddenFieldID"), "value", { get: function () { return 0; }, set: actionToBeTriggered }); })();

And... it worked.

This solution, beside the questionable support of such a mechanism, allowed me to hook a callback that was called every time the value was changed.

Conclusion

Once the patch has been delivered, we decided that the hidden field was no more necessary and replaced it with a direct call to the function.

2 comments:

  1. this could end up being quite an interesting resource for discussion, thanks for sharing and keep it up. The effort is appreciated!

    ReplyDelete
  2. regarding "hooking" set you can see this script by paul irish
    https://github.com/paulirish/break-on-access

    ReplyDelete