1 // Copyright 2012 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 /* A little code to ease navigation of these documents.
8 * + Generate a table of contents (generateTOC)
9 * + Bind foldable sections (bindToggles)
10 * + Bind links to foldable sections (bindToggleLinks)
16 // Mobile-friendly topbar menu
18 var menu = $('#menu');
19 var menuButton = $('#menu-button');
20 var menuButtonArrow = $('#menu-button-arrow');
21 menuButton.click(function(event) {
22 menu.toggleClass('menu-visible');
23 menuButtonArrow.toggleClass('vertical-flip');
24 event.preventDefault();
29 /* Generates a table of contents: looks for h2 and h3 elements and generates
30 * links. "Decorates" the element with id=="nav" with this table of contents.
32 function generateTOC() {
33 if ($('#manual-nav').length > 0) {
37 // For search, we send the toc precomputed from server-side.
38 // TODO: Ideally, this should always be precomputed for all pages, but then
39 // we need to do HTML parsing on the server-side.
40 if (location.pathname === '/search') {
45 if (nav.length === 0) {
54 if (node.id == '') node.id = 'tmp_' + toc_items.length;
56 .attr('href', '#' + node.id)
57 .text($(node).text());
59 if ($(node).is('h2')) {
63 item = $('<dd class="indent"/>');
68 if (toc_items.length <= 1) {
74 var split_index = toc_items.length / 2 + 1;
75 if (split_index < 8) {
76 split_index = toc_items.length;
78 for (var i = 0; i < split_index; i++) {
79 dl1.append(toc_items[i]);
81 for (; /* keep using i */ i < toc_items.length; i++) {
82 dl2.append(toc_items[i]);
85 var tocTable = $('<table class="unruled"/>').appendTo(nav);
86 var tocBody = $('<tbody/>').appendTo(tocTable);
87 var tocRow = $('<tr/>').appendTo(tocBody);
90 $('<td class="first"/>')
99 function bindToggle(el) {
100 $('.toggleButton', el).click(function() {
101 if ($(this).closest('.toggle, .toggleVisible')[0] != el) {
102 // Only trigger the closest toggle header.
106 if ($(el).is('.toggle')) {
108 .addClass('toggleVisible')
109 .removeClass('toggle');
113 .removeClass('toggleVisible');
118 function bindToggles(selector) {
119 $(selector).each(function(i, el) {
124 function bindToggleLink(el, prefix) {
125 $(el).click(function() {
126 var href = $(el).attr('href');
127 var i = href.indexOf('#' + prefix);
131 var id = '#' + prefix + href.slice(i + 1 + prefix.length);
132 if ($(id).is('.toggle')) {
134 .find('.toggleButton')
140 function bindToggleLinks(selector, prefix) {
141 $(selector).each(function(i, el) {
142 bindToggleLink(el, prefix);
146 function setupDropdownPlayground() {
147 if (!$('#page').is('.wide')) {
148 return; // don't show on front page
150 var button = $('#playgroundButton');
151 var div = $('#playground');
155 button.addClass('active');
162 codeEl: $('.code', div),
163 outputEl: $('.output', div),
164 runEl: $('.run', div),
165 fmtEl: $('.fmt', div),
166 shareEl: $('.share', div),
167 shareRedirect: '//play.golang.org/p/',
171 button.removeClass('active');
175 $('#menu').css('min-width', '+=60');
177 // Hide inline playground if we click somewhere on the page.
178 // This is needed in mobile devices, where the "Play" button
179 // is not clickable once the playground opens up.
180 $('#page').click(function() {
181 if (button.hasClass('active')) {
187 function setupInlinePlayground() {
189 // Set up playground when each element is toggled.
190 $('div.play').each(function(i, el) {
191 // Set up playground for this example.
192 var setup = function() {
193 var code = $('.code', el);
196 outputEl: $('.output', el),
197 runEl: $('.run', el),
198 fmtEl: $('.fmt', el),
199 shareEl: $('.share', el),
200 shareRedirect: '//play.golang.org/p/',
203 // Make the code textarea resize to fit content.
204 var resize = function() {
206 var h = code[0].scrollHeight;
207 code.height(h + 20); // minimize bouncing.
208 code.closest('.input').height(h);
210 code.on('keydown', resize);
211 code.on('keyup', resize);
212 code.keyup(); // resize now.
215 // If example already visible, set up playground now.
216 if ($(el).is(':visible')) {
221 // Otherwise, set up playground when example is expanded.
235 // fixFocus tries to put focus to div#page so that keyboard navigation works.
236 function fixFocus() {
237 var page = $('div#page');
238 var topbar = $('div#topbar');
239 page.css('outline', 0); // disable outline when focused
240 page.attr('tabindex', -1); // and set tabindex so that it is focusable
242 .resize(function(evt) {
243 // only focus page when the topbar is at fixed position (that is, it's in
244 // front of page, and keyboard event will go to the former by default.)
245 // by focusing page, keyboard event will go to page so that up/down arrow,
246 // space, etc. will work as expected.
247 if (topbar.css('position') == 'fixed') page.focus();
252 function toggleHash() {
253 var id = window.location.hash.substring(1);
254 // Open all of the toggles for a particular hash.
256 document.getElementById(id),
257 $('a[name]').filter(function() {
258 return $(this).attr('name') == id;
263 for (var i = 0; i < els.length; i++) {
265 if (el.is('.toggle')) {
266 el.find('.toggleButton')
275 function personalizeInstallInstructions() {
276 var prefix = '?download=';
277 var s = window.location.search;
278 if (s.indexOf(prefix) != 0) {
279 // No 'download' query string; detect "test" instructions from User Agent.
280 if (navigator.platform.indexOf('Win') != -1) {
281 $('.testUnix').hide();
282 $('.testWindows').show();
284 $('.testUnix').show();
285 $('.testWindows').hide();
290 var filename = s.substr(prefix.length);
291 var filenameRE = /^go1\.\d+(\.\d+)?([a-z0-9]+)?\.([a-z0-9]+)(-[a-z0-9]+)?(-osx10\.[68])?\.([a-z.]+)$/;
292 var m = filenameRE.exec(filename);
294 // Can't interpret file name; bail.
297 $('.downloadFilename').text(filename);
298 $('.hideFromDownload').hide();
302 if (ext != 'tar.gz') {
303 $('#tarballInstructions').hide();
305 if (os != 'darwin' || ext != 'pkg') {
306 $('#darwinPackageInstructions').hide();
308 if (os != 'windows') {
309 $('#windowsInstructions').hide();
310 $('.testUnix').show();
311 $('.testWindows').hide();
314 $('#windowsInstallerInstructions').hide();
317 $('#windowsZipInstructions').hide();
319 $('.testUnix').hide();
320 $('.testWindows').show();
323 var download = 'https://dl.google.com/go/' + filename;
326 '<p class="downloading">' +
327 'Your download should begin shortly. ' +
328 'If it does not, click <a>this link</a>.</p>'
330 message.find('a').attr('href', download);
331 message.insertAfter('#nav');
333 window.location = download;
336 function updateVersionTags() {
337 var v = window.goVersion;
338 if (/^go[0-9.]+$/.test(v)) {
342 $('.whereTag').hide();
346 function addPermalinks() {
347 function addPermalink(source, parent) {
348 var id = source.attr('id');
349 if (id == '' || id.indexOf('tmp_') === 0) {
350 // Auto-generated permalink.
353 if (parent.find('> .permalink').length) {
359 .append($("<a class='permalink'>¶</a>").attr('href', '#' + id));
362 $('#page .container')
363 .find('h2[id], h3[id]')
366 addPermalink(el, el);
369 $('#page .container')
373 // Add the anchor to the "dt" element.
374 addPermalink(el, el.find('> dt').first());
378 $('.js-expandAll').click(function() {
379 if ($(this).hasClass('collapsed')) {
380 toggleExamples('toggle');
381 $(this).text('(Collapse All)');
383 toggleExamples('toggleVisible');
384 $(this).text('(Expand All)');
386 $(this).toggleClass('collapsed');
389 function toggleExamples(className) {
390 // We need to explicitly iterate through divs starting with "example_"
391 // to avoid toggling Overview and Index collapsibles.
392 $("[id^='example_']").each(function() {
393 // Check for state and click it only if required.
394 if ($(this).hasClass(className)) {
396 .find('.toggleButton')
403 $(document).ready(function() {
406 bindToggles('.toggle');
407 bindToggles('.toggleVisible');
408 bindToggleLinks('.exampleLink', 'example_');
409 bindToggleLinks('.overviewLink', '');
410 bindToggleLinks('.examplesLink', '');
411 bindToggleLinks('.indexLink', '');
412 setupDropdownPlayground();
413 setupInlinePlayground();
418 personalizeInstallInstructions();
421 // godoc.html defines window.initFuncs in the <head> tag, and root.html and
422 // codewalk.js push their on-page-ready functions to the list.
423 // We execute those functions here, to avoid loading jQuery until the page
424 // content is loaded.
425 for (var i = 0; i < window.initFuncs.length; i++) window.initFuncs[i]();
428 // -- analysis ---------------------------------------------------------
430 // escapeHTML returns HTML for s, with metacharacters quoted.
431 // It is safe for use in both elements and attributes
432 // (unlike the "set innerText, read innerHTML" trick).
433 function escapeHTML(s) {
435 .replace(/&/g, '&')
436 .replace(/\"/g, '"')
437 .replace(/\'/g, ''')
438 .replace(/</g, '<')
439 .replace(/>/g, '>');
442 // makeAnchor returns HTML for an <a> element, given an anchorJSON object.
443 function makeAnchor(json) {
444 var html = escapeHTML(json.Text);
445 if (json.Href != '') {
446 html = "<a href='" + escapeHTML(json.Href) + "'>" + html + '</a>';
451 function showLowFrame(html) {
452 var lowframe = document.getElementById('lowframe');
453 lowframe.style.height = '200px';
455 "<p style='text-align: left;'>" +
458 "<div onclick='hideLowFrame()' style='position: absolute; top: 0; right: 0; cursor: pointer;'>✘</div>";
461 document.hideLowFrame = function() {
462 var lowframe = document.getElementById('lowframe');
463 lowframe.style.height = '0px';
466 // onClickCallers is the onclick action for the 'func' tokens of a
467 // function declaration.
468 document.onClickCallers = function(index) {
469 var data = document.ANALYSIS_DATA[index];
470 if (data.Callers.length == 1 && data.Callers[0].Sites.length == 1) {
471 document.location = data.Callers[0].Sites[0].Href; // jump to sole caller
476 'Callers of <code>' + escapeHTML(data.Callee) + '</code>:<br/>\n';
477 for (var i = 0; i < data.Callers.length; i++) {
478 var caller = data.Callers[i];
479 html += '<code>' + escapeHTML(caller.Func) + '</code>';
480 var sites = caller.Sites;
481 if (sites != null && sites.length > 0) {
483 for (var j = 0; j < sites.length; j++) {
487 html += '<code>' + makeAnchor(sites[j]) + '</code>';
495 // onClickCallees is the onclick action for the '(' token of a function call.
496 document.onClickCallees = function(index) {
497 var data = document.ANALYSIS_DATA[index];
498 if (data.Callees.length == 1) {
499 document.location = data.Callees[0].Href; // jump to sole callee
503 var html = 'Callees of this ' + escapeHTML(data.Descr) + ':<br/>\n';
504 for (var i = 0; i < data.Callees.length; i++) {
505 html += '<code>' + makeAnchor(data.Callees[i]) + '</code><br/>\n';
510 // onClickTypeInfo is the onclick action for identifiers declaring a named type.
511 document.onClickTypeInfo = function(index) {
512 var data = document.ANALYSIS_DATA[index];
517 ' <small>(size=' +
522 html += implementsHTML(data);
523 html += methodsetHTML(data);
527 // implementsHTML returns HTML for the implements relation of the
528 // specified TypeInfoJSON value.
529 function implementsHTML(info) {
531 if (info.ImplGroups != null) {
532 for (var i = 0; i < info.ImplGroups.length; i++) {
533 var group = info.ImplGroups[i];
534 var x = '<code>' + escapeHTML(group.Descr) + '</code> ';
535 for (var j = 0; j < group.Facts.length; j++) {
536 var fact = group.Facts[j];
537 var y = '<code>' + makeAnchor(fact.Other) + '</code>';
538 if (fact.ByKind != null) {
539 html += escapeHTML(fact.ByKind) + ' type ' + y + ' implements ' + x;
541 html += x + ' implements ' + y;
550 // methodsetHTML returns HTML for the methodset of the specified
551 // TypeInfoJSON value.
552 function methodsetHTML(info) {
554 if (info.Methods != null) {
555 for (var i = 0; i < info.Methods.length; i++) {
556 html += '<code>' + makeAnchor(info.Methods[i]) + '</code><br/>\n';
562 // onClickComm is the onclick action for channel "make" and "<-"
563 // send/receive tokens.
564 document.onClickComm = function(index) {
565 var ops = document.ANALYSIS_DATA[index].Ops;
566 if (ops.length == 1) {
567 document.location = ops[0].Op.Href; // jump to sole element
571 var html = 'Operations on this channel:<br/>\n';
572 for (var i = 0; i < ops.length; i++) {
574 makeAnchor(ops[i].Op) +
576 escapeHTML(ops[i].Fn) +
579 if (ops.length == 0) {
580 html += '(none)<br/>\n';
585 $(window).load(function() {
586 // Scroll window so that first selection is visible.
587 // (This means we don't need to emit id='L%d' spans for each line.)
588 // TODO(adonovan): ideally, scroll it so that it's under the pointer,
589 // but I don't know how to get the pointer y coordinate.
590 var elts = document.getElementsByClassName('selection');
591 if (elts.length > 0) {
592 elts[0].scrollIntoView();
596 // setupTypeInfo populates the "Implements" and "Method set" toggle for
597 // each type in the package doc.
598 function setupTypeInfo() {
599 for (var i in document.ANALYSIS_DATA) {
600 var data = document.ANALYSIS_DATA[i];
602 var el = document.getElementById('implements-' + i);
604 // el != null => data is TypeInfoJSON.
605 if (data.ImplGroups != null) {
606 el.innerHTML = implementsHTML(data);
607 el.parentNode.parentNode.style.display = 'block';
611 var el = document.getElementById('methodset-' + i);
613 // el != null => data is TypeInfoJSON.
614 if (data.Methods != null) {
615 el.innerHTML = methodsetHTML(data);
616 el.parentNode.parentNode.style.display = 'block';
622 function setupCallgraphs() {
623 if (document.CALLGRAPH == null) {
626 document.getElementById('pkg-callgraph').style.display = 'block';
628 var treeviews = document.getElementsByClassName('treeview');
629 for (var i = 0; i < treeviews.length; i++) {
630 var tree = treeviews[i];
631 if (tree.id == null || tree.id.indexOf('callgraph-') != 0) {
634 var id = tree.id.substring('callgraph-'.length);
635 $(tree).treeview({ collapsed: true, animated: 'fast' });
636 document.cgAddChildren(tree, tree, [id]);
637 tree.parentNode.parentNode.style.display = 'block';
641 document.cgAddChildren = function(tree, ul, indices) {
642 if (indices != null) {
643 for (var i = 0; i < indices.length; i++) {
644 var li = cgAddChild(tree, ul, document.CALLGRAPH[indices[i]]);
645 if (i == indices.length - 1) {
646 $(li).addClass('last');
650 $(tree).treeview({ animated: 'fast', add: ul });
653 // cgAddChild adds an <li> element for document.CALLGRAPH node cgn to
654 // the parent <ul> element ul. tree is the tree's root <ul> element.
655 function cgAddChild(tree, ul, cgn) {
656 var li = document.createElement('li');
658 li.className = 'closed';
660 var code = document.createElement('code');
662 if (cgn.Callees != null) {
663 $(li).addClass('expandable');
665 // Event handlers and innerHTML updates don't play nicely together,
666 // hence all this explicit DOM manipulation.
667 var hitarea = document.createElement('div');
668 hitarea.className = 'hitarea expandable-hitarea';
669 li.appendChild(hitarea);
671 li.appendChild(code);
673 var childUL = document.createElement('ul');
674 li.appendChild(childUL);
675 childUL.setAttribute('style', 'display: none;');
677 var onClick = function() {
678 document.cgAddChildren(tree, childUL, cgn.Callees);
679 hitarea.removeEventListener('click', onClick);
681 hitarea.addEventListener('click', onClick);
683 li.appendChild(code);
685 code.innerHTML += ' ' + makeAnchor(cgn.Func);