Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

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.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

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.

+ add another resource

Packages

Add Packages

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.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <h1>Todo</h1>

<form id="todo-form">
	<label for="todo-item">What do you need to do?</label>
	<input type="text" name="todo-item" id="todo-item">
	<button class="btn" id="add-todo">Add Todo</button>
</form>

<div id="app"></div>
              
            
!

CSS

              
                /**
 * Add box sizing to everything
 * @link http://www.paulirish.com/2012/box-sizing-border-box-ftw/
 */
*,
*:before,
*:after {
	box-sizing: border-box;
}

/**
 * Layout
 */
body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
	font-size: 112.5%;
	margin-left: auto;
	margin-right: auto;
	max-width: 40em;
	width: 88%;
}

/**
 * Forms
 */

label,
[type="text"] {
	display: block;
	width: 100%;
}

label {
	margin-bottom: 0.25em;
}

[type="text"] {
	font-size: inherit;
	margin-bottom: 1em;
	padding: 0.25em 1em;
}

[type="checkbox"] {
	margin-right: 0.5em;
}

/**
 * Buttons
 */

.btn {
	background-color: #0088cc;
	border: 1px solid #0088cc;
	border-radius: 1px;
	color: #ffffff;
	display: inline-block;
	font-size: 0.9375em;
	font-weight: normal;
	line-height: 1.2;
	margin-right: 0.3125em;
	margin-bottom: 0.3125em;
	padding: 0.5em 0.6875em;
}

.btn:hover,
.btn:focus,
.btn:active {
	background-color: #005580;
	border-color: #005580;
	color: #ffffff;
	text-decoration: none;
}

/**
 * Todos
 */

.todos {
	list-style: none;
	margin-left: 0;
	padding-left: 0;
}

.todos .completed {
	text-decoration: line-through;
}

.todo:before,
.todo:after {
	display: table;
	content: " ";
}

.todo:after {
	clear: both;
}

.todo label {
	width: auto;
}

.todo label,
.todo button {
	float: left;
}

.todo button {
	margin-left: 0.5em;
}
              
            
!

JS

              
                let {signal, component} = reef;

// Get the todo field
let todoField = document.querySelector('#todo-item');

// Create signal
let todos = signal(getTodos());

/**
 * Save todo items to local storage
 */
function saveTodos () {
    localStorage.setItem('todos', JSON.stringify(todos));
}

/**
 * Get todo items from local storage
 * @return {Array} The todo items (or an empty array if none exist)
 */
function getTodos () {
    let todos = localStorage.getItem('todos');
    if (todos) return JSON.parse(todos);
    return [];
}

/**
 * Add a todo to the list
 */
function addTodo (event) {

    // Only run if there's an item to add
    if (todoField.value.length < 1) return;

    // Prevent default form submission
    event.preventDefault();

    // Update the state
    todos.push({
        item: todoField.value,
        id: crypto.randomUUID(),
        completed: false
    });

    // Clear the input field and return to focus
    todoField.value = '';
    todoField.focus();

}

/**
 * Mark a todo as complete (or incomplete)
 * @param  {Node} item  The todo item
 */
function completeTodo (item) {

    // Get the todo item
    let todoItem = todos[item.getAttribute('data-todo')];
    if (!todoItem) return;

    // If it's completed, uncomplete it
    // Otherwise, mark is as complete
    if (todoItem.completed) {
        todoItem.completed = false;
    } else {
        todoItem.completed = true;
    }

}

/**
 * Delete a todo list item
 * @param  {Node} btn The delete button that was clicked
 */
function deleteTodo (btn) {

    // Get the index for the todo list item
    let index = btn.closest('.todo').querySelector('input').getAttribute('data-todo');
    if (!index) return;

    // Remove the item from state
    todos.splice(index, 1);

}

/**
 * Edit a todo list item
 * @param  {Node} btn The edit button that was clicked
 */
function editTodo (btn) {

    // Get the todo list DOM element
    let todoListItem = btn.closest('.todo');
    if (!todoListItem) return;

    // Get the index for the item
    let index = todoListItem.querySelector('input').getAttribute('data-todo');
    if (!index) return;

    // Get the item from state
    let todoItem = todos[index];
    if (!todoItem) return;

    // Update state
    todoItem.edit = true;

}

function saveEditTodo (event) {

    // Prevent default form submit
    event.preventDefault();

    // Get the todo list DOM node
    let newTodo = event.target.querySelector('.todo-update');
    if (!newTodo) return;

    // Get the todo list item index
    let index = newTodo.getAttribute('data-todo-edit');
    if (!index) return;

    // Get the item from state
    let todoItem = todos[index];
    if (!todoItem) return;

    // If the item is empty, delete the todo item
    // Otherwise, update it
    if (newTodo.value.length < 1) {
        todos.splice(index, 1);
    } else {
        todoItem.item = newTodo.value;
        todoItem.edit = false;
    }

}

/**
 * Remove all todo items from state
 */
function clearTodos () {
    todos.splice(0, todos.length);
}

/**
 * Handle click events
 */
function clickHandler (event) {

    // Complete todos
    let todo = event.target.closest('[data-todo]');
    if (todo) {
        completeTodo(todo);
    }

    // Edit todo
    let editBtn = event.target.closest('.todo-edit');
    if (editBtn) {
        editTodo(editBtn);
    }

    // Delete todo
    let deleteBtn = event.target.closest('.todo-delete');
    if (deleteBtn) {
        deleteTodo(deleteBtn);
    }

    // Clear all todos
    if (event.target.closest('.todo-clear')) {
        clearTodos();
    }
}

/**
 * Handle form submit events
 */
function submitHandler (event) {

    // If it's the "new todo" form, add the item
    if (event.target.matches('#todo-form')) {
        addTodo(event);
    }

    // If it's the "edit todo" form, save the edit
    if (event.target.matches('.todo-edit-form')) {
        saveEditTodo(event);
    }

}

/**
 * Create template
 */
function template () {

    // Create each todo item
    let html = todos.map(function (todo, index) {

        // If it's being edited, show a form
        // Otherwise, show the item with a checkbox
        if (todo.edit) {
            return `
                <li class="todo" id="todo-${todo.id}">
                    <form class="todo-edit-form">
                        <input type="text" class="todo-update" value="${todo.item}" data-todo-edit="${index}">
                        <button>Save</button>
                    </form>
                </li>`;
        } else {
            return `
                <li class="todo" id="todo-${todo.id}">
                    <label ${todo.completed ? ' class="completed"' : ''}>
                        <input data-todo="${index}" type="checkbox" ${todo.completed ? ' checked="checked"' : ''}>
                        <span class="todo-item">${todo.item}</span>
                    </label>
                    <button class="todo-edit">Edit</button>
                    <button class="todo-delete">Delete</button>
                </li>`;
        }

    }).join('');

    // If there are todo items, wrap it in an unordered list
    if (html.length > 0) {
        return `<ul class="todos">${html}</ul><p><button class="todo-clear">Clear All Todos</button></p>`;
    }

    return '';

}

// Create reactive component
component('#app', template);

// Listen for events
document.addEventListener('submit', submitHandler);
document.addEventListener('click', clickHandler);
document.addEventListener('reef:signal', saveTodos);
              
            
!
999px

Console