Introduction
Part 1: Apples
Introduction
1.1: Fuji
1.2: Macintosh
1.3: Cameo
Summary
Part 2: Pears
Introduction
2.1: Bosc
2.2:D'Anjou
2.3:Warden
Summary
Yes, of course there's already a gadget you can use to create a table of contents for your page (Menu > Insert > Table of Contents). It's a rather nice gadget, but because I've intentionally numbered some sections and not others, there's a mental mismatch.
As you see, the Table of Contents generator gave section numbers to everything, so depending where you look, 2.2 is either Fuji apples or Bosc pears.
I didn't want to remove the section headings, and there was no real way to remove them via the Table of Contents gadget.
If you're not familiar with Google Apps Script, it's as you think: a scripting engine for Google properties. It's particularly powerful for scripting spreadsheets, and. as of October 22, you can process Google Sites with Google Apps Script.
Before going any farther, let me show you what I generated with Google Apps Script:
I'm no HTML Picasso, but it's not bad. Not bad at all. The links work, thanks to Google Sites automatically injecting <a name> tags with every <h[1-6]> tag.
The code
Let me share the code with you. First, here's how it's invoked:
function run() {
createToc(
'https://sites.google.com/site/sitename/source-page',
'https://sites.google.com/site/sitename/destination-page');
}
Yes, so you can specify a page from which to read and generate a second page with the table-of-contents included. (Side note: This actually gives me lots of ideas about workflow -- envision if the source URL was a private site where drafts were written, you could use Google Apps Script as a way to push from a staging area to production.)
OK, the code. It's mostly processing XML and generating HTML.
// I do not warrant this code in any way.
// If you break something important, don't come crying to me.
// Actually, check your Sites revision history first.
// Then go cry.
// Load content from fromUrl. Find the table of contents, and stick it in the document
// Save at toUrl.
function createToc(fromUrl, toUrl) {
var page = SitesApp.getPageByUrl(fromUrl);
var content = page.getHtmlContent();
var doc = Xml.parse(content, true);
var root = doc.getElement();
var toc=[];
parse(toc, root);
var prefixDiv = "<div id='kberg-toc'>";
var postfixHtml = "</div><div id='kberg-toc-coda'/>";
var codaDiv = "<div id='kberg-toc-coda'/>";
var html = prefixDiv + tocToHtml(toc) + postfixHtml;
var index = content.indexOf(prefixDiv);
var newContent;
if (index == -1) {
var x = "<div dir='ltr'>";
index = content.indexOf("<div dir='ltr'>") + x.length;
Logger.log("3: " + index);
var outdex = index;
newContent = content.substr(0, index) + html + content.substr(outdex);
} else {
var outdex = content.indexOf(codaDiv);
newContent = content.substr(0, index) + html + content.substr(outdex + codaDiv.length);
}
var newpage = SitesApp.getPageByUrl(toUrl);
newpage.setHtmlContent(newContent);
}
// Recurse through the XML, finding <h[1-5]> tags.
function parse(toc, element) {
var name = element.getName().getLocalName().toUpperCase();
if (name == "H1" || name == "H2" || name == "H3" || name == "H4" || name == "H5") {
var level = parseInt(name.substring(1));
var anchorChildren = element.getElements("a");
var anchorText = (anchorChildren.length > 0)
? anchorChildren[0].getAttribute("name").getValue() : "";
var item = {
level : level,
anchor : anchorText,
description : element.getText() };
toc.push(item);
} else {
for each (var child in element.getElements()) {
parse(toc, child);
}
}
}
// Convert the table of contents entries into HTML.
function tocToHtml(toc) {
var html =
"<div style='width: 250px; background-color: #e8e8e8;'>\n"
+ "<br/><b>Contents</b><br/><br/>\n"
for each (var entry in toc) {
var html2 = indent(entry["level"]) + "<a href='#" + entry["anchor"] + "'>" +
entry["description"] + "</a><br/>\n";
html = html + html2;
}
html = html + "<br/></div>";
return html;
}
// Given an indentation level, return (2 * (level - 1)) non-breaking spaces.
function indent(level) {
var x = "";
for (var i = 0; i < level - 1; i++) {
x = x + " ";
}
return x;
}
Final Thoughts
The remaining issue is triggering this script. Google Apps Script does have an event notification mechanism (e.g. run this script when the spreadsheet opens) but right now, Google Sites only has a time-based trigger (e.g. run this script every n minutes/hours.) To be honest, if an onSave trigger was written, would createToc(url, url), which would save the TOC to itself, trigger another onSave event? No big deal, if it results in a no-change, I could tweak the code accordingly.
But the lack of an onSave trigger makes this just slightly usable for me. Having this run every two minutes is too frequent for when the site isn't updated. I don't know the plans for the Google Apps Script team and/or the Google Sites team, but this is a good use case for onSave.
I wonder how hard it would be to hide the default TOC renderers' section numbers with clever CSS, or just write my own Google Gadget...