Handsontable not loading when using webpack

Tags: #<Tag:0x00007f8b1d4a5358>

Thanks for a great product. I am using it non-commercially atm and like it a lot. Note, this is a c+p of my Stackoverflow question Handsontable not loading when using webpack

I’ve recently switched over to webpack from manually loading my JS libraries. Like others (see: #4053 and #3718) I’ve had to tamper with webpack.config.js, it now looks like so:

const path = require('path');
const webpack = require('webpack'); //to access built-in plugins

module.exports = {
    entry: './src/flaemi.js',
    output: {
        path: path.join(__dirname, 'static'),
        filename: 'flaemi.js'
        },
    module : {
        noParse: [path.join(__dirname, 'node_modules/handsontable/dist/handsontable.full.js')],
        rules: [
        {
            test: /\.css$/,
            use: [ { loader: 'style-loader' },
                   { loader: 'css-loader' } ]
        },
        {

            test: /\.(png|jpg|svg)$/,
            use: [ { loader  : 'url-loader?limit=30000&name=images/[name].[ext]' } ]
        },
        ]
    },
    plugins : [
        new webpack.ProvidePlugin({
           "$": "jquery",
           "jQuery": "jquery",
           "window.jQuery":"jquery"
        }),
    ],
    resolve : {
        alias: {
          // bind version of jquery-ui
          "jquery-ui": "jquery-ui/jquery-ui.js",
          // bind to modules;
          modules: path.join(__dirname, "node_modules"),
          'handsontable': path.join(__dirname, 'node_modules/handsontable/dist/handsontable.full.js'),
          'handsontable.css': path.resolve(__dirname, 'node_modules/handsontable/dist/handsontable.full.css')}
    }

};

I’m not sure if I need to include dependencies for Handsontable or not in package.json but I did anyway:

{
  "name": "flaemi",
  "version": "0.2.4",
  "description": "Flaemi is a geographical data visualizer.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "D3",
    "jQuery",
    "Bootstrap",
    "data",
    "visualization"
  ],
  "author": "Hrafn Malmquist",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^2.1.1",
    "file-loader": "^3.0.1",
    "style-loader": "^0.23.1",
    "url-loader": "^1.1.2",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0"
  },
  "dependencies": {
    "bootstrap": "^4.3.1",
    "bootstrap-select": "^1.13.8",
    "bootstrap4-toggle": "^3.4.0",
    "d3": "^5.9.2",
    "handsontable": "^7.0.2",
    "hot-formula-parser": "^3.0.0",
    "jquery": "^3.3.1",
    "jquery-ui": "^1.12.1",
    "moment": "^2.20.1",
    "numbro": "^2.1.2",
    "pikaday": "^1.5.1",
    "popper.js": "^1.14.7",
    "topojson": "^3.0.2"
  }
}

When I run webpack to bundle the js and include the standard demo “Headers” (JS fiddle available in link) handsontable grid it won’t load properly and there’s no error.

JS code is taken from that fiddle:

var dataObject = [
  {
    id: 1,
    flag: 'EUR',
    currencyCode: 'EUR',
    currency: 'Euro',
    level: 0.9033,
    units: 'EUR / USD',
    asOf: '08/19/2019',
    onedChng: 0.0026
  },
  {
    id: 2,
    flag: 'JPY',
    currencyCode: 'JPY',
    currency: 'Japanese Yen',
    level: 124.3870,
    units: 'JPY / USD',
    asOf: '08/19/2019',
    onedChng: 0.0001
  },
  {
    id: 3,
    flag: 'GBP',
    currencyCode: 'GBP',
    currency: 'Pound Sterling',
    level: 0.6396,
    units: 'GBP / USD',
    asOf: '08/19/2019',
    onedChng: 0.00
  },
  {
    id: 4,
    flag: 'CHF',
    currencyCode: 'CHF',
    currency: 'Swiss Franc',
    level: 0.9775,
    units: 'CHF / USD',
    asOf: '08/19/2019',
    onedChng: 0.0008
  },
  {
    id: 5,
    flag: 'CAD',
    currencyCode: 'CAD',
    currency: 'Canadian Dollar',
    level: 1.3097,
    units: 'CAD / USD',
    asOf: '08/19/2019',
    onedChng: -0.0005
  },
  {
    id: 6,
    flag: 'AUD',
    currencyCode: 'AUD',
    currency: 'Australian Dollar',
    level: 1.3589,
    units: 'AUD / USD',
    asOf: '08/19/2019',
    onedChng: 0.0020
  },
  {
    id: 7,
    flag: 'NZD',
    currencyCode: 'NZD',
    currency: 'New Zealand Dollar',
    level: 1.5218,
    units: 'NZD / USD',
    asOf: '08/19/2019',
    onedChng: -0.0036
  },
  {
    id: 8,
    flag: 'SEK',
    currencyCode: 'SEK',
    currency: 'Swedish Krona',
    level: 8.5280,
    units: 'SEK / USD',
    asOf: '08/19/2019',
    onedChng: 0.0016
  },
  {
    id: 9,
    flag: 'NOK',
    currencyCode: 'NOK',
    currency: 'Norwegian Krone',
    level: 8.2433,
    units: 'NOK / USD',
    asOf: '08/19/2019',
    onedChng: 0.0008
  },
  {
    id: 10,
    flag: 'BRL',
    currencyCode: 'BRL',
    currency: 'Brazilian Real',
    level: 3.4806,
    units: 'BRL / USD',
    asOf: '08/19/2019',
    onedChng: -0.0009
  },
  {
    id: 11,
    flag: 'CNY',
    currencyCode: 'CNY',
    currency: 'Chinese Yuan',
    level: 6.3961,
    units: 'CNY / USD',
    asOf: '08/19/2019',
    onedChng: 0.0004
  },
  {
    id: 12,
    flag: 'RUB',
    currencyCode: 'RUB',
    currency: 'Russian Rouble',
    level: 65.5980,
    units: 'RUB / USD',
    asOf: '08/19/2019',
    onedChng: 0.0059
  },
  {
    id: 13,
    flag: 'INR',
    currencyCode: 'INR',
    currency: 'Indian Rupee',
    level: 65.3724,
    units: 'INR / USD',
    asOf: '08/19/2019',
    onedChng: 0.0026
  },
  {
    id: 14,
    flag: 'TRY',
    currencyCode: 'TRY',
    currency: 'New Turkish Lira',
    level: 2.8689,
    units: 'TRY / USD',
    asOf: '08/19/2019',
    onedChng: 0.0092
  },
  {
    id: 15,
    flag: 'THB',
    currencyCode: 'THB',
    currency: 'Thai Baht',
    level: 35.5029,
    units: 'THB / USD',
    asOf: '08/19/2019',
    onedChng: 0.0044
  },
  {
    id: 16,
    flag: 'IDR',
    currencyCode: 'IDR',
    currency: 'Indonesian Rupiah',
    level: 13.83,
    units: 'IDR / USD',
    asOf: '08/19/2019',
    onedChng: -0.0009
  },
  {
    id: 17,
    flag: 'MYR',
    currencyCode: 'MYR',
    currency: 'Malaysian Ringgit',
    level: 4.0949,
    units: 'MYR / USD',
    asOf: '08/19/2019',
    onedChng: 0.0010
  },
  {
    id: 18,
    flag: 'MXN',
    currencyCode: 'MXN',
    currency: 'Mexican New Peso',
    level: 16.4309,
    units: 'MXN / USD',
    asOf: '08/19/2019',
    onedChng: 0.0017
  },
  {
    id: 19,
    flag: 'ARS',
    currencyCode: 'ARS',
    currency: 'Argentinian Peso',
    level: 9.2534,
    units: 'ARS / USD',
    asOf: '08/19/2019',
    onedChng: 0.0011
  },
  {
    id: 20,
    flag: 'DKK',
    currencyCode: 'DKK',
    currency: 'Danish Krone',
    level: 6.7417,
    units: 'DKK / USD',
    asOf: '08/19/2019',
    onedChng: 0.0025
  },
  {
    id: 21,
    flag: 'ILS',
    currencyCode: 'ILS',
    currency: 'Israeli New Sheqel',
    level: 3.8262,
    units: 'ILS / USD',
    asOf: '08/19/2019',
    onedChng: 0.0084
  },
  {
    id: 22,
    flag: 'PHP',
    currencyCode: 'PHP',
    currency: 'Philippine Peso',
    level: 46.3108,
    units: 'PHP / USD',
    asOf: '08/19/2019',
    onedChng: 0.0012
  }
];
var currencyCodes = ['EUR', 'JPY', 'GBP', 'CHF', 'CAD', 'AUD', 'NZD', 'SEK', 'NOK', 'BRL', 'CNY', 'RUB', 'INR', 'TRY', 'THB', 'IDR', 'MYR', 'MXN', 'ARS', 'DKK', 'ILS', 'PHP'];
var flagRenderer = function (instance, td, row, col, prop, value, cellProperties) {
  var currencyCode = value;
  while (td.firstChild) {
    td.removeChild(td.firstChild);
  }
  if (currencyCodes.indexOf(currencyCode) > -1) {
    var flagElement = document.createElement('DIV');
    flagElement.className = 'flag ' + currencyCode.toLowerCase();
    td.appendChild(flagElement);
  } else {
    var textNode = document.createTextNode(value === null ? '' : value);

    td.appendChild(textNode);
  }
};
var hotElement = document.querySelector('#hot');
var hotElementContainer = hotElement.parentNode;
var hotSettings = {
  data: dataObject,
  columns: [
    {
      data: 'id',
      type: 'numeric',
      width: 40
    },
    {
      data: 'flag',
			renderer: flagRenderer
    },
    {
      data: 'currencyCode',
      type: 'text'
    },
    {
      data: 'currency',
      type: 'text'
    },
    {
      data: 'level',
      type: 'numeric',
      numericFormat: {
        pattern: '0.0000'
      }
    },
    {
      data: 'units',
      type: 'text'
    },
    {
      data: 'asOf',
      type: 'date',
      dateFormat: 'MM/DD/YYYY'
    },
    {
      data: 'onedChng',
      type: 'numeric',
      numericFormat: {
        pattern: '0.00%'
      }
    }
  ],
  stretchH: 'all',
  width: 450,
  autoWrapRow: true,
  height: 487,
  maxRows: 22,
  rowHeaders: true,
  colHeaders: [
    'ID',
    'Country',
    'Code',
    'Currency',
    'Level',
    'Units',
    'Date',
    'Change'
  ]
};
var hot = new Handsontable(hotElement, hotSettings);

The strange behaviour is that the Handsontable UI or spreadsheet won’t be shown, only the v.7.0 license requirement text in the footer.

I’ve confirmed that the data loads by running console.log(hot.getSourceData()) which shows the correct data from above. If the data was loaded and rendered correctly it would show the header part as having the 8 column headers defined above like so:

<thead><tr><th class=""><div class="relative"><span class="colHeader cornerHeader">&nbsp;</span></div></th><th class=""><div class="relative"><span class="colHeader">ID</span></div></th><th class=""><div class="relative"><span class="colHeader">Country</span></div></th><th class=""><div class="relative"><span class="colHeader">Code</span></div></th><th class=""><div class="relative"><span class="colHeader">Currency</span></div></th><th class=""><div class="relative"><span class="colHeader">Level</span></div></th><th class=""><div class="relative"><span class="colHeader">Units</span></div></th><th class=""><div class="relative"><span class="colHeader">Date</span></div></th><th class=""><div class="relative"><span class="colHeader">Change</span></div></th></tr></thead>

what I get instead is:

<thead><tr><th class=""><div class="relative"><span class="colHeader cornerHeader">&nbsp;</span></div></th><th class=""><div class="relative"><span class="colHeader">ID</span></div></th></tr></thead>

but nothing is rendered and I only get the white default background. The only evidence of having included Handsontable to begin with is the license text printout.

And here’s the really weird part. By loading the page and interacted with it in Chrome by using Inspect it will sometimes render but I can discern no consistency in when that happens and anyway, that doesn’t make sense at all to me.

If I switch from the recent Handsontable v. 7.0.2 over to 6.2.2 the bundled JS is 300 kb lighter! and the error is different in that the ID column will show with listed ids and when one cell is selected the rest of the spreadsheet is rendered and everything is normal.

Thanks! Great that you like it.

You’re referring to very old issues. Why do you need noParse for Handsontable and module.alias entries? We’re using Handsontable daily in Webpack without any modifications. Do not include Handsontable dependencies in your package.json and v7.0.2 should be lighter too. They might be loaded twice in that case, depends which versions were installed.

If you see license text and can call API methods that means Handsontable was loaded. Problem might be somewhere else. How do you load CSS? We’re missing some details here. Can you upload sample project with your configuration to Github?

Hi Wojciech

Thanks for replying. It’s obvious I don’t really know how to use webpack. I went by the instructions on the old github issue threads because when I first tried to import and build Handsontable it gave a similar error.

As it happens the power unit on my workstation seems to have died so I can’t work on it just right now but I’ll be sure to follow up on this as soon as I can.

Best regards, Hrafn

Hi again Wojciech

I’ve now narrowed the issue down to having nothing to do with Webpack. But it does seem to be an issue with the collapse feature in Bootstrap 4.

If the Handsontable is nested inside a collapsed element it won’t load.

Here is a JSfiddle.

If you open the collapsed element and interact with the Handsontable for instance by right-clicking and Inspecting the (invisible) ID column it will all of a sudden load.

Strange

Hello,

Handsontable does not observe visibility yet (it can create performance issue if done badly). You need to redraw HoT when visibility state has changed, fortunately Bootstrap has events to do so. Just call hotInstance.refreshDimensions() when transition is over. Here’s an example without Bootstrap but shows the idea: https://jsfiddle.net/xfzqtw3p/1/