Skip to content

Commit

Permalink
Infrastructure: Add support for opening examples in CodePen (pull #1110)
Browse files Browse the repository at this point in the history
Resolves issue #1102 by adding support to dynamically add an "Open in Codepen" button adjacent to the heading above an example.
The examples/js/examples.js script uses the Codepen POST API to prefill its HTML/CSS/JS inputs.
The script expects a specific class on the heading of the example and requires that IDs for specific elements be passed to the scourceCode.add method call.

Co-authored-by: Matt King <a11yThinker@gmail.com>
Co-authored-by: Zoë Bijl <zoe.bijl@crowdstrike.com>
  • Loading branch information
3 people committed Sep 15, 2020
1 parent c57c012 commit 32ca9d3
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 4 deletions.
8 changes: 5 additions & 3 deletions examples/coding-template/Example-Template.html
Expand Up @@ -57,7 +57,9 @@ <h1>EXAMPLE_NAME Example</h1>
</ul>

<section>
<h2 id="ex_label">Example</h2>
<div class="example-header">
<h2 id="ex_label">Example</h2>
</div>
<div role="separator" id="ex_start_sep" aria-labelledby="ex_start_sep ex_label" aria-label="Start of"></div>
<!--
Note the ID of the following div that contains the example HTML is used as a parameter for the sourceCode.add() function.
Expand Down Expand Up @@ -189,7 +191,7 @@ <h2 id="rps_label">Role, Property, State, and Tabindex Attributes</h2>
<section>
<h2>Javascript and CSS Source Code</h2>
<!-- After the js and css files are named with the name of this example, change the href and text of the following 2 links to refer to the appropriate js and css files. -->
<ul>
<ul id="cssJsFiles">
<li>
CSS:
<a href="css/example_name.css" type="tex/css">example_name.css</a>
Expand All @@ -212,7 +214,7 @@ <h2 id="sc1_label">HTML Source Code</h2>
If you change the ID of either the 'ex1' div or the 'sc1' pre, be sure to update the sourceCode.add function parameters.
-->
<script>
sourceCode.add('sc1', 'ex1');
sourceCode.add('sc1', 'ex1', 'ex_label', 'cssJsFiles');
sourceCode.make();
</script>
</section>
Expand Down
69 changes: 69 additions & 0 deletions examples/css/core.css
Expand Up @@ -40,3 +40,72 @@ table.data.attributes tbody th,
table.data.attributes tbody td {
border: 1px solid silver;
}

/* CodePen button */
.example-header {
display: flex;
align-items: center;
margin-top: 3rem;
page-break-after: avoid;
page-break-inside: avoid;
font: 100% sans-serif;
font-family: inherit;
line-height: 1.2;
hyphens: manual;
}

.example-header > :first-child {
margin: 0;
}

.example-header > :first-child + * {
margin-left: 1em;
}

.example-header button {
display: inline-block;
position: relative;
padding: 0.4em 0.7em;
border: 1px solid hsl(213, 71%, 49%);
border-radius: 5px;
box-shadow: 0 1px 2px hsl(216, 27%, 55%);
color: #fff;
font-size: inherit;
text-shadow: 0 -1px 1px hsl(216, 27%, 25%);
background-color: hsl(216, 82%, 51%);
background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%));
}

.example-header button:hover {
border-color: hsl(213, 71%, 29%);
background-color: hsl(216, 82%, 31%);
background-image: linear-gradient(to bottom, hsl(216, 82%, 33%), hsl(216, 82%, 27%));
cursor: default;
}

.example-header button:focus {
outline: none;
}

.example-header button:focus::before {
position: absolute;
z-index: -1;

/* button border width - outline width - offset */
top: calc(-1px - 3px - 3px);
right: calc(-1px - 3px - 3px);
bottom: calc(-1px - 3px - 3px);
left: calc(-1px - 3px - 3px);
border: 3px solid hsl(213, 71%, 49%);

/* button border radius + outline width + offset */
border-radius: calc(5px + 3px + 3px);
content: '';
}

.example-header button:active {
border-color: hsl(213, 71%, 49%);
background-color: hsl(216, 82%, 31%);
background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%));
box-shadow: inset 0 3px 5px 1px hsl(216, 82%, 30%);
}
108 changes: 107 additions & 1 deletion examples/js/examples.js
Expand Up @@ -35,17 +35,26 @@ var VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
aria.widget.SourceCode = function () {
this.location = new Array();
this.code = new Array();
this.exampleHeader = new Array();
this.resources = new Array();
};

/**
* Adds source code
*
* @param {string} locationId - ID of `code` element that will display the example html
* @param {string} codeID - ID of element containing only and all of the html used to render the example widget
* @param {string} exampleHeaderId - ID of header element under which the "Open in Codepen" button belongs
* @param {string} cssJsFilesId - ID of element containing links to all the relevent js and css files used for the example widget
*
* @method add
* @memberof aria.widget.SourceCode
*/
aria.widget.SourceCode.prototype.add = function (locationId, codeId) {
aria.widget.SourceCode.prototype.add = function (locationId, codeId, exampleHeaderId, cssJsFilesId) {
this.location[this.location.length] = locationId;
this.code[this.code.length] = codeId;
this.exampleHeader[this.exampleHeader.length] = exampleHeaderId;
this.resources[this.resources.length] = cssJsFilesId;
};

/**
Expand All @@ -66,6 +75,11 @@ aria.widget.SourceCode.prototype.make = function () {
if (sourceCodeNode.innerHTML.startsWith('<br>')) {
sourceCodeNode.innerHTML = sourceCodeNode.innerHTML.replace('<br>', '');
}

// Adds the "Open In CodePen" button by the example header
if (this.exampleHeader[i]) {
addOpenInCodePenForm(i, this.exampleHeader[i], this.code[i], this.resources[i]);
}
}
};

Expand Down Expand Up @@ -300,4 +314,96 @@ function indentLines (input, indentation) {
return lines.join('\n');
}

/**
* Creates and adds an "Open in CodePen" button
*
* @param {String} exampleIndex - the example number, if there are multiple examples
* @param {String} exampleHeaderId - the example header to place the button next to
* @param {String} exampleCodeId - the example html code
* @param {String} exampleFilesId - the element containing all relevent CSS and JS file
*/
function addOpenInCodePenForm (exampleIndex, exampleHeaderId, exampleCodeId, exampleFilesId) {
var jsonInputId = 'codepen-data-ex-' + exampleIndex;
var buttonId = exampleCodeId + '-codepenbutton'

var form = document.createElement('form');
form.setAttribute('action', 'https://codepen.io/pen/define');
form.setAttribute('method', 'POST');
form.setAttribute('target', '_blank');

var input = document.createElement('input');
input.setAttribute('id', jsonInputId);
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'data');

var button = document.createElement('button');
button.innerText = 'Open In CodePen';

form.appendChild(input);
form.appendChild(button);

var exampleHeader = document.getElementById(exampleHeaderId);
exampleHeader.parentNode.insertBefore(form, exampleHeader.nextSibling);

// Correct the indentation for the example html
var indentedExampleHtml = document.getElementById(exampleCodeId).innerHTML;
indentedExampleHtml = indentedExampleHtml.replace(/^\n+/, '');
var indentation = indentedExampleHtml.match(/^\s+/)[0];
var exampleHtml = indentedExampleHtml.replace(new RegExp('^' + indentation, 'gm'), '');

var postJson = {
html: exampleHtml,
css: '',
js: '',
head: '<base href="' + location.href + '">'
};

var totalFetchedFiles = 0;
var fileLinks = document.querySelectorAll('#' + exampleFilesId + ' a');

for (let fileLink of fileLinks) {

var request = new XMLHttpRequest();

request.open('GET', fileLink.href, true);
request.onload = function() {
var href = this.responseURL;
if (this.status >= 200 && this.status < 400) {
if (href.indexOf('css') !== -1) {
postJson.css = postJson.css.concat(this.response);
}
if (href.indexOf('js') !== -1) {
postJson.js = postJson.js.concat(this.response);
}
totalFetchedFiles++;
}
else {
hideButton(buttonId, "Could not load resource: " + href);
}
};
request.onerror = function() {
hideButton(buttonId, "Could not load resource: " + fileLink.href);
};
request.send();
}

var timerId = setInterval(() => {
console.log(totalFetchedFiles);
if (totalFetchedFiles === fileLinks.length) {
document.getElementById(jsonInputId).value = JSON.stringify(postJson);
clearInterval(timerId);
}
}, 500);

setTimeout(() => {
clearInterval(timerId);
}, 10000);
}

function hideButton(buttonId, errorMsg) {
let button = document.querySelector(buttonId);
button.style.display = "none";
console.log("Removing 'Open in Codepen button'. " + errorMsg);
}

var sourceCode = new aria.widget.SourceCode();

0 comments on commit 32ca9d3

Please sign in to comment.