JavaScript preprocessors can help make authoring JavaScript easier and more convenient. For instance, CoffeeScript can help prevent easy-to-make mistakes and offer a cleaner syntax and Babel can bring ECMAScript 6 features to browsers that only support ECMAScript 5.
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.
HTML Settings
Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus/>
</header>
<section class="main">
<input id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list"></ul>
<footer class="footer">
<span class="todo-count"></span>
<ul class="filters"></ul>
<span id="clearCompleted"></span>
</footer>
</section>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Written by <a href="http://twitter.com/walter">Walter Higgins</a></p>
</footer>
</body>
<script src="https://rawgit.com/walterhiggins/ickyjs/master/icky.js"
type="text/javascript"></script>
<script src="app.js"
type="text/javascript"></script>
</htmL>
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
:focus {
outline: 0;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
.toggle-all {
width: 1px;
height: 1px;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
right: 100%;
bottom: 100%;
}
.toggle-all + label {
width: 60px;
height: 34px;
font-size: 0;
position: absolute;
top: -52px;
left: -13px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all + label:before {
content: '❯';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked + label:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle + label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
}
.todo-list li .toggle:checked + label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
//
// This example application is based on the popular ToDoMVC application.
// It is functionally identically (to the best of my knowledge) to the
// ES6 Vanilla version and uses the same CSS stylesheet and initial markup.
//
(function(exports) {
"use strict";
// short-hand for icky functions
let { gnf, update, map } = icky;
// short-hand for querySelector - default is document.querySelector
const qs = (selector, el) => (el ? el.querySelector(selector) : document.querySelector(selector));
// short-hand for subscribing to topics
const on = (...args) => {
const callback = args.pop();
args.forEach(topic => window.addEventListener(topic, callback));
};
const publish = (topic, data, el) => {
let emitter = el ? el : document;
let event = new CustomEvent(topic, { bubbles: true, detail: data });
setTimeout(() => emitter.dispatchEvent(event), 0);
};
// constants used throughout the rest of the code
const KEY = {
ENTER: 13,
ESCAPE: 27
};
// topics used by Model and Controllers
const TOPIC = {
ITEMS_LOADED: "todos/itemsLoaded",
ITEM_CHANGED: "todos/itemChanged",
ITEM_STATUS_CHANGED: "todos/itemStatusChanged",
ITEM_REMOVED: "todos/itemRemoved",
ITEM_ADDED: "todos/itemAdded",
VISIBILITY_CHANGED: "todos/visibilityChanged",
BULK_STATUS_CHANGE: "todos/bulkStatusChange"
};
const VISIBILITY = {
ALL: "All",
ACTIVE: "Active",
COMPLETED: "Completed"
};
// ## Model
// The Model object is responsible for encapsulating changes to the to-do list.
// Stored in localStorage's "todos" variable by default.
function Model(name = "todos") {
// visibility filter function
const visible = item => {
switch (visibility) {
case VISIBILITY.ALL:
return true;
case VISIBILITY.COMPLETED:
return item.done;
case VISIBILITY.ACTIVE:
return !item.done;
}
};
// load app state from localStorage or init if not present.
let todos = [],
visibility = VISIBILITY.ALL,
saved = JSON.parse(localStorage.getItem(name));
if (saved) {
todos = saved.todos;
visibility = saved.visibility;
}
// notify listeners that model is ready
setTimeout(() => publish(TOPIC.ITEMS_LOADED), 0);
// model is saved to localStorage - save() is private to the model.
const save = () => {
localStorage.setItem(name, JSON.stringify({ todos, visibility }));
};
// Model's public functions
return {
// toggle the status (done) of a to-do item
// works for both single items and arrays of items.
toggle: todo => {
if (todo.constructor == Array) {
// if it's an array then send a single notification to listeners
todo.map(item => (item.done = !item.done));
save();
publish(TOPIC.BULK_STATUS_CHANGE, todo);
} else {
const old = { ...todo };
todo.done = !todo.done;
save();
publish(TOPIC.ITEM_STATUS_CHANGED, { o: old, n: todo });
}
},
// change the text for a to-do item
setText: (todo, text) => {
const old = { ...todo };
todo.text = text;
save();
publish(TOPIC.ITEM_CHANGED, { o: old, n: todo });
},
// add a new to-do item
add: todo => {
let result = todos.push(todo);
save();
publish(TOPIC.ITEM_ADDED, result);
},
// remove a to-do item
remove: todo => {
todos.splice(todos.indexOf(todo), 1);
save();
publish(TOPIC.ITEM_REMOVED, todo);
},
// change the application's visibility option (All, Active or Completed)
visibility: v => {
if (v) {
visibility = v;
save();
publish(TOPIC.VISIBILITY_CHANGED);
} else {
return visibility;
}
},
// get a list of visible items
visible: () => todos.filter(visible),
// get a list of remaining items
remaining: () => todos.filter(item => !item.done),
// get a list of completed items
completed: () => todos.filter(item => item.done),
// get a list of all items
all: () => [...todos]
};
}
// declare model variable used throughout rest of code.
let model = null;
// ## Components
// The to-do list application is composed of distinct components which need
// to be updated when the user interacts with the app. These components are:
//
// 1. The input field for adding new to-do items
// 2. The list of to-do items each of which have
// 2.1 A Checkbox to mark the item as completed (this can be toggled on and off)
// 2.2 A Button to remove the item (appears on hover)
// 2.3 A Label with the text which when double-clicked becomes an editable input field.
// 3. A Checkbox to mark ALL items as completed or Active.
// 4. A Label showing a count of completed items.
// 5. A List of hyperlinks in the footer which will show either All, Active or Completed items.
// 6. A Button to Remove all completed items (which only appears if there are >1 completed items)
//
// Many components are constructed by invoking a function which returns a string of HTML.
// Constructing strings of HTML is now easier in ES6 thanks to Template Literals.
//
// ### Component: Todo List
const tTodoList = () => {
// The todo list can potentially create a lot of DOM elements each with many event handlers.
// When using the gnf() function to allocate global names to the event-handler functions, the
// functions are referenced from the global window.icky.namespaces.functions object.
// To avoid memory leaks construct a new dedicated namespace and naming function which will
// be torn-down and reconstructed whenever this function is called.
let namer = gnf("tTodoList");
// tTodoList doesn't have any markup itself, it just repeatedly calls tTodoItem.
return map(model.visible(), item => tTodoItem(namer, item));
};
// when any of these messages are received, update the to-do list
on(
TOPIC.ITEMS_LOADED,
TOPIC.BULK_STATUS_CHANGE,
TOPIC.ITEM_STATUS_CHANGED,
TOPIC.ITEM_REMOVED,
TOPIC.ITEM_ADDED,
TOPIC.VISIBILITY_CHANGED,
() => update("ul.todo-list", tTodoList)
);
// trigger a blur event if the user presses Enter
const onKeyPressItemEdit = gnf(input => {
if (event.keyCode == KEY.ENTER) input.blur();
});
// trigger a blur event if the user presses Esc
const onKeyUpItemEdit = gnf(input => {
if (event.keyCode == KEY.ESCAPE) {
input.dataset.isCanceled = true;
input.blur();
}
});
// ### Component: Todo Item.
// The App's most interactive component. Using this component users can:
// 1. Toggle the item's status (Completed/Active).
// 2. Change the item's text
// 3. Remove the item (by changing the text to an empty string)
// 4. Remove the item by clicking the Remove button.
function tTodoItem(nf, todo) {
// #### Controller code for tTodoItem
let label, input, listItem;
// update model if user didn't press Esc
const onBlurItemEdit = nf(input => {
listItem.classList.remove("editing");
if (input.dataset.isCanceled) return;
var value = input.value.trim();
if (value.length) {
// change to-do item's text if length > 0
model.setText(todo, value);
label.innerText = value;
} else {
// otherwise remove the item (text is '')
model.remove(todo);
}
});
// go into editing mode when user double-clicks label
const edit = nf(pLabel => {
label = pLabel;
listItem = label.parentElement.parentElement;
// CSS is used to hide the label and show the input
listItem.classList.add("editing");
input = qs("input.edit", listItem);
input.focus();
});
// #### View for tTodoItem.
// Note the use of ES6 Template Literals.
return `
<li class="${todo.done ? "completed" : ""}">
<div class="view">
<input class="toggle" type="checkbox" ${todo.done ? "checked" : ""}
onchange="${nf(() => model.toggle(todo))}()" />
<label ondblclick="${edit}(this)">${todo.text}</label>
<button class="destroy"
onclick="${nf(() => model.remove(todo))}()"></button>
</div>
<input class="edit"
onblur="${onBlurItemEdit}(this)"
onkeypress="${onKeyPressItemEdit}(this)"
onkeyup="${onKeyUpItemEdit}(this)"
value="${todo.text}"/>
</li>`;
}
// ### Component: Items remaining
const tItemsLeft = () => `${model.remaining().length} Items Left`;
on(
TOPIC.ITEMS_LOADED,
TOPIC.BULK_STATUS_CHANGE,
TOPIC.ITEM_STATUS_CHANGED,
TOPIC.ITEM_REMOVED,
TOPIC.ITEM_ADDED,
() => update(".todo-count", tItemsLeft)
);
// ### Component: Clear Completed button
const onClickClearCompleted = gnf(() => {
model.completed().forEach(model.remove);
});
const tClearCompleted = () => `
<button class="clear-completed ${model.completed().length ? "" : "hidden"}"
onclick="${onClickClearCompleted}()">
Clear completed
</button>`;
on(
TOPIC.ITEMS_LOADED,
TOPIC.BULK_STATUS_CHANGE,
TOPIC.ITEM_STATUS_CHANGED,
TOPIC.ITEM_REMOVED,
() => update("#clearCompleted", tClearCompleted)
);
// ### Component: Filter links.
const tFilterList = () => `
${tFilterItem("#/", VISIBILITY.ALL)}
${tFilterItem("#/active", VISIBILITY.ACTIVE)}
${tFilterItem("#/completed", VISIBILITY.COMPLETED)}
`;
// ### Component: Filter link
const tFilterItem = (href, type) => `
<li>
<a href="${href}"
class="${model.visibility() == type ? "selected" : ""}">${type}</a>
</li>`;
on(TOPIC.ITEMS_LOADED, TOPIC.VISIBILITY_CHANGED, () => {
update("ul.filters", tFilterList);
});
// ### Component: Toggle-All checkbox
const toggleAll = qs("input.toggle-all");
toggleAll.onchange = function() {
if (this.checked) {
model.toggle(model.remaining());
} else {
model.toggle(model.completed());
}
};
on(TOPIC.ITEMS_LOADED, TOPIC.ITEM_STATUS_CHANGED, TOPIC.ITEM_REMOVED, TOPIC.ITEM_ADDED, () => {
toggleAll.checked = model.all().length > 0 && model.remaining().length == 0;
});
// ### Component: New To-Do input field
qs("input.new-todo").onchange = function() {
let text = this.value;
if (text.trim().length == 0) {
return;
}
model.add({ text: text, done: false });
this.value = "";
};
// ## Routing
// set up client-side routes for visibility filtering
const routes = {
active: () => model.visibility("Active"),
completed: () => model.visibility("Completed")
};
// route based on hash
const routeByHash = () => {
let param = location.hash.split("/")[1];
let action = routes[param] || (() => model.visibility("All"));
action();
};
exports.onhashchange = routeByHash;
// ## Initialise the App
// create a new Model object
model = new Model();
// update visibility based on location hash
routeByHash();
})(window);
Also see: Tab Triggers