webgio / Rotativa

Rotativa, /rota'tiva/. Make Pdf from Asp.Net MVC. Available on Nuget https://www.nuget.org/packages/Rotativa
http://letsfollowtheyellowbrickroad.blogspot.it/search/label/Rotativa
MIT License
621 stars 224 forks source link

Progress® Kendo UI® TreeMap (MVC5) #205

Open ptremblayprospection opened 5 years ago

ptremblayprospection commented 5 years ago

To whom it may concern,

We have a project In MVC 5 that is using a Progress® Kendo UI® Chart and TreeMap for ASP.NET MVC. We achieved to export the Chart in the PDF with Rotativa, but the TreeMap doesn't work (Even with different Rotativa custom swiches), even if it is displayed in the view, it doesn't get displayed on the PDF, only a rectangle with nothing inside. We contacted the support of Telerik and had few exchanges about the problem. Telerik couldn't find the reason why it wasn't exported, but sugggested us to contact the community and Rotativa Team. So I added a sample of the problem and also the conversation I had with Telerik.

Thank you very much to anyone who can help us with this problem! :)

From Me
Posted on: March 28, 2019

To whom it may concern,

We generate a PDF that contains different Telerik MVC Chart with rotativa and everything works fine. But, when we tried to add a Treemap, that was displayed in the view, it doesn't get displayed on the PDF, only a rectangle with nothing inside. Can the Treemap render either SVC or canvas? We tried to add a wait time from the Rotativa generation, in case there's an animation on the Treemap that could block the export, but it doesn't work either. Is there something to activate on the Treemap to be able to display it inside the PDF, or anything to help us solve this problem we are facing?
From Telerik
Posted on: March 29, 2019

The TreeMap is a client side component which means that it is initialized on the client via an initialization JS script. In other words, on the server the page contains an initialization script and an empty div element. The Rotativa library provides server side exporting and I assume that it renders only the content available on the server. Have in mind that we are not experts in third party libraries and we cannot tell how their internals work. This export might work if the Rotativa library internally uses some kind of an engine which executes the scripts and generates the client side content. I did some research but I did not find any information if the Rotativa library is capable of running scripts and export the pages the way they look in the browser. If possible I would recommend you to write to the Rotativa team and ask them whether it is possible to export items which are initialized via scripts on the page.

At this point I can suggest you to use a client side PDF export as follows:

var draw = kendo.drawing;

draw.drawDOM($("#treeMap"))
.then(function(root) {
    // Chaining the promise via then
    return draw.exportPDF(root, {
        landscape: true
    });
})
.done(function(data) {
    // Here 'data' is the Base64-encoded PDF file
    kendo.saveAs({
        dataURI: data,
        fileName: "treeMap.pdf"
    });
});
From Me
Posted on: April 2, 2019

The client-side PDF export solution you gave us will not work, because we generate a PDF from a view which contains texts, and other Telerik graphics and data. We do not want to only export the Treemap in a single PDF, but to integrate it in the whole PDF we wanted to generate from an existing view. 

When we call the Rotativa method, we pass the model and the view we want to be exported in the PDF. It recognized the CSS as well and applied it. So, I thought that all client-side code was executed in the view before the Rotativa library generate the PDF.

We tried added the switch --javascript-delay, to make Rotativa to wait for a certain amount of time, but it didn’t change the generation. 

We will continue to investigate to find a solution.

Thank you,
From Me
Posted on: April 2, 2019

Hello again, 

I don't know if that might help, but I found a custom switch that can be added:

--run-script <js>               Run this additional javascript after the  page is done loading 

Example:  --run-script "javascript:(\$(function(){ \$('div').hide()   ;}))"

So I don't know if we could launch a method from the Treemap to specify to generate its data?

Regards,
From Me
Posted on: April 2, 2019

Hi,

Ok here's what we tried. On the view that we print as a PDF and navigate in it, where the TreeMap is, we setted on the treemap the option autobind to false. And added a button that called a JavaScript method that hide a div and then call the read method of the datasource of the TreeMap.

<h2>@Resource.EquipeActuelle</h2>
        @(Html.Kendo().TreeMap()
                        .Name("treeMap")
                        .DataSource(dataSource => dataSource
                           .Read(read => read
                                .Action("GetTreeMapFromCharteRepartionDesForces", "ChartesNumerologiques", new { idCharteRepartitionDesForces = Model.CharteNumerologiqueEquipe.CharteRepartitionDesForces_AvecLaPersonneAComparer.Id, idLangue = Model.CharteNumerologiqueEquipe.CharteRepartitionDesForces_AvecLaPersonneAComparer.Langue.Id, nomEquipe = Model.CharteNumerologiqueEquipe.Equipe.Nom })
                            )
                            .Model(m => m.Children("Items"))
                        )
                        .ValueField("Value")
                        .TextField("Name")
                        .ColorField("Color")
                        .AutoBind(false)

        )

        <div id="allo"><h1>alloooooooooooooooo</h1></div>

        <button type="button" id="gestionEquipeEnregistrer" class="changerProfilEquipe btn btn-default float-right" onclick="TEST()">
            <i class="fas fa-save"></i>
            <span>Test</span>
        </button>

        <script>
            //******************************************************
            //                   TEST
            //******************************************************
            function TEST() {

                $('#allo').hide();

                var treemap = $("#treeMap").data("kendoTreeMap");

                treemap.dataSource.read();
            }
        </script>

When we are on the view with Internet Explorer, and that we click on the button, the div is hidden and the TreeMap gets populateds with its data. Everything works fine there.

But when we inject the view and model to Rotativa to generate the PDF, and then added the switch that called the JavaScript method (--run-script "javascript: ($(function(){ TEST(); }))"). The PDF is generated without the Div, but the TreeMap is still empty. That means that the JavaScript method was called successfully since it hidded the Div.

So we wonder if we took the right method (treemap.dataSource.read();) to populate took the TreeMap from the JavaScript method or if you have any idea related to that path of solution.

Thank you for your time,
From Telerik
Posted on: April 3, 2019

Hi,

Thank you for the explanations.

What I understand at this point is that the Rotativa framework allows you to run certain scripts if you declare them within that switch. Please correct me if I am wrong.

I can suggest you to defer the scripts of the widget and execute them within a function.

e.g.

// treemap configuration
        @(Html.Kendo().TreeMap()
                        .Name("treeMap")
                        .DataSource(dataSource => dataSource
                           .Read(read => read
                                .Action("GetTreeMapFromCharteRepartionDesForces", "ChartesNumerologiques", new { idCharteRepartitionDesForces = Model.CharteNumerologiqueEquipe.CharteRepartitionDesForces_AvecLaPersonneAComparer.Id, idLangue = Model.CharteNumerologiqueEquipe.CharteRepartitionDesForces_AvecLaPersonneAComparer.Langue.Id, nomEquipe = Model.CharteNumerologiqueEquipe.Equipe.Nom })
                            )
                            .Model(m => m.Children("Items"))
                        )
                        .ValueField("Value")
                        .TextField("Name")
                        .ColorField("Color")
                        .AutoBind(false)
                        .Deferred()
        )

// scripts

<script>

    function initializeWidget() {
        @Html.Kendo().DeferredScriptsFor("treeMap", false)
    }

    $(function () {
        initializeWidget();
    })
</script>

Please note that the initializeWidget function is called within the document ready event handler in order to initialize the tree map when the page loads. It will be necessary to execute the same function within the switch of the Rotativa library.

e.g.

--run-script "javascript:(\$(function(){ initializeWidget();}))"

Please test the above approach and let me know if it works.

I look forward to your reply.
From Me
Posted on: April 3, 2019

Hello,

 Thank you very much for the information. We've tried the example you gave us, it enter in the javascript method initializeWidget(), but when the page finish loading in the browser, the Treemap is empty. 

So it doesn't display either on the view or in the PDF.

We kept Deffered() and setted autobind to true, after the test, just to see if the Treemap would be generated on the view, and it was. The 'DeferredScriptsFor' seems the right direction for the solution, maybe we missed something since it doesn't load on the view with autobind at false.

Regards,

<h2>@Resource.EquipeActuelle</h2>
@(Html.Kendo().TreeMap()
    .Name("treeMap")
    .DataSource(dataSource => dataSource
        .Read(read => read
            .Action("GetTreeMapFromCharteRepartionDesForces", "ChartesNumerologiques", new { idCharteRepartitionDesForces = Model.CharteNumerologiqueEquipe.CharteRepartitionDesForces_AvecLaPersonneAComparer.Id, idLangue = Model.CharteNumerologiqueEquipe.CharteRepartitionDesForces_AvecLaPersonneAComparer.Langue.Id, nomEquipe = Model.CharteNumerologiqueEquipe.Equipe.Nom })
        )
        .Model(m => m.Children("Items"))
    )
    .ValueField("Value")
    .TextField("Name")
    .ColorField("Color")
    .AutoBind(false)
    .Deferred()
)

<script>
    function initializeWidget() {

        @Html.Kendo().DeferredScriptsFor("treeMap", false);
    }

    $(function () {
        initializeWidget();
    })
</script>
From Telerik
Posted on: April 4, 2019

Hi,

When AutoBind is set to false the TreeMap will not request the server initially. I apologize for the inconvenience, I copy pasted the configuration of your reply and I did not notice the AutoBind setting.

I have tested to defer the scritps in the sample from my previous message and when the AutoBind is not set to false the TreeMap is populated as expected.

Could you please test the sample after configuring the TreeMap to automatically request the server for data and let me know if everything works as expected?

For your convenience I am attaching the modified version of the sample which I used for testing.

Regards,
From me
Posted on: April 4, 2019

Hi, 

We removed the AutoBind option and we can see the deferred TreeMap on the view, but still not on the PDF. I sent you a sample of a generated PDF with a Deffered TreeMap, an AutoBind TreeMap and a Kendo Bar Chart, so you could see the problem. The example is 74mo, since rotativa is 40mo itself. So, I can't sent you the sample in this tread. I used Google drive instead. 

What is interesting, is that if we remove the rotativa command:
--run-script "javascript:(\$(function(){ initializeWidget();}))"

We still do not see the Div in the PDF, so it might enter in the document ready event handler.

 function initializeWidget() {
            $("#divHidden").hide();
            @Html.Kendo().DeferredScriptsFor("treeMap_Deffered", false);
        }

Thank you for your support,
From Telerik
Posted on: April 8, 2019

Hi,

Thanks for the provided sample.

I can confirm that the Treemap is not displayed correctly in the exported pdf file. However, I assume that the TreeMap is not rendered correctly due to some Rotativa Specific behavior and unfortunately we have no access to their internal logic and we cannot debug their logic and let you know what is causing the behavior.

At some point I thought that the behavior might be due to the Ajax binding, but I tested to serialize the data on the server and initialize the widget via jQuery which will avoid the ajax request.

e.g.

// html
<h1>TreeMap JS</h1>
<div id="treeMap"></div>

// initialization script
<script>
    function initializeWidget() {
        $("#divHidden").hide();
        @Html.Kendo().DeferredScriptsFor("treeMap_Deffered", false);

        $("#treeMap").kendoTreeMap({
            dataSource: {
                data: @Html.Raw(jsonSerialiser.Serialize(Model.GetTreeMapData())),
                schema: {
                    model: {
                        children: "Items"
                    }
                }
            },
            valueField: "Value",
            textField: "Name",
            colorField:"Color"
        });
    }

    $(function () {
        initializeWidget();
    })
    </script>

// model

    public class MainModel
    {
        public MainModel()
        {
        }

        public List<ObjetGraphiqueBarre> GetListeObjetGraphBarre()
        {
            Random rnd = new Random();

            List<ObjetGraphiqueBarre> laListeObjet = new List<ObjetGraphiqueBarre>
            {
                //ATTENTION: l'ordre d'insertion est important car elle indique la valeur numérique
                new ObjetGraphiqueBarre("#D00000", "Value 01", rnd.Next(1, 20)),
                new ObjetGraphiqueBarre("#FFD230", "Value 02", rnd.Next(1, 20)),
                new ObjetGraphiqueBarre("#F75C03", "Value 03", rnd.Next(1, 20)),
                new ObjetGraphiqueBarre("#002361", "Value 04", rnd.Next(1, 20)),
                new ObjetGraphiqueBarre("#18B7C9", "Value 05", rnd.Next(1, 20)),
                new ObjetGraphiqueBarre("#066D04", "Value 06", rnd.Next(1, 20)),
                new ObjetGraphiqueBarre("#3B1552", "Value 07", rnd.Next(1, 20)),
                new ObjetGraphiqueBarre("#D8A90D", "Value 08", rnd.Next(1, 20)),
                new ObjetGraphiqueBarre("#6B104C", "Value 09",rnd.Next(1, 20))
            };

            return laListeObjet;
        }

        public ICollection<ObjetTreeMap> GetTreeMapData()
        {
            Random rnd = new Random(DateTime.Now.Millisecond);

            //On fabrique la liste de base
            List<ObjetTreeMap> listeTMObjet = new List<ObjetTreeMap>();

            //On ajoute l'objet de base
            ObjetTreeMap tmObjet = new ObjetTreeMap("Team 01", "#DCDCDC", 100, new List<ObjetTreeMap>());

            //On initialise la liste
            List<ObjetTreeMap> laListe = new List<ObjetTreeMap>
            {
                new ObjetTreeMap("Value 01", "#D00000", rnd.Next(1, 20), new List<ObjetTreeMap>()),
                new ObjetTreeMap("Value 02", "#FFD230", rnd.Next(5, 20), new List<ObjetTreeMap>()),
                new ObjetTreeMap("Value 03", "#F75C03", rnd.Next(10, 20), new List<ObjetTreeMap>()),
                new ObjetTreeMap("Value 04", "#002361", rnd.Next(5, 20), new List<ObjetTreeMap>()),
                new ObjetTreeMap("Value 05", "#18B7C9", rnd.Next(10, 20), new List<ObjetTreeMap>()),
                new ObjetTreeMap("Value 06", "#066D04", rnd.Next(1, 20), new List<ObjetTreeMap>()),
                new ObjetTreeMap("Value 07", "#3B1552", rnd.Next(5, 20), new List<ObjetTreeMap>()),
                new ObjetTreeMap("Value 08", "#D8A90D", rnd.Next(1, 20), new List<ObjetTreeMap>()),
                new ObjetTreeMap("Value 09", "#6B104C", rnd.Next(15, 20), new List<ObjetTreeMap>())
            };

            //On attache la liste
            tmObjet.Items = laListe;

            //On relit le tout
            listeTMObjet.Add(tmObjet);

            //On retourne la liste
            return listeTMObjet;
        }
    }

However, the treemap still does not render correctly in the exported pdf. Currently, the only thing I can suggest you is to contact Rotativa and ask them how they run the scripts and what might be causing this behavior. I would recommend you to supply them with our discussion and let them know what we have tried. I apologize that we are not able to resolve the issue without the help of the Rotativa team.

I am attaching the version of the sample where I tested to serialize the data on the server in case it helps for the investigation.