In Create GraphML markups from Apps Script I showed how to render GraphML from Apps Script/JavaScript. Since GraphML is just a markup language, we can use the exact same code to render any XML family markup.

What we’ll create in this article

In this article, we’ll use the same library as in Create GraphML markups from Apps Script to automate the rendering of nicely formatted HTML from JavaScript.

Getting started

Add a reference to the latest bmXml library at

1WlC-eOf-d3krlXxVSjn0XSp2tUrRhD2cqf7eqyJzO8l7mNGH2O082yX5

If you’ve used my libraries lately, you’ll know I favor an Exports namespace to organize and control access, so first create a script file called Exports like this.

var Exports = {

get libExports() {
return bmXml.Exports
},

/**
* Html Extension namespace example
* @return {Proxy}
*/
get Html() {
return this.libExports.Html
}

}
Exports.gs

The Html renderer

The library’s built in Html renderer looks like this. Other than the root definition it’s the same as the GML renderer.

/**
* @namespace Html
* wrapper for Html
*/
const Html = (() => {


const self = {}

self.root = {
tag: 'html'
}

self.setRoot = (newRoot) => {
self.root = newRoot
return self
}

/**
* this is the parent item for a complete html rendering
* @param {object} p params
* @param {XmlItem[]} p.children the content
* @param {number} [p.indent=2] number of spaces to indent each children content by
* @return {string} the rendered string
*/
self.render = ({ children, indent } = {}) => Exports.newAnyMl({ root: self.root }).render({ children, indent })

return self
})()
Html renderer in the bmXml library

Examples

Normally you’d be creating objects and/or JSON data to describe the data you want to convert as part of your app, but for the sake of illustration, we’ll just create some fake stuff in these first examples.

Add a head section

This head section brings in the materializecss framework

  const head = {
tag: "head",
children: [{
tag: "link",
attrs: {
rel: "stylesheet",
href: "https://fonts.googleapis.com/icon?family=Material Icons"
}
}, {
tag: "link",
attrs: {
type: "text/css",
rel: "stylesheet",
href: "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
}
}, {
tag: "meta",
attrs: {
name: "viewport",
content: "width=device-width, initial-scale=1.0"
}
}, {
tag: "script",
attrs: {
src: "https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js",
}
}, {
tag: "title",
children: ["Materialize web app from JSON"]
}]
}

console.log(Html.render({ children: [head] }))
Head section

And the rendered result is

<html>
  <head>
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js">
    <title>Materialize web app from JSON</title>
  </head>
</html>

Add a body section

This body will contain a few materialize elements

  const button = {
tag: "a",
attrs: {
class: "btn-floating btn-large waves-effect waves-light purple"
},
children: [{
tag: "i",
attrs: {
class: "material-icons"
},
children: ["code"]
}]
}

const content = [{
tag: "span",
attrs: {
class: "amber-text"
},
children: ["Write great", button, "with Google Apps Script"]
}]

const panel = {
tag: "div",
attrs: {
class: "card-panel indigo"
},
children: content
}


const body = {
tag: "body",
children: [panel]
}
console.log(Html.render({ children: [head, body] }))
a body

The rendered results look like this

<html>
  <head>
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js">
    <title>Materialize web app from JSON</title>
  </head>
  <body>
    <div class="card-panel indigo">
      <span class="amber-text">Write great
        <a class="btn-floating btn-large waves-effect waves-light purple">
          <i class="material-icons">code</i>
        </a>with Google Apps Script
      </span>
    </div>
  </body>
</html>

And in the browser, to make sure it works.

generated body from bmXml

Add a table to the body

Let’s go a bit further and add a table – We’ll just make up some data manually rather than picking up the data from a sheet or somewhere else – which would be the normal usage.

  const tableData = [{
"type": "User Properties",
"usage": "persistent data specific to a user"
}, {
"type": "Script Properties",
"usage": "persistent data for all users of a script"
}, {
"type": "Document Properties",
"usage": "persistent data for all users of a document"
}]
table data
Make a header row

Note that children is usually an array of child elements, but if there is only 1 item, for example some text, then you can just pass the value for convenience. (i.e. children: ‘text’ and children: [‘text’] are equivalent)

  // extract all the unique props
const headers = Array.from(tableData.reduce((p, c) => {
Reflect.ownKeys(c).forEach(k => p.add(k))
return p
}, new Set()))

const thead = {
tag: "thead",
children: [{
tag: 'tr',
children: headers.map(header => ({
tag: "th",
children: header
}))
}]
}
Make a header row
Make the body
  const tbody = {
tag: "tbody",
children: tableData.map(row => ({
tag: "tr",
children: headers.map(header => ({
tag: "td",
children: row[header]
}))
}))
}
table body
Make the table
  const table = {
tag: "table",
attrs: {
class: "amber"
},
children: [thead, tbody]
}

body.children = body.children.concat(table)

console.log(Html.render({ children: [head, body] }))
construct the table

which now renders like this

<html>
  <head>
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js">
    <title>Materialize web app from JSON</title>
  </head>
  <body>
    <div class="card-panel indigo">
      <span class="amber-text">Write great
        <a class="btn-floating btn-large waves-effect waves-light purple">
          <i class="material-icons">code</i>
        </a>with Google Apps Script
      </span>
    </div>
    <table class="amber">
      <thead>
        <tr>
          <th>type</th>
          <th>usage</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>User Properties</td>
          <td>persistent data specific to a user</td>
        </tr>
        <tr>
          <td>Script Properties</td>
          <td>persistent data for all users of a script</td>
        </tr>
        <tr>
          <td>Document Properties</td>
          <td>persistent data for all users of a document</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

And displays in the browser like this

table with bmXml

Special characters

Some special characters in HTML need to be escaped. The renderer automatically detects these and escapes them as required.

  tbody.children = [{
tag: 'tr',
children: [{
tag: 'td',
children: ['< lt ; semi ; > gt']
}, {
tag: 'td',
children: ['どういたしまして']
}]
}]
table.children = [thead, tbody]

console.log(Html.render({ children: [head, body] }))
special characters

which renders as

	
<html>
  <head>
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js">
    <title>Materialize web app from JSON</title>
  </head>
  <body>
    <div class="card-panel indigo">
      <span class="amber-text">Write great
        <a class="btn-floating btn-large waves-effect waves-light purple">
          <i class="material-icons">code</i>
        </a>with Google Apps Script
      </span>
    </div>
    <table class="amber">
      <thead>
        <tr>
          <th>type</th>
          <th>usage</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>&lt; lt ; semi ; &gt; gt</td>
          <td>どういたしまして</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>
        

And is displayed in the browser like this

special characters bmXml

Tweaking the root

As you saw earlier the Html renderer is just a few lines of code, so you could just copy it and create a customized one. However you can also set the current root to something else. Say you wanted to add a language arttribute to the standard renderer, you could do this.

  console.log(
Html.setRoot({ ...Html.root, attrs: { language: "jp" } })
.render({ children: [head, body] })
)
Tweaking the root

which would tweak the root tag like this

<html language="jp"> .... </html>

Some real data

Now let’s apply that to some real data from a sheet – perhaps you are creating an Html service that needs to dynamically tabulate some data from a spreadsheet.

In this example I’m using my fiddler library (https://ramblings.mcpher.com/vuejs-apps-script-add-ons/helper-for-fiddler/) to deal with objectifying the sheet – so turning it into an Html table is a trivial exercise.

Add prefiddler library

First we’ll need to update the Exports script with a reference to bmPreFiddler and add the library to the project – bmFiddler library id is below:

13JUFGY18RHfjjuKmIRRfvmGlCYrEkEtN6uUm-iLUcxOUFRJD-WBX-tkR
var Exports = {


get libExports() {
return bmXml.Exports
},


/**
* Html Extension namespace example
* @return {Proxy}
*/
get Html() {
return this.libExports.Html
},

/**
* @implements bmPreFiddler.fiddler
* @return {Fiddler}
*/
newFiddler(...args) {
return bmPreFiddler.PreFiddler().getFiddler (...args)
}

}
updated exports

Get the sheet

This sheet is public, so you can use it for testing if you like.

  // get an example sheet
const fiddler = Exports.newFiddler({
id: "1Sf2fjjsX7tgDg68sV3hYaYdUHE4WOLUjMUQIt-3p10I",
sheetName: "Sheet1"
})
fiddle the sheet

Tweak the table header

  // patch in the headers from the sheet
thead.children = [{
tag: "tr",
children: fiddler.getHeaders().map(header => ({
tag: "th",
children: [header]
}))
}]
tweak header

Tweak the table data

Just doing the first 10 rows.

  // patch in the first 10 rows of data from the sheet 
tbody.children = fiddler.getData().slice(0,10).map(row => ({
tag: "tr",
children: fiddler.getHeaders().map(header => ({
tag: "td",
children: [row[header]]
}))
}))
tweak data

Construct the table

  table.children = [thead, tbody]
console.log(Html.render({ children: [head, body] }))
construct and render the table

Here’s how it looks in the browser

fiddler data bmXml

Next

If you haven’t read the article on GraphML, check out Create GraphML markups from Apps Script which uses the same bmXml library to create markup for network analysis. You can use codepen.io or playcode.io (I prefer playcode nowadays) to quickly try out your generated html.

Links

bmXml – 1WlC-eOf-d3krlXxVSjn0XSp2tUrRhD2cqf7eqyJzO8l7mNGH2O082yX5 – (or github)

testBmXml – the examples in this article (or github)

bmPreFiddler – 13JUFGY18RHfjjuKmIRRfvmGlCYrEkEtN6uUm-iLUcxOUFRJD-WBX-tkR

And for the background on fiddler see Fiddler – A functional approach to fiddling with sheet data