blob: 235a3605717d5d8ca8ee1106781992f3b6f3d290 [file] [log] [blame]
/**
* @license
* Copyright 2020 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
// This file implements the playground implementation of the documentation
// page. The playground involves a "play" button that allows you to open up
// a new link to play.golang.org using the example code.
// The CSS is in content/static/css/stylesheet.css.
/**
* CSS classes used by PlaygroundExampleController
* @private @enum {string}
*/
const PlayExampleClassName = {
PLAY_HREF: '.js-exampleHref',
PLAY_CONTAINER: '.js-exampleContainer',
EXAMPLE_INPUT: '.Documentation-exampleCode',
EXAMPLE_OUTPUT: '.Documentation-exampleOutput',
EXAMPLE_ERROR: '.Documentation-exampleError',
PLAY_BUTTON: '.Documentation-examplePlayButton',
};
/**
* This controller enables playground examples to expand their dropdown or
* generate shareable Go Playground URLs.
*/
class PlaygroundExampleController {
/**
* @param {Element} exampleEl - The div that contains playground content for the given example.
*/
constructor(exampleEl) {
// Used to indicate when to terminate before the eventlistener is added.
let hasError = false;
if (!exampleEl) {
console.warn('Must provide playground example element');
hasError = true;
}
/**
* The example container
* @private {Element}
*/
this._exampleEl = /** @type {!Element} */ (exampleEl);
/**
* The anchor tag used to identify the container with an example href.
* There is only one in an example container div.
* @private {Element}
*/
const anchorEl = exampleEl.querySelector('a');
if (!anchorEl) {
console.warn('anchor tag is not detected');
hasError = true;
}
this._anchorEl = /** @type {!Element} */ (anchorEl);
/**
* The error element
* @private {Element}
*/
const errorEl = exampleEl.querySelector(PlayExampleClassName.EXAMPLE_ERROR);
if (!errorEl) {
hasError = true;
}
this._errorEl = /** @type {!Element} */ (errorEl);
/**
* Button that redirects to an example's playground, this element
* only exists in executable examples.
* @private {Element}
*/
const playButtonEl = exampleEl.querySelector(PlayExampleClassName.PLAY_BUTTON);
if (!playButtonEl) {
hasError = true;
}
this._playButtonEl = /** @type {!Element} */ (playButtonEl);
/**
* The executable code of an example.
* @private {Element}
*/
const inputEl = exampleEl.querySelector(PlayExampleClassName.EXAMPLE_INPUT);
if (!inputEl) {
console.warn('Input element is not detected');
hasError = true;
}
this._inputEl = /** @type {!Element} */ (inputEl);
/**
* The output of the given example code. This only exists if the
* author of the package provides an output for this example.
* @private {Element}
*/
this._outputEl = exampleEl.querySelector(PlayExampleClassName.EXAMPLE_OUTPUT);
if (hasError) {
return;
}
this._playButtonEl.addEventListener('click', e =>
this.handlePlayButtonClick(/** @type {!MouseEvent} */ (e))
);
}
/**
* Retrieve the hash value of the anchor element.
* @returns {string}
*/
getAnchorHash() {
return this._anchorEl.hash;
}
/**
* Expands the current playground example.
*/
expand() {
this._exampleEl.open = true;
}
/**
* Changes the text of the example's output box.
* @param {string} output
*/
setOutputText(output) {
if (this._outputEl) {
this._outputEl.textContent = output;
}
}
/**
* Sets the error message text and overwrites
* output box to indicate a failed response.
* @param {string} err
*/
setErrorText(err) {
this._errorEl.textContent = err;
this.setOutputText('An error has occurred…');
}
/**
* Opens a new window to play.golang.org using the
* example snippet's code in the playground.
* @param {!MouseEvent} e
* @private
*/
handlePlayButtonClick(e) {
const PLAYGROUND_BASE_URL = '//play.golang.org/p/';
this.setOutputText('Waiting for remote server…');
fetch('/play/', {
method: 'POST',
body: this._inputEl.textContent,
})
.then(res => res.text())
.then(shareId => {
window.open(PLAYGROUND_BASE_URL + shareId);
})
.catch(err => {
this.setErrorText(/** @type {!string} */ (err));
});
}
}
const exampleHashRegex = location.hash.match(/^#(example-.*)$/);
if (exampleHashRegex) {
const exampleHashEl = document.getElementById(exampleHashRegex[1]);
if (exampleHashEl) {
exampleHashEl.open = true;
}
}
// We use a spread operator to convert a nodelist into an array of elements.
const /** @type {Array<Element>} */ exampleHrefs = [
...document.querySelectorAll(PlayExampleClassName.PLAY_HREF),
];
/**
* Sometimes exampleHrefs and playContainers are in different order, so we
* find an exampleHref from a common hash.
* @param {PlaygroundExampleController} playContainer - playground container
*/
const findExampleHash = playContainer =>
exampleHrefs.find(ex => {
return ex.hash === playContainer.getAnchorHash();
});
document.querySelectorAll(PlayExampleClassName.PLAY_CONTAINER).forEach(el => {
// There should be the same amount of hrefs referencing examples as example containers.
const playContainer = new PlaygroundExampleController(el);
const exampleHref = findExampleHash(playContainer);
if (exampleHref) {
exampleHref.addEventListener('click', () => {
playContainer.expand();
});
} else {
console.warn('example href not found');
}
});