Select Editor tutorial has bugs

Tags: #<Tag:0x00007f8b23d2aa90>

Hi there.

I’m testing out the Select Editor tutorial, but I get the following error when I run the code after step 4:

_baseEditor.js?dc24:303 Uncaught TypeError: this.focus is not a function
    at Editor.beginEditing (_baseEditor.js?dc24:303)
    at EditorManager.openEditor (editorManager.js?14dd:222)
    at EditorManager.onCellDblClick (editorManager.js?14dd:570)
    at Object.eval (editorManager.js?14dd:103)
    at Settings.getSetting (settings.js?6242:163)
    at Walkontable.getSetting (core.js?1533:270)
    at Event.onMouseUp (event.js?8e85:312)
    at HTMLDivElement.eval (event.js?8e85:111)
    at HTMLDivElement.callbackProxy (eventManager.js?8c32:65)

Additionally, before this, I get:

SelectEditor.js?dbb5:59 Uncaught TypeError: handsontable__WEBPACK_IMPORTED_MODULE_2__.default.helper.isArray is not a function
    at Editor.SelectEditor.prepareOptions (SelectEditor.js?dbb5:59)
    at Editor.SelectEditor.prepare (SelectEditor.js?dbb5:42)
    at EditorManager.prepareEditor (editorManager.js?14dd:184)
    at Core._refreshBorders (core.js?cf0b:3905)
    at Selection.eval (core.js?cf0b:276)
    at eval (localHooks.js?5411:48)
    at arrayEach (array.js?3251:171)
    at Selection.runLocalHooks (localHooks.js?5411:47)
    at Selection.setRangeEnd (selection.js?eb42:405)
    at Selection.setRangeStart (selection.js?eb42:301)

I swapped that line out for Array.isArray()

I’m basically going through the tutorial and pasting the code as I find it:


import Handsontable from 'handsontable'

// TASK 1
var SelectEditor = Handsontable.editors.BaseEditor.prototype.extend();


export default SelectEditor




// TASK 2
SelectEditor.prototype.init = function() {
  // Create detached node, add CSS class and make sure its not visible
  this.select = document.createElement('SELECT');
  Handsontable.dom.addClass(this.select, 'htSelectEditor');
  this.select.style.display = 'none';

  // Attach node to DOM, by appending it to the container holding the table
  this.instance.rootElement.appendChild(this.select);
};




// TASK 3

// Create options in prepare() method
SelectEditor.prototype.prepare = function() {
  // Remember to invoke parent's method
  Handsontable.editors.BaseEditor.prototype.prepare.apply(this, arguments);

  var selectOptions = this.cellProperties.selectOptions;
  var options;

  if (typeof selectOptions == 'function') {
    options = this.prepareOptions(selectOptions(this.row,
      this.col, this.prop))
  } else {
    options = this.prepareOptions(selectOptions);
  }
  Handsontable.dom.empty(this.select);

  for (var option in options) {
    if (options.hasOwnProperty(option)) {
      var optionElement = document.createElement('OPTION');
      optionElement.value = option;
      Handsontable.dom.fastInnerHTML(optionElement, options[option]);
      this.select.appendChild(optionElement);
    }
  }
};

SelectEditor.prototype.prepareOptions = function(optionsToPrepare) {
  var preparedOptions = {};

  if (Handsontable.helper.isArray(optionsToPrepare)) {
    for(var i = 0, len = optionsToPrepare.length; i < len; i++) {
      preparedOptions[optionsToPrepare[i]] = optionsToPrepare[i];
    }
  } else if (typeof optionsToPrepare == 'object') {
    preparedOptions = optionsToPrepare;
  }

  return preparedOptions;
};




// TASK 4

SelectEditor.prototype.getValue = function() {
  return this.select.value;
};

SelectEditor.prototype.setValue = function(value) {
  this.select.value = value;
};

SelectEditor.prototype.open = function() {
  var width = Handsontable.dom.outerWidth(this.TD);
  var height = Handsontable.dom.outerHeight(this.TD);
  var rootOffset = Handsontable.dom.offset(this.instance.rootElement);
  var tdOffset = Handsontable.dom.offset(this.TD);

  // sets select dimensions to match cell size
  this.select.style.height = height + 'px';
  this.select.style.minWidth = width + 'px';

  // make sure that list positions matches cell position
  this.select.style.top = tdOffset.top - rootOffset.top + 'px';
  this.select.style.left = tdOffset.left - rootOffset.left + 'px';
  this.select.style.margin = '0px';

  // display the list
  this.select.style.display = '';
};

SelectEditor.prototype.close = function() {
  this.select.style.display = 'none';
};

In my Table’s column settings, I have:

    {
      data: 'name',
      type: 'text',
      editor: SelectEditor,
      selectOptions: ['option1', 'option2', 'option3']

    },

npm list reveals:

handsontable@7.0.2 

I need to get building a few custom editors ASAP, so it would be great to get a fast resolution on this!

Thanks,
Dave

OK, have tried this using ES6 (using the React example as a guide) but I get different errors:

Uncaught TypeError: Cannot read property 'selectOptions' of undefined
    at SelectEditor.prepare (SelectEditorClass.js?9b69:29)
    at EditorManager.prepareEditor (editorManager.js?14dd:184)
    at Core._refreshBorders (core.js?cf0b:3905)
    at Selection.eval (core.js?cf0b:276)
    at eval (localHooks.js?5411:48)
    at arrayEach (array.js?3251:171)
    at Selection.runLocalHooks (localHooks.js?5411:47)
    at Selection.setRangeEnd (selection.js?eb42:405)
    at Selection.setRangeStart (selection.js?eb42:301)
    at mouseDown (mouseEventHandler.js?e5cb:71)

At the point of error, this is:

Here’s the code:

import Handsontable from 'handsontable'

export default class SelectEditor extends Handsontable.editors.BaseEditor {

  constructor (props) {
    super(props)
  }


  init () {
    super.init(arguments)
    // Create detached node, add CSS class and make sure its not visible
    this.select = document.createElement('SELECT')
    Handsontable.dom.addClass(this.select, 'htSelectEditor')
    this.select.style.display = 'none'

    // Attach node to DOM, by appending it to the container holding the table
    this.instance.rootElement.appendChild(this.select)
  }

  prepare () {
    // Remember to invoke parent's method
    super.prepare(arguments)

    const selectOptions = this.cellProperties.selectOptions
    let options

    if (typeof selectOptions == 'function') {
      options = this.prepareOptions(selectOptions(this.row,
        this.col, this.prop))
    } else {
      options = prepareOptions(selectOptions)
    }
    Handsontable.dom.empty(this.select)

    for (let option in options) {
      if (options.hasOwnProperty(option)) {
        const optionElement = document.createElement('OPTION')
        optionElement.value = option
        Handsontable.dom.fastInnerHTML(optionElement, options[option])
        this.select.appendChild(optionElement)
      }
    }
  }

  getValue () {
    return this.select.value
  }

  setValue (value) {
    this.select.value = value
  }

  open () {
    const width = Handsontable.dom.outerWidth(this.TD)
    const height = Handsontable.dom.outerHeight(this.TD)
    const rootOffset = Handsontable.dom.offset(this.instance.rootElement)
    const tdOffset = Handsontable.dom.offset(this.TD)

    // sets select dimensions to match cell size
    this.select.style.height = height + 'px'
    this.select.style.minWidth = width + 'px'

    // make sure that list positions matches cell position
    this.select.style.top = tdOffset.top - rootOffset.top + 'px'
    this.select.style.left = tdOffset.left - rootOffset.left + 'px'
    this.select.style.margin = '0px'

    // display the list
    this.select.style.display = ''
  }

  close () {
    this.select.style.display = 'none'
  }

}

function prepareOptions (optionsToPrepare) {
  let preparedOptions = {}

  if (Handsontable.helper.isArray(optionsToPrepare)) {
    let i = 0
    const len = optionsToPrepare.length
    for (; i < len; i++) {
      preparedOptions[optionsToPrepare[i]] = optionsToPrepare[i]
    }
  } else if (typeof optionsToPrepare == 'object') {
    preparedOptions = optionsToPrepare
  }

  return preparedOptions
}

OK, it looks like the ES5 tutorial is missing a bit of code.

After the getValue, setValue, open, close function declarations, it’s missing:

SelectEditor.prototype.focus = function () {
  this.select.focus()
}

Changing the isArray to Array.isArray is also needed (IE9+), and in fact prepareOptions does not need to be part of the class definition:

function prepareOptions (optionsToPrepare) {
  var preparedOptions = {}

  if (Array.isArray(optionsToPrepare)) {
    for (var i = 0, len = optionsToPrepare.length; i < len; i++) {
      preparedOptions[optionsToPrepare[i]] = optionsToPrepare[i]
    }
  } else if (typeof optionsToPrepare == 'object') {
    preparedOptions = optionsToPrepare
  }

  return preparedOptions
}
...
  if (typeof selectOptions == 'function') {
    options = prepareOptions(selectOptions(this.row,
      this.col, this.prop))
  } else {
    options = prepareOptions(selectOptions)
  }

Now, just to get the class version working!

OK, now I’m attempting to add the keyboard listeners, but not having any joy in getting them to fire.

Where it says:

this.instance.addHook('beforeKeyDown', onBeforeKeyDown);

onBeforeKeyDown never fires when I am using the select:

If I replace the handler with just console.log I get no output at all when using the keyboard.

(It doesn’t seem to matter if this is a single or multiselect)

This seems to be needed as on a mac at least there is no way to exit the select without adding a listener to do this manually.

All help appreciated.

So it seems they do fire, but NOT when the select is focused.

Moving the handlers to native DOM handlers, and the function runs but I get:

SelectEditor.js?dbb5:86 Uncaught TypeError: handsontable__WEBPACK_IMPORTED_MODULE_1__.default.dom.enableImmediatePropagation is not a function
    at HTMLSelectElement.onSelectKeydown (SelectEditor.js?dbb5:86)

It seems that Handsontable.dom.enableImmediatePropagation is undefined.

I’ve either missed something, or my guess is that this tutorial is out of date and needs updating?

Last question (and I know help is coming Aleksandra!) but is I’ve managed to do something with keyboard focus, albeit outside the HOT API.

I update the value of the editor with an update() function using finishEditing() as outlined in the docs:

SelectEditor.prototype.open = function () {
  positionElement(this, this.select)
  this.select.addEventListener('keydown', this.onSelectKeydown)
}

SelectEditor.prototype.update = function (value) {
  this.setValue(value)
  this.finishEditing()
  // focus does not go back to the grid
}

SelectEditor.prototype.close = function () {
  this.select.style.display = 'none'
  this.select.removeEventListener('keydown', this.onSelectKeydown)
}

However, focus does not go back to the grid like it does in the PasswordEditor example.

I’m feeling rather stumped right now.

Hopefully that’s my last question.

Probably worth doing a summary because there’s been so many comments:

I’m looking get working:

  • an ES6 class component
  • which creates a custom element (for the example, an HTML select is fine)
  • its values are passed using a property declared in columns
  • keyboard interaction should focus, navigate, update and exit
  • the main table takes control after

I’m fairly sure I understand what all the functions do having read through the docs so many times, I just need the code to work.

Cheers,
Dave

Hey Dave,

Sorry for keeping you waiting. I don’t know why I haven’t got a notification on this subject :thinking:

What is your progress? Is there anything I can do to help now?

Hi Aleksandra,

Thanks for picking this up!

Still the same situation as I’ve outlined in the thread TBH.

Did you get a chance to read through? I tried to document everything as I found it.

The next step would be to create a JSFiddle, but because I think there may be some issues with differences between the docs and the API, I thought this might be a waste of time.

Would you like to read through then get back to me?

PS. I am at your disposal all day - if you need me to do something - just ask!

Yup, I think that this tutorial might be outdated. I remember that our devs checked all the demos last year but now sure if they’ve changed anything in the ~custom examples. And I can’t see enableImmediatePropagation nowhere in the code.

I’ve asked our developer to check what we’re missing.

If we could have a working example, preferably as an ES6 class, that would suit us right now.

It looks like this https://github.com/handsontable/handsontable/blob/master/src/editors/selectEditor.js is the best source of inspiration. The code is in ES6 while the tutorial is written with ES5

Thanks, we’ll take a look

FYI, we got it working!

1 Like