HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="./main.css">
<title>Backdraft Polygraph Example Application</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
@charset "UTF-8";
body {
font-family: Helvetica Neue, Arial, sans-serif;
}
polygon {
fill: #42b983;
opacity: .75;
}
circle {
fill: transparent;
stroke: #999;
}
text {
font-family: Helvetica Neue, Arial, sans-serif;
font-size: 10px;
fill: #666;
}
label {
display: inline-block;
margin-left: 10px;
width: 20px;
}
div.value {
display:inline-flex;
width:3em;
justify-content: center;
}
.raw {
position: absolute;
top: 0;
left: 300px;
}
import {Component, Collection, CollectionChild, render, svg, e, toWatchable} from "https://unpkg.com/bd-core@2.3.1/lib.js";
// This is a port of the Vue example of the same name.
// Other than the mathematical calculation of a point (which is a property of mathematics, not the example), it shares
// no common code.
// The application consists of
// * a polygraph--an svg polygon drawn inside a circle with labeled points: implemented as the PolyGraph component
// * a collection of controllers for each point: implemented as a Collection of StatController components
// * a little form to add a stat: part of the Page component
// * some text that reflects the JSON value of stats: part of the Page component
//
// In addition to playing with the controllers, you can play with the underlying data in the console via the variable
// window.polygraphData. For example, try typing "window.polygraphData.reverse()" into the console to reverse the order
// of the points.
//
// To grok the program look at Polygraph, then AxisLabel, then StatController, and finally Page.
// The raw data to observe
// window.polygraphData allows the data to be mutated in the debug console
let stats = window.polygraphData = toWatchable([
{label: "A", value: 100},
{label: "B", value: 100},
{label: "C", value: 100},
{label: "D", value: 100},
{label: "E", value: 100},
{label: "F", value: 100}
]);
function valueToPoint(value, index, total){
let x = 0;
let y = -value * 0.8;
let angle = Math.PI * 2 / total * index;
let cos = Math.cos(angle);
let sin = Math.sin(angle);
return {x: Math.round(x * cos - y * sin + 100), y: Math.round(x * sin + y * cos + 100)};
}
class AxisLabel extends CollectionChild.withWatchables("item:label, value", "point") {
// AxisLabel is used by Polygraph to output the labels in the polygraph. Polygraph is given an watchable array of
// "stats" of {label: string, value: number}; see Polygraph. Each item in the array causes one AxisLabel instance to
// be created that's associated with one stat item.
//
// AxisLabel uses mathematics (see valueToPoint, above) to create an (x, y) point given a value, and then renders a
// a svg text node with the x and y attributes reflecting the point and innerHTML reflecting the label.
//
// Backdraft reflection is used to automatically update the point any time any of the inputs into valueToPoint mutate,
// and then update the svg text node's x any y attributes any time the point changes. Similarly, the svg's text node
// is automatically updated any time the label mutates. The superclass, CollectionChild.withWatchables, causes the
// watchable properties label, value, and point to be declared on each AxisLabel instance. Further, label and value
// are actually virtual properties, simply reflecting the label and value properties of the particular stat to which
// a particular AxisLabel instance is associated.
constructor(kwargs){
super(kwargs);
// initialize the point property, given the value property
// withWatchables("item:label, value") ensures that this.label and this.value reflect the stat item's value and label properties
this.onMutateValue(this.value);
}
bdElements(){
// reflect the value of this.label into a svg text node
return svg("text", {
bdReflect: {
// update the svg text node's x attribute any time this.point.x changes
x: ["point", p => p.x],
// update the svg text node's y attribute any time this.point.y changes
y: ["point", p => p.y],
// update the svg text node's innerHTML any time this.label changes
innerHTML: "label"
}
});
}
onMutateValue(value){
// anytime item.value mutates, we must update the point
this.point = valueToPoint((value || 0) + 10, this.collectionIndex, this.collectionLength);
}
onMutateCollectionLength(){
// anytime the length of the collection changes, we must update the point
this.onMutateValue(this.value);
}
onMutateCollectionIndex(){
// anytime the array index of the item associated with this instance changes, we must update the point.
// This can happen when points are re-ordered...because Backdraft *does not* destroy/create DOM nodes consequent
// to reordering. You can test this by opening the console after loading the app, and executing
// "window.polygraphData.reverse()", which will reverse the data in the array. Backdraft does all of this
// *without* a virtual dom, *without* adding keys to the array items...indeed...without "re-rendering"
// but rather updating the already rendered document fragment!
this.onMutateValue(this.value);
}
}
class Polygraph extends Component {
// Polygraph is an svg drawing of a polygon that consists of the (x, y) points computed by pointsToPolyPoints given
// an array of "stats" of {label: string, value: number}. The array is provided as a keyword argument at construction
// in the property "stats" and is a watchable array. Polygraph takes advantage of this feature by updating the
// svg polygon points attribute any time any value in stats changes. In particular,
//
// bdReflect_points: [this.kwargs.stats, pointsToPolyPoints]
//
// instructs Backdraft to reflect the value of pointsToPolyPoints(this.kwargs.stats) into the points attribute
// of the polygon node any time this.kwargs.stats mutates.
//
// Polygraph also draws a circle and includes a Collection of AxisLabels, one AxisLabel for each item in stats. The
// Backdraft class Collection is used to manage a set of homogeneous components that reflect a collection (actualized
// as an array) of homogeneous data--a very common pattern. Collection ensures that a child item is created/mutated/deleted
// to reflect the underlying data...all using algorithms as efficient as hand-tuned code.
bdElements(){
function pointsToPolyPoints(stats){
let length = stats.length;
return stats.map((s, i) => valueToPoint(s.value, i, length)).map(p => p.x + "," + p.y).join(" ");
}
return svg("svg", {width: 200, height: 200},
svg("g",
svg("polygon", {bdReflect_points: [this.kwargs.stats, pointsToPolyPoints]}),
svg("circle", {cx: 100, cy: 100, r: 80}),
e(Collection, {elements: svg("g"), childType: AxisLabel, collection: this.kwargs.stats})
)
);
}
}
class StatController extends CollectionChild.withWatchables("item:label, value", "point") {
// This is the component that has the slider label, value, and delete button;
// it is implemented using the same ideas as AxisLabel.
bdElements(){
return e("div",
e("label", {
// when the label property mutates in the stat item associated with this instance,
// reflect the new value into this node.
bdReflect: "label"
}),
e("input", {
type: "range", min: 0, max: 100, value: this.collectionItem.value,
// when the input node signals an event of type "input",
// set the value property of the stat item associated with this instance
bdOn_input: e => (this.collectionItem.value = Number(e.target.value)),
// when the value property mutates in the stat item associated with this instance,
// reflect the new value into the value attribute of this node.
bdReflect_value: "value"
}),
e("div", {
className: "value",
// when the value property mutates in the stat item associated with this instance,
// reflect the new value into this node.
bdReflect: "value"
}),
e("button", {
className: "remove",
bdOn_click: () => {
// when the button node signals an event of type "click", try to delete this item from the underlying data
// notice that, given our design that *reflects* the data, we simply manipulate the underlying data
// and the user interface automatically synchronizes...all without virtual dom!
if(this.parent.collection.length > 3){
this.parent.collection.splice(this.collectionIndex, 1);
}else{
alert("Can't delete more!");
}
}
}, "X")
);
}
}
class Page extends Component {
// Here's the whole application...it consists of a Polygraph instance, a Collection of StatController instances, a little
// form to add a stat, and some text that reflects the JSON value of stats.
//
// When a new Page instance is instantiated, it is passed the stats array in the keyword constructor argument "stats".
bdElements(){
return e("div",
e(Polygraph, {bdAttach: "graph", stats: this.kwargs.stats}),
e(Collection, {childType: StatController, collection: this.kwargs.stats}),
e("form",
e("input", {bdAttach: "input"}),
e("button", {bdAdvise: {"click": this.add.bind(this)}}, "Add a Stat")
),
e("pre", {
className: "raw",
bdReflect: [this.kwargs.stats, () => JSON.stringify(stats, null, "\t")]
})
);
}
add(e){
e.preventDefault();
let label = this.input.value;
if(!label) return;
this.kwargs.stats.push({label: label, value: 100});
this.input.value = "";
}
}
render(Page, {stats: stats}, "root");
Also see: Tab Triggers