HTML Canvas error: 'getContext' of null - in AngularJS App

Hi EE!

We use have been using a HTML canvas to print out orders and 99% of the time it works fine. but randomly...

error.PNG
Its like it has lost the canvas ID, but was working fine. Below is our angularJS orderPrinterFactory.js code that writes the canvas each print (which can be multiple as once)

We are really stuck with this one! Any help would be great!

(function () {

    var injectParams = ['$rootScope', '$http', 'ngDexie', '$q', 'tabFactory'];

    var orderPrinterFactory = function ($rootScope, $http, ngDexie, $q, tabFactory) {
                
        function drawReceipt(canvas, receiptData, products, note) {
            // get context of canvas
            $rootScope.consolelog(receiptData)
            //if (canvas.getContext) {
                if(canvas === null){
                     canvas = document.getElementById('orderCanvas');
                }
                var context = canvas.getContext('2d');
                canvas.height = '20000';
                var fontType = '\'Arial\', sans-serif';
                var x = parseInt('0');
                var y = parseInt('30');
                var titleFont = 'normal ' + 'normal ' + 'bold ' + 40 + 'px ' + fontType; //(true or false) | italics (italic or normal), caps (small-caps or normal), bold (bold or normal), size, font
                var tabFont = 'normal ' + 'normal ' + 'bold ' + 78 + 'px ' + fontType; //(true or false) | italics (italic or normal), caps (small-caps or normal), bold (bold or normal), size, font
                var subTitleFont = 'normal ' + 'normal ' + 'normal ' + 36 + 'px ' + fontType; //(true or false) | italics (italic or normal), caps (small-caps or normal), bold (bold or normal), size, font
                var normalFont = 'normal ' + 'normal ' + 'normal ' + 36 + 'px ' + fontType; //(true or false) | italics (italic or normal), caps (small-caps or normal), bold (bold or normal), size, font
                var normalBoldFont = 'normal ' + 'normal ' + 'bold ' + 36 + 'px ' + fontType; //(true or false) | italics (italic or normal), caps (small-caps or normal), bold (bold or normal), size, font
                var smallFont = 'normal ' + 'normal ' + 'normal ' + 24 + 'px ' + fontType; //(true or false) | italics (italic or normal), caps (small-caps or normal), bold (bold or normal), size, font
                var rWidth = parseInt('576');
                var smallLine = parseInt('15');
                var line = parseInt('30');
                var centerX = parseInt('288');
                var breakLine = '----------------------------------------------------------------------';
                var currentTime = new Date()
                var seconds = currentTime.getSeconds()
                if (seconds < 10) {
                    seconds = "0" + seconds
                }
                var minutes = currentTime.getMinutes()
                if (minutes < 10) {
                    minutes = "0" + minutes
                }
                var hours = currentTime.getHours()
                if (hours > 11) {
                    var timeFormat = "PM";
                } else {
                    var timeFormat = "AM";
                }
                if (hours > 12) {
                    hours = hours - 12
                }
                var month = currentTime.getMonth() + 1
                if (month < 10) {
                    month = "0" + month
                }
                var day = currentTime.getDate()
                var year = currentTime.getFullYear()
                var date = day + "/" + month + "/" + year;
                var time = hours + ":" + minutes + ":" + seconds + ' ' + timeFormat;
                
                y = y + line;
                if (receiptData.transaction.tab.tabNumber) {
                    context.textAlign = 'center';
                    context.font = tabFont;
                    context.textBaseline = 'alphabetic';
                    context.fillText('Tab ' + receiptData.transaction.tab.tabNumber, centerX, y);
                    y = y + line + line;
                }
                if (receiptData.transaction.tab.tableNumber) {
                    context.textAlign = 'center';
                    context.font = tabFont;
                    context.textBaseline = 'alphabetic';
                    context.fillText('Table ' + receiptData.transaction.tab.tableNumber, centerX, y);
                    y = y + line + line;
                }
                
                context.textAlign = 'center';
                context.font = smallFont;
                context.textBaseline = 'alphabetic';
                context.fillText('STAFF MEMBER: ' + receiptData.staff.toUpperCase(), centerX, y);
                y = y + line;

                context.textAlign = 'center';
                context.font = subTitleFont;
                context.textBaseline = 'alphabetic';
                context.fillText(breakLine, centerX, y);
                y = y + line + line;

                context.textAlign = 'start';
                context.font = normalFont;
                context.textBaseline = 'alphabetic';
                context.fillText(date, 0, y);
                context.textAlign = 'end';

                context.font = normalFont;
                context.textBaseline = 'alphabetic';
                context.fillText(time, rWidth, y);
                y = y + line + line;

                context.textAlign = 'start';
                context.font = normalBoldFont;
                context.textBaseline = 'alphabetic';
                context.fillText('PRODUCTS', 0, y);
                y = y + line + smallLine;

                $rootScope.consolelog(products)

                for (var i = 0; i < products.length; i++) {

                    context.textAlign = 'start';
                    context.font = normalFont;
                    context.textBaseline = 'alphabetic';
                    context.fillText(products[i].quantity + ' x ' + products[i].buttonName, 0, y);

                    if(products[i].modifiers) {
                        
                        y = y + line;
                        
                        for (var m = 0; m < products[i].modifiers.length; m++) {

                            context.textAlign = 'start';
                            context.font = smallFont;
                            context.textBaseline = 'alphabetic';
                            context.fillText('[M] ' + products[i].modifiers[m].name, 60, y);

                            y = y + line;

                            if(products[i].subModifiers) {
                                for (var s = 0; s < products[i].subModifiers.length; s++) {
                                    if(products[i].subModifiers[s].parent === products[i].modifiers[m].id){
                                        context.textAlign = 'start';
                                        context.font = smallFont;
                                        context.textBaseline = 'alphabetic';
                                        context.fillText('- ' + products[i].subModifiers[s].name, 100, y);

                                        y = y + line;

                                        if(products[i].subSubModifiers) {
                                            for (var ss = 0; ss < products[i].subSubModifiers.length; ss++) {
                                                if(products[i].subSubModifiers[ss].parent === products[i].subModifiers[s].id){
                                                    context.textAlign = 'start';
                                                    context.font = smallFont;
                                                    context.textBaseline = 'alphabetic';
                                                    context.fillText('- ' + products[i].subSubModifiers[ss].name, 140, y);

                                                    y = y + line;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    y = y + line + smallLine;

                }
                
                if(note != ''){
                    context.textAlign = 'start';
                    context.font = normalBoldFont;
                    context.textBaseline = 'alphabetic';
                    context.fillText('NOTE', 0, y);
                    y = y + line + smallLine;

                    context.textAlign = 'start';
                    context.font = normalFont;
                    context.textBaseline = 'alphabetic';
                    context.fillText(note, 0, y);
                    y = y + line + smallLine;
                }
                
                var imgData = context.getImageData(0, 0, rWidth, y);
                canvas.height = y;
                context.putImageData(imgData, 0, 0);

                return y;
            //}

        };

        function lookup(printQueueId, productToPrintQueue) {
            for (var i = 0, len = productToPrintQueue.length; i < len; i++) {
                if (productToPrintQueue[i].pqid === printQueueId)
                    return true;
            }
            return false;
        }

        var orderPrinter = {};

        // print canvas
        orderPrinter.orderPrintCanvas = function (receiptData, note) {
            var duplicatePrinters = []
            var productToPrintQueue = []
            angular.forEach(receiptData.transaction.products, function (value) { //loop through transaction products
                $rootScope.consolelog(value)
                ngDexie.get('products', value.id).then(function (product) { //foreach product get that products information from indexeddb
                    $rootScope.consolelog(product)
                    var printQueueId = product.printQueueId;
                    if (lookup(printQueueId, productToPrintQueue)) {
                        for (var i = 0, l = productToPrintQueue.length; i < l; i++) {
                            if (productToPrintQueue[i].pqid === printQueueId) {
                                productToPrintQueue[i].products.push({
                                    id: product.id,
                                    quantity: value.quantity,
                                    name: product.name,
                                    buttonName: product.buttonName,
                                    modifiers: value.modifiers,
                                    subModifiers: value.subModifiers,
                                    subSubModifiers: value.subSubModifiers
                                })
                            } else {
                                productToPrintQueue.push({
                                    pqid: printQueueId,
                                    products: [{
                                        id: product.id,
                                        quantity: value.quantity,
                                        name: product.name,
                                        buttonName: product.buttonName,
                                        modifiers: value.modifiers,
                                        subModifiers: value.subModifiers,
                                        subSubModifiers: value.subSubModifiers
                                    }]
                                })
                            }
                        }
                    } else {
                        productToPrintQueue.push({
                            pqid: printQueueId,
                            products: [{
                                id: product.id,
                                quantity: value.quantity,
                                name: product.name,
                                buttonName: product.buttonName,
                                modifiers: value.modifiers,
                                subModifiers: value.subModifiers,
                                subSubModifiers: value.subSubModifiers
                            }]
                        })
                    }
                }).finally(function () {
                    //$rootScope.consolelog(productToPrintQueue)
                    angular.forEach(productToPrintQueue, function (printQueue) { 
                        ngDexie.getByIndex('localprinters', 'printQueueId', parseInt(printQueue.pqid)).then(function (data) { //then from the products store get the printqueueid for that particular product
                            if(data.ip != '0.0.0.0'){
                                 if (duplicatePrinters.indexOf(printQueue.pqid) < 0) { // check if the the printer ip is already in the array then dont print as we already have :)
                                    $rootScope.consolelog('products to be printed: ' + printQueue.products);
                                    var canvas = document.getElementById('orderCanvas');
                                    var y = drawReceipt(canvas, receiptData, printQueue.products, note);
                                    var address = 'http://' + data.ip + '/cgi-bin/epos/service.cgi?devid=local_printer&timeout=60000';
                                    var context = canvas.getContext('2d');
                                    var builder = new epson.ePOSBuilder();
                                    builder.brightness = 1.0;
                                    builder.halftone = builder.HALFTONE_ERROR_DIFFUSION;
                                    // builder.addLayout(builder.LAYOUT_RECEIPT, 800, 0, 0, 0, 0, 0)
                                    builder.addImage(context, 0, 0, 576, y, builder.COLOR_1, builder.MODE_MONO);
                                    builder.addCut(builder.CUT_FEED);

                                    var epos = new epson.ePOSPrint(address);
                                    //epos.onreceive = function (res) { alert(res.success); };
                                    epos.onerror = function (err) {
                                        $rootScope.consolelog(err.status);
                                    };
                                    //epos.oncoveropen = function () { alert('coveropen'); };

                                    epos.send(builder.toString());

                                    //epos.print(canvas);
                                    $rootScope.consolelog('reciept printed');
                                    canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
                                    duplicatePrinters.push(printQueue.pqid)
                                    //$rootScope.consolelog(duplicatePrinters)
                                }
                            }
                        })
                    });
                })
            })
        };
        
        orderPrinter.orderPrintRemainingCanvas = function (receiptData, note) {
            var duplicatePrinters = []
            var productToPrintQueue = []
            tabFactory.getProductsToPrintRemaining(receiptData.transaction.tab.tabId).success(function (transactionProducts){
                $rootScope.consolelog('transactionProducts: ' + transactionProducts)
                angular.forEach(transactionProducts, function (value) { //loop through transaction products
                    $rootScope.consolelog(value);
                    ngDexie.get('products', value.productId).then(function (product) { //foreach product get that products information from indexeddb
                        $rootScope.consolelog(product)
                        var printQueueId = product.printQueueId;
                        if (lookup(printQueueId, productToPrintQueue)) {
                            for (var i = 0, l = productToPrintQueue.length; i < l; i++) {
                                if (productToPrintQueue[i].pqid === printQueueId) {
                                    if(value.quantity === value.printed){
                                        var printedQty = '0';
                                    }else{
                                        var printedQty = parseInt(value.quantity) - parseInt(value.printed);
                                    }
                                    $rootScope.consolelog(printedQty)
                                    if(printedQty != '0') {
                                        productToPrintQueue[i].products.push({
                                            id: product.id,
                                            quantity: printedQty,
                                            name: product.name,
                                            buttonName: product.buttonName,
                                            modifiers: value.modifiers,
                                            subModifiers: value.subModifiers,
                                            subSubModifiers: value.subSubModifiers
                                        })
                                    }

                                } else {
                                    if(value.quantity === value.printed){
                                        var printedQty = '0';
                                    }else{
                                        var printedQty = parseInt(value.quantity) - parseInt(value.printed);
                                    }
                                    $rootScope.consolelog(printedQty)
                                    if(printedQty != '0') {
                                        productToPrintQueue.push({
                                            pqid: printQueueId,
                                            products: [{
                                                id: product.id,
                                                quantity: printedQty,
                                                name: product.name,
                                                buttonName: product.buttonName,
                                                modifiers: value.modifiers,
                                                subModifiers: value.subModifiers,
                                                subSubModifiers: value.subSubModifiers
                                            }]
                                        })
                                    }
                                }
                            }
                        } else {
                            if(value.quantity === value.printed){
                                var printedQty = '0';
                            }else{
                                var printedQty = parseInt(value.quantity) - parseInt(value.printed);
                            }
                            $rootScope.consolelog(printedQty)
                            if(printedQty != '0') {
                                productToPrintQueue.push({
                                    pqid: printQueueId,
                                    products: [{
                                        id: product.id,
                                        quantity: printedQty,
                                        name: product.name,
                                        buttonName: product.buttonName,
                                        modifiers: value.modifiers,
                                        subModifiers: value.subModifiers,
                                        subSubModifiers: value.subSubModifiers
                                    }]
                                })
                            }
                        }
                        tabFactory.setProductsToPrintRemaining(receiptData.transaction.tab.tabId, value.randomId);
                    }).finally(function () {
                        //$rootScope.consolelog(productToPrintQueue)
                        angular.forEach(productToPrintQueue, function (printQueue) { 
                            ngDexie.getByIndex('localprinters', 'printQueueId', parseInt(printQueue.pqid)).then(function (data) { //then from the products store get the printqueueid for that particular product
                                if(data.ip != '0.0.0.0'){
                                     if (duplicatePrinters.indexOf(printQueue.pqid) < 0) { // check if the the printer ip is already in the array then dont print as we already have :)
                                        $rootScope.consolelog('products to be printed: ');
                                        $rootScope.consolelog(printQueue.products);
                                        var canvas = document.getElementById('orderCanvas');
                                        var y = drawReceipt(canvas, receiptData, printQueue.products, note);
                                        var address = 'http://' + data.ip + '/cgi-bin/epos/service.cgi?devid=local_printer&timeout=60000';
                                        var context = canvas.getContext('2d');
                                        var builder = new epson.ePOSBuilder();
                                        builder.brightness = 1.0;
                                        builder.halftone = builder.HALFTONE_ERROR_DIFFUSION;
                                        // builder.addLayout(builder.LAYOUT_RECEIPT, 800, 0, 0, 0, 0, 0)
                                        builder.addImage(context, 0, 0, 576, y, builder.COLOR_1, builder.MODE_MONO);
                                        builder.addCut(builder.CUT_FEED);

                                        var epos = new epson.ePOSPrint(address);
                                        //epos.onreceive = function (res) { alert(res.success); };
                                        epos.onerror = function (err) {
                                            $rootScope.consolelog(err.status);
                                        };
                                        //epos.oncoveropen = function () { alert('coveropen'); };

                                        epos.send(builder.toString());

                                        //epos.print(canvas);
                                        $rootScope.consolelog('reciept printed');
                                        canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
                                        duplicatePrinters.push(printQueue.pqid)
                                        //$rootScope.consolelog(duplicatePrinters)
                                    }
                                }
                            })
                        });
                    })
                })
            })
        };
        return orderPrinter;

    }

    orderPrinterFactory.$inject = injectParams;

    angular.module('myApp').factory('orderPrinterFactory', orderPrinterFactory);

}());

Open in new window

hostingplusAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

BigRatCommented:
I presume the problem arises from :-

                if(canvas === null){
                     canvas = document.getElementById('orderCanvas');
                }
                var context = canvas.getContext('2d');

and I'd ask myself if in fact canvas is null? or undefined? or whatever by simply removing the test.
Furthermore I'd double check that "orderCanvas" is defined as an element by logging an error after the getElementById.
1
hostingplusAuthor Commented:
Hi BigRat,

Many thanks for commenting, your help on this issue would be great. Just as a side note also, we would be happy to pay for some assistance with this - We could arrange a skype/teamviewer session if its easier for you but ill provide more information now.

 if(canvas === null){
                     canvas = document.getElementById('orderCanvas');
                }
                var context = canvas.getContext('2d');

Open in new window


The above is a catch we have only recently included,hoping if is was NULL it would recreate it. Dosnt seem to be the case.

canvas = document.getElementById('orderCanvas');
var context = canvas.getContext('2d');

Open in new window


We have caught the error at a pilot site and can confirm when we inspected that "orderCanvas" was defined as a element on the page, but this error continued to come up until we refreshed.

It was alike the canvas was "broken" or it would not see it.

Im sure you can understand its a very difficult issue to explain, but let me know if I can provide anymore details or information.
0
BigRatCommented:
Quick question : with which browser does this error occur?
0
Ultimate Tool Kit for Technology Solution Provider

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy now.

hostingplusAuthor Commented:
We only run and support Chrome at this stage. Latest version or close to. Thanks
0
BigRatCommented:
While I think about that, you could try to get someone to press the F12 key when the error occurs just to see if there are any other errors occuring. I would also be a lot happier if you tried to detect if "canvas" was null (as if it could not be found), log it, alert it (or display an error) and restarted/reloaded the app.

With errors like this, one ought to switch to a more permanent loggin (not console but in a file) and start logging events and calls that have "something to do with the error". Like for example, is "document"in this context the correct "document", for that would cause the canvas element not to be found.
1
hostingplusAuthor Commented:
Thanks BigRat,

Clients have called us when this has occurred and we jumped straight on remotely and can confirm the only error is that seen in the screen shot above (from that client even)

We are not sure how we get back NULL sometimes as the spec stats URL HERE We dont use WebGL at all.

context = canvas . getContext(contextId [, ... ] )
Returns an object that exposes an API for drawing on the canvas. The first argument specifies the desired API, either "2d" or "webgl". Subsequent arguments are handled by that API.

This specification defines the "2d" context below. There is also a specification that defines a "webgl" context. [WEBGL]

Returns null if the given context ID is not supported, if the canvas has already been initialised with the other context type (e.g. trying to get a "2d" context after getting a "webgl" context).

Throws an InvalidStateError exception if the setContext() or transferControlToProxy() methods have been used.
0
hostingplusAuthor Commented:
Also wanted to mention,

this is our canvas from the web-app:

<canvas id="orderCanvas" width="576" height="1500" style="display:none;"></canvas>

Open in new window


Lets say someone orders 3 drinks and 2 Steaks.

They will print at different printers:

3 drinks will be printed on the drinks printer (192.168.1.1)
2 steaks will be printed to the food printer (192.168.1.2)

As you will see the orderPrinterFactory.js is what does this, but they both use the same canvas - this could be an issue but for days ive been replicating that in house and it never fails! Big orders all types. Just works.
0
BigRatCommented:
With reference to post ID: 41131307 the issue is not about the canvas object per se, but the fact that document.getElementById('orderCanvas') is returning null.

If I believe that the error message is correct, then the getElementById cannot find such a DOM element - irrespective of what type it is - in the document given by the variable "document".

Now this is either a bug in Chrome, because I'd implement canvas as a memory bitmap which would have a dc and such things (used?) to be limited - ie: a resource problem; or, and most likely, that the variable document is valid but does not have an element with that id. And this is because it is refering to a different document. The obvious reason being a frame or a popup or something similar. Alternatively, this might also be a bug in Chrome if the HTML containing the canvas was injected, or a bug in your Angular code which hasn't injected the HTML at the time the use of the canvas element occurred.

What you could do is add an extra element next to the canvas element, say a hidden input element with a unique id and get a pointer to that just before the use of canvas. If that getElementById returns a null then you know that it has nothing to do with canvas and a lot to do with the context of document.

Apropos testing in house. My experience is that customers use my software in ways that I do not expect. I have seen people do contorted things instead of pressing a button, and I've seen the need for an extra button, because they do things which I did not expect. This is why in house testing is useful for removing the silly errors but there is no substitute for going on site and sitting next to people all day just to see how they use it. The worst problem is work-a-rounds. Years after I have fixed the problem, people are still using the work-a-round and worst still, our support staff are still advising people to do so!
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
hostingplusAuthor Commented:
Hi BigRat,

I've implemented what you have suggested by adding a hidden input and in our factory we get the documentById and then change the value. Will see if we get the same error.

Another thought as JS runs line by line but does not wait for the previous line to finish before executing the next line could this be our problem? So the getContext is executing before the var canvas is set?

Thanks
0
BigRatCommented:
No, JS executes synchronously. In fact in IE JS executes on the same thread as the GUI, so the screen does not get updated until the script has finished.

I suspect that, under certain circumstances, the "document" variable end up not pointing to the document which contains the canvas.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
HTML

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.