Search code examples
javascripthandsontable

can't get function's global execution context variable in runtime


i guess this is a pure vanilla's javascript issue.

i needed to override a function showDatepicker , i did it this way :

            const Base = (Handsontable.editors.DateEditor as any).prototype as any;
            const DateEditorHelper = Base.extend();

            DateEditorHelper.prototype.showDatepicker = function (event: any[]) {                    
                Base.showDatepicker.apply(this, event)

                this.$datePicker.config(this.getDatePickerConfig());

                var offset = this.TD.getBoundingClientRect();
                var dateFormat = this.cellProperties.dateFormat || this.defaultDateFormat;
                var datePickerConfig = this.$datePicker.config();
                var dateStr = void 0;
                var isMouseDown = this.instance.view.isMouseDown();
                var isMeta = event ? (0, _unicode.isMetaKey)(event.keyCode) : false;

                this.datePickerStyle.top = offset.top >= $(window).height() - 224 ? window.pageYOffset + offset.top - 224 + (0, _element.outerHeight)(this.TD) + 'px' : window.pageYOffset + offset.top + (0, _element.outerHeight)(this.TD) + 'px';
                this.datePickerStyle.left = window.pageXOffset + offset.left + 'px';

                this.$datePicker._onInputFocus = function () { };
                datePickerConfig.format = dateFormat;

                if (this.originalValue) {
                    dateStr = this.originalValue;

                    if ((0, _moment2.default)(dateStr, dateFormat, true).isValid()) {
                        this.$datePicker.setMoment((0, _moment2.default)(dateStr, dateFormat), true);
                    }

                    // workaround for date/time cells - pikaday resets the cell value to 12:00 AM by default, this will overwrite the value.
                    if (this.getValue() !== this.originalValue) {
                        this.setValue(this.originalValue);
                    }

                    if (!isMeta && !isMouseDown) {
                        this.setValue('');
                    }
                } else if (this.cellProperties.defaultDate) {
                    dateStr = this.cellProperties.defaultDate;

                    datePickerConfig.defaultDate = dateStr;

                    if ((0, _moment2.default)(dateStr, dateFormat, true).isValid()) {
                        this.$datePicker.setMoment((0, _moment2.default)(dateStr, dateFormat), true);
                    }

                    if (!isMeta && !isMouseDown) {
                        this.setValue('');
                    }
                } else {
                    // if a default date is not defined, set a soft-default-date: display the current day and month in the
                    // datepicker, but don't fill the editor input
                    this.$datePicker.gotoToday();
                }

                this.datePickerStyle.display = 'block';
                this.$datePicker.show();
            }

It's actually the original function's code in which i made some small modification. i thought the function's inner code variables will be evaluated in runtime (i'm not talking about the inner function scope variables as its arguments or variables declared in it but about the global variables to the execution context). Before the function get executed there are no errors at all (when the browser reads the function , it doesn't complain about that some variables are undefined) but when it runs , i got this error :

Uncaught ReferenceError: _unicode is not defined
    at eval (eval at DateEditorHelper.showDatepicker (module.js?v=64fd5db96274:40281), <anonymous>:1:1)
    at e.DateEditorHelper.showDatepicker (module.js?v=64fd5db96274:40281)

The overriden function is running in the same context as where the original function should runs , i don't know why a global variable is undefined.

The original function is a handsontable built-in editor (datePicker). The overriding idea i got it from this thread and this


Solution

  • You say that you copied the code from the original function and made minor changes. The problems seems to be that you're using a variable named _unicode. Are you defining that variable?

    When replacing a method's code with the 'original' code modified, you must made sure that any other variable/function referenced by the old code is also copied.

    You must take into account that the original method was defined in another context. Probably, the author didn't mean for this class to be derived, or to have its methods overriden.

    The advantage of modules (or IIFEs) is that you can define a public API while encapsulating the private parts of the implementation. Of course, this means that overriding methods or functions is much more difficult.

    In this case, the variable _unicode is clearly intended to be part of the private implementation. The fact that its name follows the convention of starting with an underscore is almost proof of that. It probably is defined in the same module as DatePickerHelper, or imported from another, but in any case I'm almost sure it is not exported, so you cannot have access to it and your code fails.

    For your override to work, you must either change the code to avoid using that _unicode variable, or define it yourself the same way it is done in the original code. Of course, depending of how it is defined you may end up having to 'copy' lots of that code, but that's an issue that any JavaScript programmer who has ever tried to override a method from an external library has suffered.

    Let's see an example of this 'private implementation':

    (function() {
      // this is a function I want to keep private.
      function giveMeHello() {
        return 'hello, ';
      }
      
      // this is the class I want to share
      class Greeting {
        greet(name) {
          console.log(giveMeHello() + name);
        }
      }
      
      window.Greeting = Greeting;
    })();
    
    const greet1 = new Greeting();
    greet1.greet('Oscar');  // outputs 'hello, Oscar'.
    
    // As a consumer of Greeting, I don't like the way if greets people. I want the name to be in uppercase
    // implementation is practically identical to original code
    Greeting.prototype.greet = function() {
      console.log(giveMeHello() + name.toUpperCase());
    };
    
    // Let's use it!
    greet1.greet('John'); // Oh! Error! giveMeHello is not defined!

    As you see, in this example I doing the same you are. But my implementation relies, just as the original one, on the giveMeHello function. But giveMeHello is no a global or public function. I haven't got access to it from the point in which I am overriding the function. Therefore, when I execute the method, it fails.

    If now I just copy the giveMeHello function before overriding the method, it will work.

    Do you get it now? If you don't, I suggest you to read more about scopes in JavaScript, something that is beyond the purpose of StackOverflow.