I am attempting to create a Portlet with a Chart.j...
# suitescript
r
I am attempting to create a Portlet with a Chart.js chart in it. Can't add it as a library, am told NetSuite doesn't like it, but having trouble letting go. Can anyone point me to a reference / sample that shows it working in a Portlet or a Suitelet. My sincere thanks.
b
iframes work pretty weirdly in portlets
honestly the dashboard in general has a pretty hostile javascript environment. As an example, netsuite adds listeners for all buttons, so dont expect your buttons to do what you want
your general choices for your script are a Simple Form or an Inline HTML portlet
which one have you attempted
r
Inline HTML. I may punt and just go to Highcharts. I appreciate the feedback!
b
i remember high charts as the one that i had to work extra hard to get the require configuration correct
r
Well, after 2 days trying to get charts.js working (which I would do in either a suitelet or a portlet, I am not sure I can survive another unfriendly tool (and by unfriendly, I mean one that I can't figure out and for which there seems little reference material).
b
what are you trying?
if you are using inline html, that implies that you are writing your own html content
r
I am inserting the chart.js into the header of the html, and trying to figure how to get the rendering function to execute, which is generally done with the windows.onload event. If I could figure out how to intercept whatever the equivalent of windows.onload inside a suitelet or portlet, I think I'd be there.
b
should be the same thing
I would also recommend using ss2
the dashboard does not like added script elements, they are removed
party why i favor the form portlet, which allows you to add client script without using script elements
r
I am definitely using SS2.0, and I haven't tried the form portlet, but maybe I will. Thanks for the idea.
b
expect to use an inline html field to add your html and css
and then add a client script file to do your suitescript
I find it best to put the html/css in a separate html file and then load its contents to set your inline html field so you dont leave an ugly html string in your javascript
w
Copy code
function createStringHTML(dataObject) {
    var sHTML = '<html>\n';
    sHTML += '<head>\n';
    sHTML += '<script src="<https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.bundle.min.js>"></script>\n<script type="text/javascript" src="<https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@0.4.0>"></script>\n';
    sHTML += '<style>'
    sHTML += '.stat > table {\n';
.....
more style tags...
.....
    sHTML += '</style>'
    sHTML += '</head>\n';
    sHTML += '<body>\n';
    sHTML += '<table>'
    sHTML += '<tr><td colspan=2 style="padding-top:0.5cm;border-bottom:1px solid black;text-align:center;font-family:sans-serif;font-size:1cm;"><b>Sales Statistics for: </b>' + dataObject.htmlTitle + '</td></tr>'

    sHTML += '<tr><td style="width:17cm">'
    sHTML += ' <canvas id="ForecastVsBudget" style="width: 17cm; height:10cm;margin: 0 auto"></canvas>\n'
    sHTML += ' <canvas id="Bookings" style="width: 17cm; height:8cm;margin: 0 auto"></canvas>\n'
....
a bunch of more html...
....
    sHTML += '</td></tr></table>'
    sHTML += '</body>\n';
    sHTML += '<script type="text/javascript">\n';

    sHTML += 'function createCharts(){\n';
    sHTML += 'Chart.defaults.global.title.fontSize = 20;\n'
    sHTML += 'Chart.defaults.global.title.display = true;\n'
    sHTML += 'Chart.defaults.global.tooltips.mode = "index";\n'
    sHTML += 'Chart.defaults.global.tooltips.intersect = false;\n'
    sHTML += 'Chart.defaults.global.hover.mode = "nearest";\n'
    sHTML += 'Chart.defaults.global.hover.intersect = true;\n'
    sHTML += 'Chart.defaults.global.elements.line.fill = false;\n'
    sHTML += 'Chart.defaults.global.elements.line.borderWidth = 4;\n'
    sHTML += 'Chart.defaults.global.elements.line.tension = 0;\n'
    sHTML += 'Chart.defaults.scale.gridLines.drawBorder = false;\n'
    sHTML += 'Chart.defaults.global.legend.labels.boxWidth = 4;\n'
    sHTML += 'Chart.defaults.global.plugins.datalabels.backgroundColor = function(context) {return context.dataset.backgroundColor;};\n'
    sHTML += 'Chart.defaults.global.plugins.datalabels.borderRadius = 4;\n';
    sHTML += 'Chart.defaults.global.plugins.datalabels.color = "white";\n';
    sHTML += 'Chart.defaults.global.plugins.datalabels.font.weight = "bold";\n'

    sHTML += 'var options = ' + JSON.stringify(dataObject.bookings) + ';\n';
    sHTML += "var ctx = document.getElementById('Bookings').getContext('2d');\n";
    sHTML += "new Chart(ctx, options);\n";

    sHTML += 'var options = ' + JSON.stringify(dataObject.quotes) + ';\n';
    sHTML += "var ctx = document.getElementById('Quotes').getContext('2d');\n";
    sHTML += "new Chart(ctx, options);\n";

    sHTML += 'var options = ' + JSON.stringify(dataObject.noOrderAndQuotes) + ';\n';
    sHTML += "var ctx = document.getElementById('NoOrderAndQuotes').getContext('2d');\n";
    sHTML += "new Chart(ctx, options);\n";

    sHTML += 'var options = ' + JSON.stringify(dataObject.activitiesChart) + ';\n';
    sHTML += "var ctx = document.getElementById('activitiesChart').getContext('2d');\n";
    sHTML += "new Chart(ctx, options);\n";

    sHTML += 'var options = ' + JSON.stringify(dataObject.productSplit) + ';\n';
    sHTML += "options.options.plugins.datalabels = {\n";
    sHTML += "color: '#000',\n";
    sHTML += "formatter: (value, ctx) => { \n";
    sHTML += "  var sum = 0; \n";
    sHTML += "  var dataArr = ctx.chart.data.datasets[0].data; \n";
    sHTML += "  dataArr.map(data => { \n";
    sHTML += "    sum += data; \n";
    sHTML += "  }); \n";
    sHTML += '  var percentage = ctx.chart.data.labels[ctx.dataIndex] + "\\n" +  (value*100 / sum).toFixed(1)+"%"; \n';
    sHTML += "  return percentage;}};\n";
    sHTML += "var ctx = document.getElementById('ProductSplit').getContext('2d');\n";
    sHTML += "new Chart(ctx, options);\n";

    sHTML += 'var options = ' + JSON.stringify(dataObject.forecastVsBudget) + ';\n';
    sHTML += "options.options.scales =  {\n";
    sHTML += "xAxes: [{ "
    sHTML += "        stacked: true, "
    sHTML += "        gridLines: { display: false },"
    sHTML += "      }],"
    sHTML += '  yAxes: [{\n';
    sHTML += '    stacked: true,\n';
    sHTML += '    ticks: {\n';
    sHTML += '      callback: function(value) {return value.toString().replace(\/\\B(?=(\\d{3})+(?!\\d))/g, " ")+"k";}\n';
    sHTML += '    }\n';
    sHTML += '  }]\n';
    sHTML += "};\n";
    sHTML += "options.options.tooltips.callbacks = {\n";
    sHTML += "  label: function(tooltipItem, data) {\n";
    sHTML += '    if(data.datasets[tooltipItem.datasetIndex].label == "Percentage of budget" && tooltipItem.yLabel.toString() != "NaN"){return data.datasets[tooltipItem.datasetIndex].label + ": " + (parseFloat(tooltipItem.yLabel)*100).toFixed(0)+"%";}else if(tooltipItem.yLabel.toString() != "NaN"){return data.datasets[tooltipItem.datasetIndex].label + ": " + tooltipItem.yLabel.toString().replace(\/\\B(?=(\\d{3})+(?!\\d))/g, " ")+"k";}\n';
    sHTML += '  }\n';
    sHTML += '};\n';
    sHTML += "options.options.plugins.datalabels = {\n";
    sHTML += '  formatter: (value) => {return value.toString().replace(\/\\B(?=(\\d{3})+(?!\\d))/g, " ")+"k";}\n';
    sHTML += "}\n";
    sHTML += "var ctx = document.getElementById('ForecastVsBudget').getContext('2d');\n";
    sHTML += "new Chart(ctx, options);\n";
    sHTML += '}\n';

    sHTML += 'setTimeout(createCharts, 30);\n';
    sHTML += '</script>\n';
    return sHTML;
}
I've made a couple of chart.js suitelets a long time ago. I made them with an inline html-field and as a full html page. I had a bunch of functions that generated a dataObject and then it was put through this function(not pretty, but it worked):
It would generate this:
b
i too shall recommend you putting your html in a file
in your case, you have data from the suitelet, which i would recommend using a template engine to fill in your data
you could feel very respectable doing so since you are now implementing serverside rendering, one step away from the modern clientside rendering
w
haha, thank you. I'll take that as a compliment(?) 🙂 Yeah now that I go back and look at it, I really should've put the HTML in a separate file and perhaps used the built-in freemarker engine.
r
Tomas, I think it is great and I am grateful that you posted it here. I did just what you mentioned, an HTML Template with a little Freemarker. I think the stuff you posted looks terrific. Have a great weekend.