Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider adding support to send HTML rendered code embeded in Email #221

Open
Stephanevg opened this issue Mar 27, 2019 · 10 comments
Open

Comments

@Stephanevg
Copy link
Owner

Rendering HTML to be send directly in an Email can be a bit different, as some of the styles seem to work differently. This issue is to identify

  1. in which condition are the email output different (Depending on which underlying technology we use to send the email? strucutre of the code, unsuported tags or CSS styles?
  2. How can we ensure that the output in a Email, will be identical to one generated locally.

The solution (might / could) be as simple as just create a specific CSS style that will apply 'only' to HTML code that is intended to be used in an Email.

This excludes adding charts (Is tracked here

@Stephanevg Stephanevg added the enhancement New feature or request label Mar 27, 2019
@wightsci
Copy link
Contributor

Have a look at: How to Code HTML Email Newsletters for some of the issues around HTML emails. Having generated these myself from scratch in scripts, the rule seems to be 'simple is best'. Not sure if that helps very much.

@Stephanevg
Copy link
Owner Author

Ho great, that is exactly something I have been looking for actually. thanks @wightsci

In the end, there a few things that report generator won't be be able / allowed to do, because it release on additional framework / external files which we cannot attach to our email

We ce can/must do for sure is the following:

  • Use inline CSS
  • Use Tables to structure the Email content (yes.. apprently that is how it works)

Design

Thinking out loud

On the design part of things I am unsure what the best approach would be.

As we don't want to have any dependencies on any of the PSHTML Assets (Bootstrap / Jquery / ChartJS) we need to be sure that the HTML strucutre we are shipping doesn't contains:

  1. A CDN / Script / Link reference to any of the above mentionned framework.
  2. No Bootstrap class are assigned to any of the HTML tags (We could potentially override any bootstrap styling that the user add using.)

How could this look like

I want to have an open discussion on how this could look like. I see several possible scenarios which I would like to discuss.

  1. We write a new function called ConvertTo-PSHTMLEmailContent (or something around these lines) where there would be a parameter -HtmlDocument which would get an HTML document generated by PHSTML. there, we will parse the HTMLStructure, and search for the things that are not supported in email (such as references to CDN / frameworks) and remove any bootstrap class references (This step might not be needed).
    The function would also add the necessary inline CSS styling. (this could be done by adding a new CSS file, which can contain our desired classes, and we simply add it on top of the HTML document using a Get-Content -Raw

  2. We add switches on all of the html tags, like ForEmail which will add the correct CSS styling. (writing this one seemed already cumbersome / difficult to implement).

  3. Document what is allowed and not in an About_PSHTMLEmail.md file.

for the moment, I cannot think of other solutions, but please share your ideas, thoughts on how this could:

  1. have the best end user experience (Easy and straightforward to use)
  2. The easiest for us to implement.

Looking forward to your feedback ideas critics and design proposals.

@wightsci
Copy link
Contributor

wightsci commented Jul 12, 2019 via email

@christophekumor
Copy link
Contributor

Good idea !
If I’m not wrong the rendering of the email depends of the email client, I mean outlook do not render mail like Thunderbird, and even in outlook, it depend of the version..
you have to pay attention especially about images.. there is different way to add them to an email. And some way are working on one client, others no..
I’ll look at what I’m doing actually for outlook, I don’t remember (did it last year..)

@Stephanevg
Copy link
Owner Author

Stephanevg commented Jul 13, 2019

@christophekumor have a look at the link from @wightsci above. Everything is described in there already apperently

@christophekumor
Copy link
Contributor

Ok, i just wanted to share my own personal experience about generating email with powershell. Sorry

@Stephanevg Stephanevg added this to the 0.9.0 milestone Jul 20, 2019
@LxLeChat
Copy link
Contributor

LxLeChat commented Jul 21, 2019

hi,
I took the bar chart example, and added a javascript function to transformt the chart into a base64 source. It replace the chart canvas into an img tag with the base64 render of the the chart.
Then it delete all the script tags

<html ><head ><title >Bar Chart</title></head><Body ><h1 >PSHTML Graph</h1><div ><p >This is a bar graph</p>
    <canvas Width="400px" Id="barcanvas" Height="400px"  ></canvas></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js" type="text/javascript"  ></script>
<script id='chartscript'>
    var ctx = document.getElementById("barcanvas").getContext('2d');
    var myChart = new Chart(ctx, {
        "type":"bar",
        "data":{
            "labels":["January","February","Mars","April","Mai","June","July","August","September","October","November","december"],
            "datasets":[{
                "borderWidth":1,
                "xAxisID":null,
                "yAxisID":null,
                "backgroundColor":"rgb(30,144,255)",
                "borderColor":null,
                "borderSkipped":null,
                "hoverBackgroundColor":null,
                "hoverBorderColor":null,
                "hoverBorderWidth":0,
                "data":[4,1,6,12,17,25,18,17,22,30,35,44],
                "label":"2018"
            }]
        },
            "options":{
                "barPercentage":1,
                "categoryPercentage":1,
                "responsive":false,
                "barThickness":null,
                "maxBarThickness":0,
                "offsetGridLines":true,
                "scales":{
                    "yAxes":[{"ticks":{"beginAtZero":true}}],"xAxes":[""]
                },
                "title":{
                    "display":true,
                    "text":"Bar Chart Example"
                },
                "animation": {
                    "onComplete" : RemoveCanvasAndCreateBase64Image
                }
            }
        } );

        function RemoveCanvasAndCreateBase64Image (){
            var base64 = this.toBase64Image();
            var element = document.getElementById('barcanvas');
            var parent = element.parentNode;
            var img = document.createElement('img');
            img.src = base64;
            parent.appendChild(img);
            parent.removeChild(element);
			var scripttags = document.getElementsByTagName('script');
			var scripttags = document.getElementsByTagName('script');
			for (i=0;i<scripttags.length;){
				var parent = scripttags[i].parentNode;
				parent.removeChild(scripttags[i]);
			}
        };
        </script></Body></html>

important part: this is part of chartjs. It will call the RemoveCanvasAndCreateBase64Image js function when the chart animation is completed. We can not do this before! or the base64image will represent a white image.

"animation": {
                    "onComplete" : RemoveCanvasAndCreateBase64Image
                }

second important part, the js function that do to work for you :)

all of this JS code must be added into the GetDefinition method from the Chart base Class. Best we overload getdifnition with a new parameter, a [bool]ToBase64

Then we must change the New-PSHTMLChart function and add a [switch]$ToBase64 = $False

this is the easy part.

I recommand using https://github.com/adamdriscoll/selenium-powershell to be able to fetch the code of the generated page (the one after the javascript is executed ... ! )
in adam example, we could just do a $drivers.pagesource and voila !

Might be a better way to do this !

Edit:
Copy/Paste the html code into a file on your disk then, after importing the selenium project:

$drivers = Start-SeChrome
Enter-SeUrl -Driver $drivers -Url C:\temp\pshtml\BarChart1.html
sleep 10
$drivers.pagesource | out-fille c:\temp\pshtml\barchart_withoutJS.html

Edit2:
Here is the overload i started to write, it needs to be completed with the javascript available in the html code:

[String] GetDefinition([String]$CanvasID,[Bool]$ToBase64){
        
        $FullDefintion = [System.Text.StringBuilder]::new()
        $FullDefintion.Append($this.GetDefinitionStart([String]$CanvasID))
        $FullDefintion.AppendLine($this.GetDefinitionBody())
        $FullDefintion.AppendLine($this.GetDefinitionEnd())
        $FullDefintion.AppendLine("function RemoveCanvasAndCreateBase64Image (){")
        $FullDefintion.AppendLine("var base64 = this.toBase64Image();")
        $FullDefintion.AppendLine("var element = this.canvas;")
        $FullDefintion.AppendLine("var parent = element.parentNode;")
        $FullDefintion.AppendLine("var img = document.createElement('img');")
        $FullDefintion.AppendLine("img.src = base64;")
        $FullDefintion.AppendLine("img.name = element.id;")
        $FullDefintion.AppendLine("element.before(img);")
        $FullDefintion.AppendLine("parent.removeChild(element);")
        $FullDefintion.AppendLine("};")
        $FullDefintion.replace('"RemoveCanvasAndCreateBase64Image"','RemoveCanvasAndCreateBase64Image')
        $FullDefintionCleaned = Clear-WhiteSpace $FullDefintion
        return $FullDefintionCleaned
    }

Somewhere we also need to add the animation : for the javascript function to execute properly. I'll try to complete the code later.

@Stephanevg
Copy link
Owner Author

As discussed on Slack with @LxLeChat for this to be function, we will need have one script tag per PSHTML chart. (currently, several ones could be located in one ScriptChart).
This enhancement is tracked here -> #255

@LxLeChat
Copy link
Contributor

LxLeChat commented Jul 22, 2019

function RemoveCanvasAndCreateBase64Image (){
    var base64 = this.toBase64Image();
    var element = this.canvas;
    var parent = element.parentNode;
    var img = document.createElement('img');
    img.src = base64;
    img.name = element.id;
    element.before(img);
    parent.removeChild(element);
    //var scripttags = document.getElementsByTagName('script');
    //var scripttags = document.getElementsByTagName('script');
    //for (i=0;i<scripttags.length;){
    //    var parent = scripttags[i].parentNode;
    //    parent.removeChild(scripttags[i]);
    //}
};

this is an updated version of the function to create the base64 img.
This will work even if their are multiple charts on the same page.
It'll create a img tag, with the name of the charttype like this: <img src=... name="barcanvas">
For the moment i commented the part that delete script tags because all the code for the chart are in one huge script block.. and i want to make sure that the script tag is not deleted after the completion of the first chart ... !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design enhancement New feature or request
Development

No branches or pull requests

5 participants