From 95f65248a0f7b3c344881831067365c0aef571fb Mon Sep 17 00:00:00 2001
From: SheetJS <dev@sheetjs.com>
Date: Mon, 1 Aug 2022 01:34:23 -0400
Subject: [PATCH] angularjs

---
 .../03-demos/10-database.md                   | 170 +++++++++++++++
 .../04-getting-started/03-demos/12-legacy.md  | 136 +++++++++++-
 .../04-getting-started/03-demos/14-grid.md    | 153 +++++++++++++
 .../docs/04-getting-started/03-demos/index.md |   5 +-
 docz/static/angularjs/index.html              |  78 +++++++
 docz/static/angularjs/ui-grid.html            | 201 ++++++++++++++++++
 docz/static/cdg/index.html                    | 167 +++++++++++++++
 7 files changed, 906 insertions(+), 4 deletions(-)
 create mode 100644 docz/docs/04-getting-started/03-demos/14-grid.md
 create mode 100644 docz/static/angularjs/index.html
 create mode 100644 docz/static/angularjs/ui-grid.html
 create mode 100644 docz/static/cdg/index.html

diff --git a/docz/docs/04-getting-started/03-demos/10-database.md b/docz/docs/04-getting-started/03-demos/10-database.md
index 1919cf3..0058705 100644
--- a/docz/docs/04-getting-started/03-demos/10-database.md
+++ b/docz/docs/04-getting-started/03-demos/10-database.md
@@ -563,6 +563,176 @@ for(var i = 0; i < localForage.length; ++i) aoo.push(JSON.parse(await localForag
 const wb = XLSX.utils.json_to_sheet(aoo);
 ```
 
+### Other SQL Databases
+
+The `generate_sql` function from ["Building Schemas from Worksheets"](#building-schemas-from-worksheets)
+can be adapted to generate SQL statements for a variety of databases, including:
+
+**PostgreSQL**
+
+The `pg` connector library was tested against the `generate_sql` output as-is. 
+
+The `rows` property of a query result is an array of objects that plays nice
+with `json_to_sheet`:
+
+```js
+const aoa = await connection.query(`SELECT * FROM DataTable`).rows;
+const worksheet = XLSX.utils.json_to_sheet(aoa);
+```
+
+**MySQL / MariaDB**
+
+The `mysql2` connector library was tested.  The differences are shown below,
+primarily stemming from the different quoting requirements and field types.
+
+<details><summary><b>Differences</b> (click to show)</summary>
+
+```js
+// highlight-start
+// define mapping between determined types and MySQL types
+const PG = { "n": "REAL", "s": "TEXT", "b": "TINYINT" };
+// highlight-end
+
+function generate_sql(ws, wsname) {
+
+  // generate an array of objects from the data
+  const aoo = XLSX.utils.sheet_to_json(ws);
+
+  // types will map column headers to types, while hdr holds headers in order
+  const types = {}, hdr = [];
+
+  // loop across each row object
+  aoo.forEach(row =>
+    // Object.entries returns a row of [key, value] pairs.  Loop across those
+    Object.entries(row).forEach(([k,v]) => {
+
+      // If this is first time seeing key, mark unknown and append header array
+      if(!types[k]) { types[k] = "?"; hdr.push(k); }
+
+      // skip null and undefined
+      if(v == null) return;
+
+      // check and resolve type
+      switch(typeof v) {
+        case "string": // strings are the broadest type
+          types[k] = "s"; break;
+        case "number": // if column is not string, number is the broadest type
+          if(types[k] != "s") types[k] = "n"; break;
+        case "boolean": // only mark boolean if column is unknown or boolean
+          if("?b".includes(types[k])) types[k] = "b"; break;
+        default: types[k] = "s"; break; // default to string type
+      }
+    })
+  );
+
+  // The final array consists of the CREATE TABLE query and a series of INSERTs
+  return [
+    // generate CREATE TABLE query and return batch
+    // highlight-next-line
+    `CREATE TABLE ${wsname} (${hdr.map(h =>
+      // highlight-next-line
+      `${h} ${PG[types[h]]}`
+    ).join(", ")});`
+  ].concat(aoo.map(row => { // generate INSERT query for each row
+    // entries will be an array of [key, value] pairs for the data in the row
+    const entries = Object.entries(row);
+    // fields will hold the column names and values will hold the values
+    const fields = [], values = [];
+    // check each key/value pair in the row
+    entries.forEach(([k,v]) => {
+      // skip null / undefined
+      if(v == null) return;
+      // highlight-next-line
+      fields.push(`${k}`);
+      // when the field type is numeric, `true` -> 1 and `false` -> 0
+      if(types[k] == "n") values.push(typeof v == "boolean" ? (v ? 1 : 0) : v);
+      // otherwise,
+      // highlight-next-line
+      else values.push(`"${v.toString().replaceAll('"', '""')}"`);
+    })
+    if(fields.length) return `INSERT INTO \`${wsname}\` (${fields.join(", ")}) VALUES (${values.join(", ")})`;
+  })).filter(x => x); // filter out skipped rows
+}
+```
+
+</details>
+
+The first property of a query result is an array of objects that plays nice
+with `json_to_sheet`:
+
+```js
+const aoa = await connection.query(`SELECT * FROM DataTable`)[0];
+const worksheet = XLSX.utils.json_to_sheet(aoa);
+```
+
+
+### Query Builders
+
+Query builders are designed to simplify query generation and normalize field
+types and other database minutiae.
+
+**Knex**
+
+The result of a `SELECT` statement is an array of objects:
+
+```js
+const aoo = await connection.select("*").from("DataTable");
+const worksheet = XLSX.utils.json_to_sheet(aoa);
+```
+
+Knex wraps primitive types when creating a table. `generate_sql` takes a `knex`
+connection object and uses the API:
+
+<details><summary><b>Generating a Table</b> (click to show)</summary>
+
+```js
+// define mapping between determined types and Knex types
+const PG = { "n": "float", "s": "text", "b": "boolean" };
+
+async function generate_sql(knex, ws, wsname) {
+
+  // generate an array of objects from the data
+  const aoo = XLSX.utils.sheet_to_json(ws);
+
+  // types will map column headers to types, while hdr holds headers in order
+  const types = {}, hdr = [];
+
+  // loop across each row object
+  aoo.forEach(row =>
+    // Object.entries returns a row of [key, value] pairs.  Loop across those
+    Object.entries(row).forEach(([k,v]) => {
+
+      // If this is first time seeing key, mark unknown and append header array
+      if(!types[k]) { types[k] = "?"; hdr.push(k); }
+
+      // skip null and undefined
+      if(v == null) return;
+
+      // check and resolve type
+      switch(typeof v) {
+        case "string": // strings are the broadest type
+          types[k] = "s"; break;
+        case "number": // if column is not string, number is the broadest type
+          if(types[k] != "s") types[k] = "n"; break;
+        case "boolean": // only mark boolean if column is unknown or boolean
+          if("?b".includes(types[k])) types[k] = "b"; break;
+        default: types[k] = "s"; break; // default to string type
+      }
+    })
+  );
+
+  await knex.schema.dropTableIfExists(wsname);
+  await knex.schema.createTable(wsname, (table) => { hdr.forEach(h => { table[PG[types[h]] || "text"](h); }); });
+  for(let i = 0; i < aoo.length; ++i) {
+    if(!aoo[i] || !Object.keys(aoo[i]).length) continue;
+    try { await knex.insert(aoo[i]).into(wsname); } catch(e) {}
+  }
+  return knex;
+}
+```
+
+</details>
+
 
 ### MongoDB Structured Collections
 
diff --git a/docz/docs/04-getting-started/03-demos/12-legacy.md b/docz/docs/04-getting-started/03-demos/12-legacy.md
index 830bbf6..90c87c2 100644
--- a/docz/docs/04-getting-started/03-demos/12-legacy.md
+++ b/docz/docs/04-getting-started/03-demos/12-legacy.md
@@ -20,6 +20,135 @@ designed to be referenced with `<script>` tags.
 
 ## Frameworks
 
+### AngularJS
+
+[AngularJS](https://en.wikipedia.org/wiki/AngularJS) was a front-end MVC
+framework that was abandoned by Google in 2022. It should not be confused with
+"Angular" the modern framework.
+
+The [Live demo](pathname:///angularjs/index.html) shows a simple table that is
+updated with file data and exported to spreadsheets.
+
+This demo uses AngularJS 1.5.0.
+
+<details><summary><b>Full Exposition</b> (click to show)</summary>
+
+**Array of Objects**
+
+A common data table is often stored as an array of objects:
+
+```js
+$scope.data = [
+  { Name: "Bill Clinton", Index: 42 },
+  { Name: "GeorgeW Bush", Index: 43 },
+  { Name: "Barack Obama", Index: 44 },
+  { Name: "Donald Trump", Index: 45 }
+];
+```
+
+This neatly maps to a table with `ng-repeat`:
+
+```html
+<table id="sjs-table">
+  <tr><th>Name</th><th>Index</th></tr>
+  <tr ng-repeat="row in data">
+    <td>{{row.Name}}</td>
+    <td>{{row.Index}}</td>
+  </tr>
+</table>
+```
+
+The `$http` service can request binary data using the `"arraybuffer"` response
+type coupled with `XLSX.read` with type `"array"`:
+
+```js
+  $http({
+    method:'GET',
+    url:'https://sheetjs.com/pres.xlsx',
+    responseType:'arraybuffer'
+  }).then(function(data) {
+    var wb = XLSX.read(data.data, {type:"array"});
+    var d = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
+    $scope.data = d;
+  }, function(err) { console.log(err); });
+```
+
+The HTML table can be directly exported with `XLSX.utils.table_to_book`:
+
+```js
+var wb = XLSX.utils.table_to_book(document.getElementById('sjs-table'));
+XLSX.writeFile(wb, "export.xlsx");
+```
+
+
+**Import Directive**
+
+A general import directive is fairly straightforward:
+
+- Define the `importSheetJs` directive in the app:
+
+```js
+app.directive("importSheetJs", [SheetJSImportDirective]);
+```
+
+- Add the attribute `import-sheet-js=""` to the file input element:
+
+```html
+<input type="file" import-sheet-js="" multiple="false"  />
+```
+
+- Define the directive:
+
+```js
+function SheetJSImportDirective() {
+  return {
+    scope: { opts: '=' },
+    link: function ($scope, $elm) {
+      $elm.on('change', function (changeEvent) {
+        var reader = new FileReader();
+
+        reader.onload = function (e) {
+          /* read workbook */
+          var ab = e.target.result;
+          var workbook = XLSX.read(ab);
+
+          /* DO SOMETHING WITH workbook HERE */
+        };
+
+        reader.readAsArrayBuffer(changeEvent.target.files[0]);
+      });
+    }
+  };
+}
+```
+
+
+**Export Service**
+
+An export can be triggered at any point!  Depending on how data is represented,
+a workbook object can be built using the utility functions.  For example, using
+an array of objects:
+
+```js
+/* starting from this data */
+var data = [
+  { name: "Barack Obama", pres: 44 },
+  { name: "Donald Trump", pres: 45 }
+];
+
+/* generate a worksheet */
+var ws = XLSX.utils.json_to_sheet(data);
+
+/* add to workbook */
+var wb = XLSX.utils.book_new();
+XLSX.utils.book_append_sheet(wb, ws, "Presidents");
+
+/* write workbook and force a download */
+XLSX.writeFile(wb, "sheetjs.xlsx");
+```
+
+</details>
+
 ### KnockoutJS
 
 [KnockoutJS](https://en.wikipedia.org/wiki/Knockout_(web_framework)) was a
@@ -28,8 +157,9 @@ popular MVVM framework.
 The [Live demo](pathname:///knockout/knockout.html) shows a view model that is
 updated with file data and exported to spreadsheets.
 
+<details><summary><b>Full Exposition</b> (click to show)</summary>
 
-#### State
+**State**
 
 Arrays of arrays are the simplest data structure for representing worksheets.
 
@@ -70,7 +200,7 @@ var aoa = model.aoa();
 var ws = XLSX.utils.aoa_to_sheet(aoa);
 ```
 
-#### Data Binding
+**Data Binding**
 
 `data-bind="foreach: ..."` provides a simple approach for binding to `TABLE`:
 
@@ -92,3 +222,5 @@ binding is possible using the `$parent` and `$index` binding context properties:
   </tr>
 </table>
 ```
+
+</details>
\ No newline at end of file
diff --git a/docz/docs/04-getting-started/03-demos/14-grid.md b/docz/docs/04-getting-started/03-demos/14-grid.md
new file mode 100644
index 0000000..aa1a543
--- /dev/null
+++ b/docz/docs/04-getting-started/03-demos/14-grid.md
@@ -0,0 +1,153 @@
+---
+sidebar_position: 14
+title: Data Grids and UI
+---
+
+Various JavaScript UI components provide a more interactive editing experience.
+Most are able to interchange with arrays of arrays or arrays of data objects.
+This demo focuses on a few open source data grids.
+
+:::note
+
+[SheetJS Pro](https://sheetjs.com/pro) offers additional features like styling
+and images. The UI tools typically support many of these advanced features.
+
+To eliminate any confusion, the live examples linked from this page demonstrate
+SheetJS Community Edition data interchange.
+
+:::
+
+## Managed Lifecycle
+
+Many UI components tend to manage the entire lifecycle, providing methods to
+import and export data.
+
+The `sheet_to_json` utility function generates arrays of objects, which is
+suitable for a number of libraries.  When more advanced shapes are needed,
+it is easier to munge the output of an array of arrays.
+
+
+### Canvas DataGrid
+
+After extensive testing, [`canvas-datagrid`](https://canvas-datagrid.js.org/demo.html)
+stood out as a very high-performance grid with an incredibly simple API.
+
+[Click here for a live integration demo.](pathname:///cdg/index.html)
+
+<details><summary><b>Full Exposition</b> (click to show)</summary>
+
+**Obtaining the Library**
+
+The `canvas-datagrid` NodeJS packages include a minified script that can be
+directly inserted as a script tag.  The unpkg CDN also serves this script:
+
+```html
+<script src="https://unpkg.com/canvas-datagrid/dist/canvas-datagrid.js"></script>
+```
+
+**Previewing Data**
+
+The HTML document needs a container element:
+
+```html
+<div id="gridctr"></div>
+```
+
+Grid initialization is a one-liner:
+
+```js
+var grid = canvasDatagrid({
+  parentNode: document.getElementById('gridctr'),
+  data: []
+});
+```
+
+For large data sets, it's necessary to constrain the size of the grid.
+
+```js
+grid.style.height = '100%';
+grid.style.width = '100%';
+```
+
+Once the workbook is read and the worksheet is selected, assigning the data
+variable automatically updates the view:
+
+```js
+grid.data = XLSX.utils.sheet_to_json(ws, {header:1});
+```
+
+This demo previews the first worksheet.
+
+**Editing**
+
+`canvas-datagrid` handles the entire edit cycle.  No intervention is necessary.
+
+**Saving Data**
+
+`grid.data` is immediately readable and can be converted back to a worksheet.
+Some versions return an array-like object without the length, so a little bit of
+preparation may be needed:
+
+```js
+/* converts an array of array-like objects into an array of arrays */
+function prep(arr) {
+  var out = [];
+  for(var i = 0; i < arr.length; ++i) {
+    if(!arr[i]) continue;
+    if(Array.isArray(arr[i])) { out[i] = arr[i]; continue };
+    var o = new Array();
+    Object.keys(arr[i]).forEach(function(k) { o[+k] = arr[i][k] });
+    out[i] = o;
+  }
+  return out;
+}
+
+/* build worksheet from the grid data */
+var ws = XLSX.utils.aoa_to_sheet(prep(grid.data));
+
+/* build up workbook */
+var wb = XLSX.utils.book_new();
+XLSX.utils.book_append_sheet(wb, ws, 'SheetJS');
+
+/* generate download */
+XLSX.writeFile(wb, "SheetJS.xlsx");
+```
+
+**Additional Features**
+
+This demo barely scratches the surface.  The underlying grid component includes
+many additional features including massive data streaming, sorting and styling.
+
+</details>
+
+
+### Angular UI Grid
+
+:::warning
+
+This UI Grid is for AngularJS, not the modern Angular.  New projects should not
+use AngularJS.  This demo is included for legacy applications.
+
+The [AngularJS demo](./legacy#angularjs) covers more general strategies.
+
+:::
+
+[Click here for a live integration demo.](pathname:///angularjs/ui-grid.html)
+
+<details><summary><b>Notes</b> (click to show)</summary>
+
+The library does not provide any way to modify the import button, so the demo
+includes a simple directive for a HTML File Input control.  It also includes a
+sample service for export which adds an item to the export menu.
+
+The demo `SheetJSImportDirective` follows the prescription from the README for
+File input controls using `readAsArrayBuffer`, converting to a suitable
+representation and updating the scope.
+
+`SheetJSExportService` exposes export functions for `XLSB` and `XLSX`.  Other
+file formats can be exported by changing the `bookType` variable.  It grabs
+values from the grid, builds an array of arrays, generates a workbook and forces
+a download.  By setting the `filename` and `sheetname` options in the `ui-grid`
+options, the output can be controlled.
+
+</details>
diff --git a/docz/docs/04-getting-started/03-demos/index.md b/docz/docs/04-getting-started/03-demos/index.md
index 54d04e6..09e64bb 100644
--- a/docz/docs/04-getting-started/03-demos/index.md
+++ b/docz/docs/04-getting-started/03-demos/index.md
@@ -18,7 +18,7 @@ The demo projects include small runnable examples and short explainers.
 
 ### Frameworks
 
-- [`Angular.JS`](https://github.com/SheetJS/SheetJS/tree/master/demos/angular/)
+- [`Angular.JS`](./legacy#angularjs)
 - [`Angular 2+ and Ionic`](https://github.com/SheetJS/SheetJS/tree/master/demos/angular2/)
 - [`Knockout`](./legacy#knockout)
 - [`Meteor`](https://github.com/SheetJS/SheetJS/tree/master/demos/meteor/)
@@ -27,10 +27,11 @@ The demo projects include small runnable examples and short explainers.
 
 ### Front-End UI Components
 
-- [`canvas-datagrid`](https://github.com/SheetJS/SheetJS/tree/master/demos/datagrid/)
+- [`canvas-datagrid`](./grid#canvas-datagrid)
 - [`x-spreadsheet`](https://github.com/SheetJS/SheetJS/tree/master/demos/xspreadsheet/)
 - [`react-data-grid`](https://github.com/SheetJS/SheetJS/tree/master/demos/react/modify/)
 - [`vue3-table-light`](https://github.com/SheetJS/SheetJS/tree/master/demos/vue/modify/)
+- [`angular-ui-grid`](./grid#angular-ui-grid)
 
 ### Platforms and Integrations
 
diff --git a/docz/static/angularjs/index.html b/docz/static/angularjs/index.html
new file mode 100644
index 0000000..71ab290
--- /dev/null
+++ b/docz/static/angularjs/index.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<!-- sheetjs (C) 2013-present  SheetJS http://sheetjs.com -->
+<!-- vim: set ts=2: -->
+<html ng-app="s5s">
+<head>
+  <title>SheetJS + AngularJS</title>
+  <!-- Angular -->
+  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
+
+  <!-- SheetJS library -->
+  <script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
+  <script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
+</head>
+<body>
+<pre>
+<b><a href="http://sheetjs.com">SheetJS + AngularJS demo</a></b>
+
+The core library can be used as-is in AngularJS applications.
+The <a href="https://docs.sheetjs.com">Community Edition README</a> details some common use cases.
+We also have some <a href="http://sheetjs.com/demos/">more public demos</a>
+
+This demo shows:
+- $http request for XLSX file and scope update with data
+- HTML table using ng-repeat
+- XLSX table export using `XLSX.utils.table_to_book`
+
+<a href="https://sheetjs.com/pres.xlsx">Sample Spreadsheet</a>
+
+The table has hardcoded fields `Name` and `Index`.
+
+</pre>
+
+<div ng-controller="sheetjs">
+
+<table id="s5s-table">
+  <tr><th>Name</th><th>Index</th></tr>
+  <tr ng-repeat="row in data">
+    <td>{{row.Name}}</td>
+    <td>{{row.Index}}</td>
+  </tr>
+</table>
+
+<button id="exportbtn">Export Table</button>
+
+</div>
+
+<script>
+var app = angular.module('s5s', []);
+app.controller('sheetjs', function($scope, $http) {
+  $http({
+    method:'GET',
+    url:'https://sheetjs.com/pres.xlsx',
+    responseType:'arraybuffer'
+  }).then(function(data) {
+    var wb = XLSX.read(data.data, {type:"array"});
+    var d = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
+    $scope.data = d;
+  }, function(err) { console.log(err); });
+});
+exportbtn.addEventListener('click', function() {
+  var wb = XLSX.utils.table_to_book(document.getElementById('s5s-table'));
+  XLSX.writeFile(wb, "export.xlsx");
+});
+</script>
+<script type="text/javascript">
+/* eslint no-use-before-define:0 */
+  var _gaq = _gaq || [];
+  _gaq.push(['_setAccount', 'UA-36810333-1']);
+  _gaq.push(['_trackPageview']);
+
+  (function() {
+    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+  })();
+</script>
+</body>
+</html>
diff --git a/docz/static/angularjs/ui-grid.html b/docz/static/angularjs/ui-grid.html
new file mode 100644
index 0000000..0a4f6b7
--- /dev/null
+++ b/docz/static/angularjs/ui-grid.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html>
+<!-- SheetJS (C) 2013-present  SheetJS http://sheetjs.com -->
+<!-- vim: set ts=2: -->
+<html ng-app="app">
+<head>
+  <title>SheetJS + AngularJS + ui-grid</title>
+  <!-- Angular -->
+  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.js"></script>
+  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-touch.js"></script>
+  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-animate.js"></script>
+
+  <!-- SheetJS library -->
+  <script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
+  <script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
+
+  <!-- ui-grid -->
+  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-grid/4.11.0/ui-grid.js"></script>
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-grid/4.11.0/ui-grid.css"/>
+<style>
+.grid1 {
+  width: 500px;
+  height: 400px;
+};
+</style>
+</head>
+<body>
+<pre>
+<b><a href="http://sheetjs.com">SheetJS + AngularJS demo</a></b>
+
+The core library can be used as-is in AngularJS applications.
+The <a href="https://docs.sheetjs.com">Community Edition README</a> details some common use cases.
+We also have some <a href="http://sheetjs.com/demos/">more public demos</a>
+
+This demo shows:
+- SheetJSExportService: a service for exporting data from a ui-grid
+- SheetJSImportDirective: a directive providing a file input button for import
+
+<a href="https://obamawhitehouse.archives.gov/sites/default/files/omb/budget/fy2014/assets/receipts.xls">Sample Spreadsheet</a>
+</pre>
+
+<div ng-controller="MainCtrl">
+  <input type="file" import-sheet-js="" opts="gridOptions" multiple="false"  />
+  <div id="grid1" ui-grid="gridOptions" ui-grid-selection ui-grid-exporter class="grid"></div>
+</div>
+
+<!-- SheetJS Service -->
+<script>
+function SheetJSExportService(uiGridExporterService) {
+
+function exportSheetJS(gridApi, wopts) {
+  var columns = uiGridExporterService.getColumnHeaders(gridApi.grid, 'all');
+  var data = uiGridExporterService.getData(gridApi.grid, 'all', 'all');
+
+  var fileName = gridApi.grid.options.filename || 'SheetJS';
+  fileName += wopts.bookType ? "." + wopts.bookType : '.xlsx';
+
+  var sheetName = gridApi.grid.options.sheetname || 'Sheet1';
+
+  var wb = XLSX.utils.book_new(), ws = uigrid_to_sheet(data, columns);
+  XLSX.utils.book_append_sheet(wb, ws, sheetName);
+  XLSX.writeFile(wb, fileName);
+}
+
+var service = {};
+service.exportXLSB = function exportXLSB(gridApi) { return exportSheetJS(gridApi, { bookType: 'xlsb', bookSST: true, type: 'array' }); };
+service.exportXLSX = function exportXLSX(gridApi) { return exportSheetJS(gridApi, { bookType: 'xlsx', bookSST: true, type: 'array' }); }
+
+return service;
+
+/* utilities */
+function uigrid_to_sheet(data, columns) {
+  var o = [], oo = [], i = 0, j = 0;
+
+  /* column headers */
+  for(j = 0; j < columns.length; ++j) oo.push(get_value(columns[j]));
+  o.push(oo);
+
+  /* table data */
+  for(i = 0; i < data.length; ++i) {
+    oo = [];
+    for(j = 0; j < data[i].length; ++j) oo.push(get_value(data[i][j]));
+    o.push(oo);
+  }
+  /* aoa_to_sheet converts an array of arrays into a worksheet object */
+  return XLSX.utils.aoa_to_sheet(o);
+}
+
+function get_value(col) {
+  if(!col) return col;
+  if(col.value) return col.value;
+  if(col.displayName) return col.displayName;
+  if(col.name) return col.name;
+  return null;
+}
+}
+
+function SheetJSImportDirective() {
+return {
+  scope: { opts: '=' },
+  link: function($scope, $elm) {
+    $elm.on('change', function(changeEvent) {
+      var reader = new FileReader();
+
+      reader.onload = function(e) {
+        /* read workbook */
+        var ab = e.target.result;
+        var wb = XLSX.read(ab);
+
+        /* grab first sheet */
+        var wsname = wb.SheetNames[0];
+        var ws = wb.Sheets[wsname];
+
+        /* grab first row and generate column headers */
+        var aoa = XLSX.utils.sheet_to_json(ws, {header:1, raw:false});
+        var cols = [];
+        for(var i = 0; i < aoa[0].length; ++i) cols[i] = { field: aoa[0][i] };
+
+        /* generate rest of the data */
+        var data = [];
+        for(var r = 1; r < aoa.length; ++r) {
+          data[r-1] = {};
+          for(i = 0; i < aoa[r].length; ++i) {
+            if(aoa[r][i] == null) continue;
+            data[r-1][aoa[0][i]] = aoa[r][i];
+          }
+        }
+
+        /* update scope */
+        $scope.$apply(function() {
+          $scope.opts.columnDefs = cols;
+          $scope.opts.data = data;
+        });
+      };
+
+      reader.readAsArrayBuffer(changeEvent.target.files[0]);
+    });
+  }
+};
+}
+</script>
+
+<!-- App -->
+<script>
+var app = angular.module('app', ['ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.selection', 'ui.grid.exporter']);
+
+/* Inject SheetJSExportService */
+app.factory('SheetJSExportService', SheetJSExportService);
+SheetJSExportService.inject = ['uiGridExporterService'];
+
+app.controller('MainCtrl', ['$scope', '$http','SheetJSExportService', function($scope, $http, SheetJSExportService) {
+  $scope.gridOptions = {
+    columnDefs: [
+      { field: 'name' },
+      { field: 'gender', visible: false},
+      { field: 'company' }
+    ],
+    enableGridMenu: true,
+    enableSelectAll: true,
+    exporterMenuPdf: false,
+    exporterMenuCsv: false,
+    showHeader: true,
+    onRegisterApi: function(gridApi){
+      $scope.gridApi = gridApi;
+    },
+    /* SheetJS Service setup */
+    filename: "SheetJSAngular",
+    sheetname: "ng-SheetJS",
+    gridMenuCustomItems: [
+      {
+        title: 'Export all data as XLSX',
+        action: function() { SheetJSExportService.exportXLSX($scope.gridApi); },
+        order: 200
+      },
+      {
+        title: 'Export all data as XLSB',
+        action: function() { SheetJSExportService.exportXLSB($scope.gridApi); },
+        order: 201
+      }
+    ]
+  };
+
+  $http.get('https://cdn.rawgit.com/angular-ui/ui-grid.info/gh-pages/data/100.json').success(function(data) { $scope.gridOptions.data = data; });
+
+}]);
+app.directive("importSheetJs", [SheetJSImportDirective]);
+
+</script>
+<script type="text/javascript">
+/* eslint no-use-before-define:0 */
+  var _gaq = _gaq || [];
+  _gaq.push(['_setAccount', 'UA-36810333-1']);
+  _gaq.push(['_trackPageview']);
+
+  (function() {
+    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+  })();
+</script>
+</body>
+</html>
diff --git a/docz/static/cdg/index.html b/docz/static/cdg/index.html
new file mode 100644
index 0000000..055c3cf
--- /dev/null
+++ b/docz/static/cdg/index.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<!-- xlsx.js (C) 2013-present  SheetJS http://sheetjs.com -->
+<!-- vim: set ts=2: -->
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title>SheetJS + canvas-datagrid Live Demo</title>
+<style>
+#drop{
+	border:2px dashed #bbb;
+	-moz-border-radius:5px;
+	-webkit-border-radius:5px;
+	border-radius:5px;
+	padding:25px;
+	text-align:center;
+	font:20pt bold,"Vollkorn";color:#bbb
+}
+#b64data{
+	width:100%;
+}
+a { text-decoration: none }
+</style>
+</head>
+<body>
+<pre>
+<b><a href="http://sheetjs.com">SheetJS Data Preview Live Demo</a></b>
+
+<a href="https://canvas-datagrid.js.org/">canvas-datagrid component library</a>
+
+<a href="https://github.com/SheetJS/sheetjs">Source Code Repo</a>
+<a href="https://github.com/SheetJS/sheetjs/issues">Issues?  Something look weird?  Click here and report an issue</a>
+
+<div id="drop">Drop a spreadsheet file here to see sheet data</div>
+<input type="file" name="xlfile" id="xlf" /> ... or click here to select a file
+<textarea id="b64data">... or paste a base64-encoding here</textarea>
+<b>Advanced Demo Options:</b>
+</pre>
+<p><input type="submit" value="Export to XLSX!" id="xport" onclick="export_xlsx();" disabled="true"></p>
+<div id="htmlout"></div>
+<br />
+<script src="https://unpkg.com/canvas-datagrid/dist/canvas-datagrid.js"></script>
+<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/shim.min.js"></script>
+<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
+<script>
+/*jshint browser:true */
+/* eslint-env browser */
+/* eslint no-use-before-define:0 */
+/*global Uint8Array, Uint16Array, ArrayBuffer */
+/*global XLSX */
+
+var cDg;
+
+var process_wb = (function() {
+	var XPORT = document.getElementById('xport');
+	var HTMLOUT = document.getElementById('htmlout');
+
+	return function process_wb(wb) {
+		/* get data */
+		var ws = wb.Sheets[wb.SheetNames[0]];
+		var data = XLSX.utils.sheet_to_json(ws, {header:1});
+
+		/* update canvas-datagrid */
+		if(!cDg) cDg = canvasDatagrid({ parentNode:HTMLOUT, data:data });
+		cDg.style.height = '100%';
+		cDg.style.width = '100%';
+		cDg.data = data;
+		XPORT.disabled = false;
+
+		/* create schema (for A,B,C column headings) */
+		var range = XLSX.utils.decode_range(ws['!ref']);
+		for(var i = range.s.c; i <= range.e.c; ++i) cDg.schema[i - range.s.c].title = XLSX.utils.encode_col(i);
+
+		HTMLOUT.style.height = (window.innerHeight - 400) + "px";
+		HTMLOUT.style.width = (window.innerWidth - 50) + "px";
+
+		if(typeof console !== 'undefined') console.log("output", new Date());
+	};
+})();
+
+var do_file = (function() {
+	return function do_file(files) {
+		var f = files[0];
+		var reader = new FileReader();
+		reader.onload = function(e) {
+			if(typeof console !== 'undefined') console.log("onload", new Date());
+			var data = e.target.result;
+			data = new Uint8Array(data);
+			process_wb(XLSX.read(data, {type: 'array'}));
+		};
+		reader.readAsArrayBuffer(f);
+	};
+})();
+
+(function() {
+	var drop = document.getElementById('drop');
+	if(!drop.addEventListener) return;
+
+	function handleDrop(e) {
+		e.stopPropagation();
+		e.preventDefault();
+		do_file(e.dataTransfer.files);
+	}
+
+	function handleDragover(e) {
+		e.stopPropagation();
+		e.preventDefault();
+		e.dataTransfer.dropEffect = 'copy';
+	}
+
+	drop.addEventListener('dragenter', handleDragover, false);
+	drop.addEventListener('dragover', handleDragover, false);
+	drop.addEventListener('drop', handleDrop, false);
+})();
+
+(function() {
+	var xlf = document.getElementById('xlf');
+	if(!xlf.addEventListener) return;
+	function handleFile(e) { do_file(e.target.files); }
+	xlf.addEventListener('change', handleFile, false);
+})();
+
+var export_xlsx = (function() {
+	function prep(arr) {
+		var out = [];
+		for(var i = 0; i < arr.length; ++i) {
+			if(!arr[i]) continue;
+			if(Array.isArray(arr[i])) { out[i] = arr[i]; continue };
+			var o = new Array();
+			Object.keys(arr[i]).forEach(function(k) { o[+k] = arr[i][k] });
+			out[i] = o;
+		}
+		return out;
+	}
+
+	return function export_xlsx() {
+		if(!cDg) return;
+		/* convert canvas-datagrid data to worksheet */
+		var new_ws = XLSX.utils.aoa_to_sheet(prep(cDg.data));
+
+		/* build workbook */
+		var new_wb = XLSX.utils.book_new();
+		XLSX.utils.book_append_sheet(new_wb, new_ws, 'SheetJS');
+
+		/* write file and trigger a download */
+		XLSX.writeFile(new_wb, 'SheetJSCanvasDataGridExport.xlsx', {bookSST:true});
+	};
+})();
+
+(async() => {
+	const ab = await(await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer();
+	process_wb(XLSX.read(ab));
+})();
+</script>
+<script type="text/javascript">
+/* eslint no-use-before-define:0 */
+	var _gaq = _gaq || [];
+	_gaq.push(['_setAccount', 'UA-36810333-1']);
+	_gaq.push(['_trackPageview']);
+
+	(function() {
+		var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+		ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+		var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+	})();
+</script>
+</body>
+</html>