Getting Chart.js to Play Nicely With Magento

Published by John on March 7, 2017 Under Javascript, Magento

Magento LogoWhile working on a site running Magento version 1.9, I was running into some issues getting Chart.js to work. When attempting to load a chart, I got the following error:

TypeError: ticks is undefined

This was then followed up with a:

TypeError: element._view is undefined

After a bit of debugging, I determined that the issue was related to a conflict between prototype.js, which Magento 1.X still makes fairly heavy use of and Chart.js.

Specifically, Chart.js uses the map() function on an array to help draw the y-axis points(ticks,) using a callback function that makes use of the three parameters available with the map function: the value, index, and array being worked on.

However, when calling map() on a site with Prototype.js version 1.7 running, only the first two variables get passed and the third, in this case the ticks array, does not.

So, if you create a callback using the callback(value, index, my_array), which has been part of the Javascript Spec since at least 2011, and then try to access the my_array variable it results in an error because my_array is not being passed to the callback function.

The problem stems from Prototype.js modifying the DOM and apparently getting it wrong. When you remove some of the modifications of Protoype’s Enumerable class, which adds features/functions to arrays, the error no longer is generated and Chart.js loads correctly. Specifically, during testing it seemed that just removing the collect(iterator, context) function from the Enumerable class was sufficient to fix the error in Chart.js, but of course that would likely break other things, so doesn’t actually fix the problem.

The Problem of Modifying the DOM

This is a fairly common complaint about Prototype.js.

As opposed to other libraries, such as jQuery, Prototype directly modifies/extends the standard DOM to add features. For example, prototype.js extends arrays to add a number of functions, such as each().

So, in protoype, you would do something like this:


var my_array = ["first", "second", "third"];

my_array.each(function(index){alert(index);});

Where in jQuery, you would do something like:


var my_array = ["first", "second", "third"];

jQuery(my_array).each(function(index){alert(index);});

The difference being that in Prototype.js, the each function gets added as what essentially beomes a native method of the Array, while in jQuery, you use the jQuery object to access the each function.

While in some cases this can be neat/useful, when you over-ride the DOM directly and change how standard functions/variables work, you are inevitably going to break somethings if you get it wrong or your modifications deviate from the standard. This is fine if your site is built 100% using prototype, but if you start introducing other scripts that assume they will be able to use functions that are part of the standard Javascript Spec, you are probably going to have a bad time.

The Fix

The broken map function is apparently specific to the 1.7 branch of Prototype.js. I tested in the most recent version, 1.7.3 and the map function appears to work.

So, you could try to upgrade to the latest version of prototype.js, which has not been updated in 17 months at the time of this writing, and it should work. I tried this briefly in Magento, just to see if it broke anything, and it appears to work okay. However, 1.7 is quite different from 1.7.3, so I am hesitant to do this in a production environment without a great deal of testing.

Another option is to write your own callback function, which does not use the my_array parameter of the callback function. This made sense for me, as I needed to modify it anyway. I am using Chart.js to display the number of orders per day of the month. So, I ended up adding a custom userCallback function to prevent decimals from being shown on the y-axis of the line graph.

My modified callback function is below. Note that the userCallback function only uses two parameters, the value and the index:

var my_chart_canvas = document.getElementById("my_chart_canvas");
var my_chart = new Chart(my_chart_canvas, {
    type: 'line',
    data: {
        labels: [1,2,3],
        datasets: [{
            label: '# of Orders',
            data: [1,2,3],
            borderWidth: 1
        }]
    },
    options: {
        scales: {
            yAxes: [{
                ticks: {
                    beginAtZero:true,
                    userCallback: function(label, index) {
				if (Math.floor(label) === label) {
					return label;
				}
                     }  
                },
                scaleLabel: {
				display: true,
				labelString: 'Orders'
      }
            }],
            
          xAxes: [{
                scaleLabel: {
				display: true,
				labelString: 'Day of Month'
      }
            }]
        }
    }
});

2 Comments |

Comments:

  1. P'Moo on Apr 14, 2017

    Thanks a million… you really saved my day with that one !

    I confirm that it works with the latest version of Prototype 1.7.3

  2. Patrick on Jun 20, 2017

    Hi,

    Thank you for this tip! that helped me a lot !

Add a Comment