0

When I move deferred.resolve to outside after the all the svg to canvas, I am not getting the entire function to work inside a deferred.resolve. I want the deferred.promise() to execute when all the svgs are converted to canvas and not just the first svg.

$(document).ready(function() {

    $( '#save_dashboard' ).click(function() {

        // Create a deferred object
        var dfd = new $.Deferred();

        // https://github.com/niklasvh/html2canvas/issues/95#issuecomment-45114424
        // First render all SVGs to canvases
        targetElem = $('#dashboard');

        var elements = targetElem.find('svg').map(function() {
            var svg = $(this);
            var canvas = $('<canvas></canvas>');
            svg.replaceWith(canvas);

            // Get the raw SVG string and curate it
            var content = svg.wrap('<p></p>').parent().html();
            content = content.replace(/xlink:title='hide\/show'/g, '');
            content = encodeURIComponent(content);
            svg.unwrap();

            // Create an image from the svg
            var image = new Image();
            image.src = 'data:image/svg+xml,' + content;
            image.onload = function() {
                canvas[0].width = image.width;
                canvas[0].height = image.height;

                // Render the image to the canvas
                var context = canvas[0].getContext('2d');
                dfd.resolve(context.drawImage(image, 0, 0));
            };

            return dfd.promise();
            /* return {
                svg: svg,
                canvas: canvas
            }; */
        }); // end of targetElem.find('svg').map(function() {...});

        // $.when(dfd).done(function(){
        //    console.log('dfd done');
        dfd.then(function(_canvas){
            console.log('dfd done', _canvas);

            // http://www.kubilayerdogan.net/html2canvas-take-screenshot-of-web-page-and-save-it-to-server-javascript-and-php/
            $('#dashboard').html2canvas({
                onrendered: function (canvas) {
                    //Set hidden field's value to image data (base-64 string)
                    var dashboardPng = canvas.toDataURL('image/png');
                    console.log('dashboardPng: ' + dashboardPng);

                    $.ajax({
                        url:'save_dashboard_image.php',
                        data:{dashboardPngData: dashboardPng},
                        type:'POST',
                        dataType:'json',
                        success: function(){
                            console.log('success');
                        }
                        ,
                        error: function(xhr, status, error){
                            console.log('The requested page was: ' + document.URL +
                                '. The error number returned was: ' + xhr.status +
                                '. The error message was: ' + error);
                        }
                    });
                }
            });
        // }); // end of when(dfd).done()
        }); // end of dfd.then(function(_canvas){...})

    }); // end of save_dashboard click function
}); // end of document ready

Solution Modified from Terry's answer below: https://stackoverflow.com/a/29637380/2120512

$(document).ready(function() {

    $( '#save_dashboard' ).click(function() {

        // Declare an array to store all deferred objects from each svg element
        var svgDfds = [],
            targetElem = $('#dashboard');

        targetElem.find('svg').each(function() {
            var dfd = new $.Deferred(),
                svg = $(this),
                canvas = $('<canvas></canvas>');

            svg.replaceWith(canvas);

            // Get the raw SVG string and curate it
            var content = svg.wrap('<p></p>').parent().html();
            content = content.replace(/xlink:title='hide\/show'/g, '');
            content = encodeURIComponent(content);
            svg.unwrap();

            // Create an image from the svg
            var image = new Image();
            image.src = 'data:image/svg+xml,' + content;
            image.onload = function() {
                canvas[0].width = image.width;
                canvas[0].height = image.height;

                // Render the image to the canvas
                var context = canvas[0].getContext('2d');

                // Resolve or reject the deferred
                dfd.resolve(context.drawImage(image, 0, 0));
            };

            // Push deferred object into array
            svgDfds.push(dfd);

        }); // end of targetElem.find('svg').map(function() {...});

        // Check for all deferreds
        $.when.apply($, svgDfds).then(function(_canvas) {
            console.log('dfd done', _canvas);

            // http://www.kubilayerdogan.net/html2canvas-take-screenshot-of-web-page-and-save-it-to-server-javascript-and-php/
            $('#dashboard').html2canvas({
                onrendered: function (canvas) {
                    //Set hidden field's value to image data (base-64 string)
                    var dashboardPng = canvas.toDataURL('image/png');
                    console.log('dashboardPng: ' + dashboardPng);

                    $.ajax({
                        url:'save_dashboard_image.php',
                        data:{dashboardPngData: dashboardPng},
                        type:'POST',
                        dataType:'json',
                        success: function(){
                            console.log('success');
                        }
                        ,
                        error: function(xhr, status, error){
                            console.log('The requested page was: ' + document.URL +
                                '. The error number returned was: ' + xhr.status +
                                '. The error message was: ' + error);
                        }
                    });
                }
            });
        });

    }); // end of save_dashboard click function
}); // end of document ready
Community
  • 1
  • 1
imparante
  • 503
  • 9
  • 21
  • 1
    duplicate of [How to post immediately after svg to canvas conversion is done?](http://stackoverflow.com/q/29635005/1048572) – Bergi Apr 15 '15 at 00:56

2 Answers2

1

You cannot use a single deferred for multiple image loads. Each image does need its own deferred, so that you can create a dedicated promise for it. Only then you get back an array of different promises (that each resolve when their respective image is loaded), which you then can pass all to $.when to await them.

Your code should look like this:

$( '#save_dashboard' ).click(function() {
    var targetElem = $('#dashboard');

    var elementPromises = targetElem.find('svg').map(function() {
        // Create the deferred objects here!
        var dfd = new $.Deferred();
        …
        var image = new Image();
        …
        image.onload = function() {
            …
            dfd.resolve(context.drawImage(image, 0, 0));
        };

        return dfd.promise();
    }).get(); // an array, not a jquery collection

    var allLoaded = $.when.apply($, elementPromises);
    allLoaded.then(…);
});
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • my svgs convert, but post doesn't catch the newly created canvases from the svgs on the page. It executes before the canvases are finished being created. – imparante Apr 14 '15 at 21:32
  • That's odd. The canvas are created and inserted into the page synchronously, and `allLoaded` doesn't resolve before all pictures are drawn on them. – Bergi Apr 14 '15 at 21:37
  • Without deferred I was able to do this in a two-step process too: convert the svgs to canvases, and post the screen capture with html2canvas. – imparante Apr 14 '15 at 21:42
  • It still is that two-step process? I wonder how you did the parallel async svg conversion without promises. – Bergi Apr 14 '15 at 21:45
  • It was a broken two-step process. I attempted to convert and the post would immediately execute. If I reattempted with the newly converted canvases, it would post the obviously have the correct canvases. Terry's solution is working with a little modification. Thank you Bergi! – imparante Apr 14 '15 at 22:09
  • 1
    @user2120512: Well, it was basically the same solution as mine. What did you need to modify? – Bergi Apr 14 '15 at 22:46
1

You should structure your code in the following way:

  1. Create a new array on click, use that to store all deferreds from SVGs
  2. Loop through all SVG elements
    • In each instance, create a new internal deferred object
    • Resolve or reject the deferred
    • Push it into the array outside the loop
  3. Use $.when.apply($, arrayOfDeferredObjects) to check for status of all deferred objects in the array

In code, that should look like this:

$('#save_dashboard').click(function() {

    var svgDfd = [],  // Declare an array to store ALL deferreds from svgs
        $targetElem = $('#dashboard');

    // Use .each() to iterate through all SVGs
    $targetElem.find('svg').each(function() {

        // New deferred object per SVG instance
        var dfd = new $.Deferred();

        // At some point in your code, resolve or reject the deferred
        dfd.resolve();

        // Push deferred object into array
        svgDfds.push(dfd);
    }

    // Check for all deferreds
    $.when.apply($, svgDfds).then(function() {
        // Do stuff
    });
});

Therefore, with your code slightly modified and refactored to fit the paradigm above:

$(document).ready(function() {

    $( '#save_dashboard' ).click(function() {

        // Declare an array to store all deferredo objects from each svg element
        var svgDfds = [],
            $targetElem = $('#dashboard');

        $targetElem.find('svg').each(function() {
            var dfd = new $.Deferred(),
                svg = $(this),
                canvas = $('<canvas></canvas>');

            svg.replaceWith(canvas);

            // Get the raw SVG string and curate it
            var content = svg.wrap('<p></p>').parent().html();
            content = content.replace(/xlink:title='hide\/show'/g, '');
            content = encodeURIComponent(content);
            svg.unwrap();

            // Create an image from the svg
            var image = new Image();
            image.src = 'data:image/svg+xml,' + content;
            image.onload = function() {
                canvas[0].width = image.width;
                canvas[0].height = image.height;

                // Render the image to the canvas
                var context = canvas[0].getContext('2d');
                dfd.resolve(context.drawImage(image, 0, 0));
            };

            svgDfds.push(dfd);

        });


        $.when.apply($, svgDfds).then(function(){
            $('#dashboard').html2canvas({
                onrendered: function (canvas) {
                    //Set hidden field's value to image data (base-64 string)
                    var dashboardPng = canvas.toDataURL('image/png');
                    console.log('dashboardPng: ' + dashboardPng);

                    $.ajax({
                        url:'save_dashboard_image.php',
                        data:{dashboardPngData: dashboardPng},
                        type:'POST',
                        dataType:'json',
                        success: function(){
                            console.log('success');
                        }
                        ,
                        error: function(xhr, status, error){
                            console.log('The requested page was: ' + document.URL +
                                '. The error number returned was: ' + xhr.status +
                                '. The error message was: ' + error);
                        }
                    });
                }
            });
        });

    });
}); 
Terry
  • 63,248
  • 15
  • 96
  • 118
  • Just discovered apply() to call an array of arguments. Mind blown! This helps solve the problem of iterating through each svg element and posting the finished converted canvas. – imparante Apr 14 '15 at 22:40