Todomvc backbone update (#101)

This commit is contained in:
Thorsten Kober 2023-03-14 10:29:04 -07:00 committed by GitHub
parent 6523f66497
commit 09210bc05e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 2279 additions and 14800 deletions

View file

@ -4,7 +4,9 @@ package-lock.json
/resources/tentative
/resources/todomvc/architecture-examples
/resources/todomvc/architecture-examples/*
!/resources/todomvc/architecture-examples/backbone
/resources/todomvc/vanilla-examples/*
!/resources/todomvc/vanilla-examples/javascript-es5
!/resources/todomvc/vanilla-examples/javascript-es6

2
.gitignore vendored
View file

@ -4,6 +4,8 @@
.directory
/node_modules
/resources/todomvc/architecture-examples/backbone/node_modules/
/resources/todomvc/vanilla-examples/javascript-es5/node_modules/
/resources/todomvc/vanilla-examples/javascript-es6/node_modules/
/resources/todomvc/vanilla-examples/javascript-es6-webpack/node_modules/

View file

@ -6,7 +6,9 @@ package-lock.json
/resources/tentative/*
!/resources/tentative/editors
/resources/todomvc/architecture-examples
/resources/todomvc/architecture-examples/*
!/resources/todomvc/architecture-examples/backbone
/resources/todomvc/vanilla-examples/*
!/resources/todomvc/vanilla-examples/javascript-es5
!/resources/todomvc/vanilla-examples/javascript-es6

View file

@ -189,8 +189,8 @@ Suites.push({
});
Suites.push({
name: "BackboneJS-TodoMVC",
url: "todomvc/architecture-examples/backbone/index.html",
name: "TodoMVC-Backbone",
url: "todomvc/architecture-examples/backbone/dist/index.html",
async prepare(page) {
await page.waitForElement("#appIsReady");
const newTodo = page.querySelector(".new-todo");

View file

@ -0,0 +1,21 @@
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
var ENTER_KEY = 13;
var ESC_KEY = 27;
$(function () {
"use strict";
// kick things off by creating the `App`
window.appView = new app.AppView();
/**
* Speedometer specific:
* Adding a dom node after todo app is done initializing.
* Speedometer waits for this dom note to be present in the dom.
*/
const dummyNodeToNotifyAppIsReady = document.createElement("div");
dummyNodeToNotifyAppIsReady.id = "appIsReady";
document.body.appendChild(dummyNodeToNotifyAppIsReady);
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,141 @@
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}

View file

@ -1,14 +1,14 @@
/*global Backbone */
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function () {
'use strict';
"use strict";
// Todo Collection
// ---------------
var Todos = Backbone.Collection.extend({
const Todos = Backbone.Collection.extend({
// Reference to this collection's model.
model: app.Todo,
@ -16,22 +16,22 @@ var app = app || {};
// Filter down the list of all todo items that are finished.
completed: function () {
return this.where({completed: true});
return this.where({ completed: true });
},
// Filter down the list to only todo items that are still not finished.
remaining: function () {
return this.where({completed: false});
return this.where({ completed: false });
},
// We keep the Todos in sequential order, despite being saved by unordered
// GUID in the database. This generates the next order number for new items.
nextOrder: function () {
return this.length ? this.last().get('order') + 1 : 1;
return this.length ? this.last().get("order") + 1 : 1;
},
// Todos are sorted by their original insertion order.
comparator: 'order'
comparator: "order",
});
// Create our global collection of **Todos**.

View file

@ -0,0 +1,376 @@
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 {
text-align: center;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
}
.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;
}
}

View file

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en" data-framework="backbone" data-version="1.4.1" data-features="">
<head>
<meta charset="UTF-8" />
<meta name="description" content="A TodoMVC workload app for Speedometer!" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>TodoMVC: Backbone</title>
<link rel="stylesheet" href="base.css" />
<link rel="stylesheet" href="index.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 class="toggle-all" id="toggle-all" type="checkbox" />
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list"></ul>
</section>
<footer class="footer"></footer>
</section>
<footer class="info">
<p>Click on input field to write your todo.</p>
<p>At least two characters are needed to be a valid entry.</p>
<p>Press 'enter' to add the todo.</p>
<p>Double-click to edit a todo</p>
</footer>
<script type="text/template" id="item-template">
<div class="view">
<input class="toggle" type="checkbox" <%= completed ? 'checked' : '' %>>
<label><%- title %></label>
<button class="destroy"></button>
</div>
<input class="edit" value="<%- title %>">
</script>
<script type="text/template" id="stats-template">
<span class="todo-count"><strong><%= remaining %></strong> <%= remaining === 1 ? 'item' : 'items' %> left</span>
<ul class="filters">
<li>
<a class="selected" href="#/">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<% if (completed) { %>
<button class="clear-completed">Clear completed</button>
<% } %>
</script>
<script src="jquery.min.js"></script>
<script src="underscore-min.js"></script>
<script src="backbone-min.js"></script>
<script src="sync/backbone.sync.js"></script>
<script src="models/todo.js"></script>
<script src="collections/todos.js"></script>
<script src="views/todo-view.js"></script>
<script src="views/app-view.js"></script>
<script src="routers/router.js"></script>
<script src="app.js"></script>
<script>
window.Backbone.sync = () => {};
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -1,8 +1,9 @@
/*global Backbone */
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function () {
'use strict';
"use strict";
// Todo Model
// ----------
@ -12,15 +13,15 @@ var app = app || {};
// Default attributes for the todo
// and ensure that each todo created has `title` and `completed` keys.
defaults: {
title: '',
completed: false
title: "",
completed: false,
},
// Toggle the `completed` state of this todo item.
toggle: function () {
this.save({
completed: !this.get('completed')
completed: !this.get("completed"),
});
}
},
});
})();

View file

@ -1,24 +1,26 @@
/*global Backbone */
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function () {
'use strict';
"use strict";
// Todo Router
// ----------
var TodoRouter = Backbone.Router.extend({
const TodoRouter = Backbone.Router.extend({
routes: {
'*filter': 'setFilter'
"*filter": "setFilter",
},
setFilter: function (param) {
// Set the current filter to be used
app.TodoFilter = param || '';
app.TodoFilter = param || "";
// Trigger a collection filter event, causing hiding/unhiding
// of Todo view items
app.todos.trigger('filter');
}
app.todos.trigger("filter");
},
});
app.TodoRouter = new TodoRouter();

View file

@ -0,0 +1,102 @@
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
Backbone.sync = (function (method, model, options) {
// we need to make sure we initialize a store, in this case
// we will just use a JS object.
const cache = {};
// The size will be primarily used to assign ids to newly
// created models. Each time a new model is created, the size
// will be incremented.
let size = 0;
// since we need to create a store for the models/collections
// we are actually going to invoke the outer function which will
// return this function below as the Backbone.sync that will
// actually be used.
return function (method, model, options) {
// create a new deferred object. standard sync returns the $.ajax
// request, which internally returns a deferred. It's important to
// do this so that people can chain on fetch using the standard .then/.fail
// syntax, rather than just the success/error callbacks.
const deferred = $.Deferred();
// when creating a new model...
if (method === "create") {
// first assign it an id. The server would traditionally do this.
model.id = size;
// store it in our cache
cache[model.id] = model;
// make sure we increment the number of models we now have.
size += 1;
// if a success callback was provided, execute it.
if (options.success)
options.success(model, model.toJSON(), options);
// resolve the deferred.
deferred.resolve(model);
// we are updating a model
} else if (method === "update") {
// as long as the model actually exists in our store
if (cache[model.id]) {
// overwrite what is currently in the store with this model.
cache[model.id] = model;
// if a success callback was provided, execute it.
if (options.success)
options.success(model, model.toJSON(), options);
deferred.resolve(model);
// if this model doesn't exist yet, we can't update it
} else {
if (options.error)
options.error(model, "Model not found");
deferred.reject(model);
}
// if we're trying to read a model...
} else if (method === "read") {
// as long as it exists
if (cache[model.id]) {
// if a success callback was provided, execute it.
if (options.success)
options.success(model, cache[model.id].toJSON(), options);
// resolve
deferred.resolve(model);
} else {
if (options.error)
options.error(model, "Model not found");
deferred.reject(model);
}
// if we're deleting a model...
} else if (method === "delete") {
// first make sure it exists in the cache
if (cache[model.id]) {
// then remove it from the cache
delete cache[model.id];
// and trigger the success callback. Note we're passing an
// empty object as the second argument, because a deletion
// would result in an empty return from the server.
if (options.success)
options.success(model, {}, options);
// resolve the deferred
deferred.resolve(model);
// otherwise, error that the model doesn't exist.
} else {
if (options.error)
options.error(model, "Model not found");
deferred.reject(model);
}
}
return deferred.promise();
};
})();

File diff suppressed because one or more lines are too long

View file

@ -1,70 +1,76 @@
/*global Backbone, jQuery, _, ENTER_KEY */
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function ($) {
'use strict';
"use strict";
// The Application
// ---------------
// Our overall **AppView** is the top-level piece of UI.
app.AppView = Backbone.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: '.todoapp',
el: ".todoapp",
// Our template for the line of statistics at the bottom of the app.
statsTemplate: _.template($('#stats-template').html()),
statsTemplate: _.template($("#stats-template").html()),
// Delegated events for creating new items, and clearing completed ones.
events: {
'keypress .new-todo': 'createOnEnter',
'click .clear-completed': 'clearCompleted',
'click .toggle-all': 'toggleAllComplete'
"keypress .new-todo": "createOnEnter",
"click .clear-completed": "clearCompleted",
"click .toggle-all": "toggleAllComplete",
},
// At initialization we bind to the relevant events on the `Todos`
// collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*.
initialize: function () {
this.allCheckbox = this.$('.toggle-all')[0];
this.$input = this.$('.new-todo');
this.$footer = this.$('.footer');
this.$main = this.$('.main');
this.$list = $('.todo-list');
this.allCheckbox = this.$(".toggle-all")[0];
this.$input = this.$(".new-todo");
this.$footer = this.$(".footer");
this.$main = this.$(".main");
this.$list = $(".todo-list");
this.listenTo(app.todos, 'add', this.addOne);
this.listenTo(app.todos, 'reset', this.addAll);
this.listenTo(app.todos, 'change:completed', this.filterOne);
this.listenTo(app.todos, 'filter', this.filterAll);
this.listenTo(app.todos, 'all', _.debounce(this.render, 0));
this.listenTo(app.todos, "add", this.addOne);
this.listenTo(app.todos, "reset", this.addAll);
this.listenTo(app.todos, "change:completed", this.filterOne);
this.listenTo(app.todos, "filter", this.filterAll);
this.listenTo(app.todos, "all", _.debounce(this.render, 0));
// initially hide main and footer section
this.$main.hide();
this.$footer.hide();
// Suppresses 'add' events with {reset: true} and prevents the app view
// from being re-rendered for every model. Only renders when the 'reset'
// event is triggered at the end of the fetch.
app.todos.fetch({reset: true});
app.todos.fetch({ reset: true });
},
// Re-rendering the App just means refreshing the statistics -- the rest
// of the app doesn't change.
render: function () {
var completed = app.todos.completed().length;
var remaining = app.todos.remaining().length;
const completed = app.todos.completed().length;
const remaining = app.todos.remaining().length;
if (app.todos.length) {
this.$main.show();
this.$footer.show();
this.$footer.html(this.statsTemplate({
completed: completed,
remaining: remaining
}));
this.$footer.html(
this.statsTemplate({
completed: completed,
remaining: remaining,
})
);
this.$('.filters li a')
.removeClass('selected')
.filter('[href="#/' + (app.TodoFilter || '') + '"]')
.addClass('selected');
this.$(".filters li a")
.removeClass("selected")
.filter(`[href="#/${app.TodoFilter || ""}"]`)
.addClass("selected");
} else {
this.$main.hide();
this.$footer.hide();
@ -76,18 +82,18 @@ var app = app || {};
// Add a single todo item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOne: function (todo) {
var view = new app.TodoView({ model: todo });
const view = new app.TodoView({ model: todo });
this.$list.append(view.render().el);
},
// Add all items in the **Todos** collection at once.
addAll: function () {
this.$list.html('');
this.$list.html("");
app.todos.each(this.addOne, this);
},
filterOne: function (todo) {
todo.trigger('visible');
todo.trigger("visible");
},
filterAll: function () {
@ -99,7 +105,7 @@ var app = app || {};
return {
title: this.$input.val().trim(),
order: app.todos.nextOrder(),
completed: false
completed: false,
};
},
@ -108,24 +114,24 @@ var app = app || {};
createOnEnter: function (e) {
if (e.which === ENTER_KEY && this.$input.val().trim()) {
app.todos.create(this.newAttributes());
this.$input.val('');
this.$input.val("");
}
},
// Clear all completed todo items, destroying their models.
clearCompleted: function () {
_.invoke(app.todos.completed(), 'destroy');
_.invoke(app.todos.completed(), "destroy");
return false;
},
toggleAllComplete: function () {
var completed = this.allCheckbox.checked;
const completed = this.allCheckbox.checked;
app.todos.each(function (todo) {
todo.save({
completed: completed
completed: completed,
});
});
}
},
});
})(jQuery);

View file

@ -1,28 +1,29 @@
/*global Backbone, jQuery, _, ENTER_KEY, ESC_KEY */
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function ($) {
'use strict';
"use strict";
// Todo Item View
// --------------
// The DOM element for a todo item...
app.TodoView = Backbone.View.extend({
//... is a list tag.
tagName: 'li',
// ... is a list tag.
tagName: "li",
// Cache the template function for a single item.
template: _.template($('#item-template').html()),
template: _.template($("#item-template").html()),
// The DOM events specific to an item.
events: {
'click .toggle': 'toggleCompleted',
'dblclick label': 'edit',
'click .destroy': 'clear',
'keypress .edit': 'updateOnEnter',
'keydown .edit': 'revertOnEscape',
'blur .edit': 'close'
"click .toggle": "toggleCompleted",
"dblclick label": "edit",
"click .destroy": "clear",
"keypress .edit": "updateOnEnter",
"keydown .edit": "revertOnEscape",
"blur .edit": "close",
},
// The TodoView listens for changes to its model, re-rendering. Since
@ -30,9 +31,9 @@ var app = app || {};
// **TodoView** in this app, we set a direct reference on the model for
// convenience.
initialize: function () {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
this.listenTo(this.model, 'visible', this.toggleVisible);
this.listenTo(this.model, "change", this.render);
this.listenTo(this.model, "destroy", this.remove);
this.listenTo(this.model, "visible", this.toggleVisible);
},
// Re-render the titles of the todo item.
@ -44,25 +45,22 @@ var app = app || {};
// `id` change. It's known Backbone LocalStorage bug, therefore
// we've to create a workaround.
// https://github.com/tastejs/todomvc/issues/469
if (this.model.changed.id !== undefined) {
return;
}
if (this.model.changed.id !== undefined)
return null;
this.$el.html(this.template(this.model.toJSON()));
this.$el.toggleClass('completed', this.model.get('completed'));
this.$el.toggleClass("completed", this.model.get("completed"));
this.toggleVisible();
this.$input = this.$('.edit');
this.$input = this.$(".edit");
return this;
},
toggleVisible: function () {
this.$el.toggleClass('hidden', this.isHidden());
this.$el.toggleClass("hidden", this.isHidden());
},
isHidden: function () {
return this.model.get('completed') ?
app.TodoFilter === 'active' :
app.TodoFilter === 'completed';
return this.model.get("completed") ? app.TodoFilter === "active" : app.TodoFilter === "completed";
},
// Toggle the `"completed"` state of the model.
@ -72,52 +70,51 @@ var app = app || {};
// Switch this view into `"editing"` mode, displaying the input field.
edit: function () {
this.$el.addClass('editing');
this.$el.addClass("editing");
const temp = this.$input.val();
this.$input.trigger("focus").val("").val(temp);
this.$input.focus();
},
// Close the `"editing"` mode, saving changes to the todo.
close: function () {
var value = this.$input.val();
var trimmedValue = value.trim();
const value = this.$input.val();
const trimmedValue = value.trim();
// We don't want to handle blur events from an item that is no
// longer being edited. Relying on the CSS class here has the
// benefit of us not having to maintain state in the DOM and the
// JavaScript logic.
if (!this.$el.hasClass('editing')) {
if (!this.$el.hasClass("editing"))
return;
}
if (trimmedValue) {
if (trimmedValue)
this.model.save({ title: trimmedValue });
} else {
else
this.clear();
}
this.$el.removeClass('editing');
this.$el.removeClass("editing");
},
// If you hit `enter`, we're through editing the item.
updateOnEnter: function (e) {
if (e.which === ENTER_KEY) {
if (e.which === ENTER_KEY)
this.close();
}
},
// If you're pressing `escape` we revert your change by simply leaving
// the `editing` state.
revertOnEscape: function (e) {
if (e.which === ESC_KEY) {
this.$el.removeClass('editing');
this.$el.removeClass("editing");
// Also reset the hidden input back to the original value.
this.$input.val(this.model.get('title'));
this.$input.val(this.model.get("title"));
}
},
// Remove the item, destroy the model from *localStorage* and delete its view.
clear: function () {
this.model.destroy();
}
},
});
})(jQuery);

View file

@ -1,28 +1,32 @@
<!doctype html>
<html lang="en" data-framework="backbonejs">
<!DOCTYPE html>
<html lang="en" data-framework="backbone" data-version="1.4.1" data-features="">
<head>
<meta charset="utf-8">
<title>Backbone.js TodoMVC example</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
<meta charset="UTF-8" />
<meta name="description" content="A TodoMVC workload app for Speedometer!" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>TodoMVC: Backbone</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css" />
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css" />
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus>
<input class="new-todo" placeholder="What needs to be done?" autofocus />
</header>
<section class="main">
<input class="toggle-all" id="toggle-all" type="checkbox">
<input class="toggle-all" id="toggle-all" type="checkbox" />
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list"></ul>
</section>
<footer class="footer"></footer>
</section>
<footer class="info">
<p>Click on input field to write your todo.</p>
<p>At least two characters are needed to be a valid entry.</p>
<p>Press 'enter' to add the todo.</p>
<p>Double-click to edit a todo</p>
<p>Written by <a href="https://github.com/addyosmani">Addy Osmani</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script type="text/template" id="item-template">
<div class="view">
@ -49,19 +53,18 @@
<button class="clear-completed">Clear completed</button>
<% } %>
</script>
<!-- <script src="node_modules/todomvc-common/base.js"></script> -->
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/underscore/underscore-min.js"></script>
<script src="node_modules/backbone/backbone-min.js"></script>
<script src="js/backbone.sync.js"></script>
<script src="js/models/todo.js"></script>
<script src="js/collections/todos.js"></script>
<script src="js/views/todo-view.js"></script>
<script src="js/views/app-view.js"></script>
<script src="js/routers/router.js"></script>
<script src="js/app.js"></script>
<script src="src/sync/backbone.sync.js"></script>
<script src="src/models/todo.js"></script>
<script src="src/collections/todos.js"></script>
<script src="src/views/todo-view.js"></script>
<script src="src/views/app-view.js"></script>
<script src="src/routers/router.js"></script>
<script src="src/app.js"></script>
<script>
window.Backbone.sync = () => {};
window.Backbone.sync = () => {};
</script>
</body>
</html>

View file

@ -1,16 +0,0 @@
/*global $ */
/*jshint unused:false */
var app = app || {};
var ENTER_KEY = 13;
var ESC_KEY = 27;
$(function () {
'use strict';
// kick things off by creating the `App`
window.appView = new app.AppView();
var dummyNodeToNotifyAppIsReady = document.createElement('div');
dummyNodeToNotifyAppIsReady.id = 'appIsReady';
document.body.appendChild(dummyNodeToNotifyAppIsReady);
});

View file

@ -1,118 +0,0 @@
Backbone.sync = function(method, model, options) {
// we need to make sure we initialize a store, in this case
// we will just use a JS object.
var cache = {};
// The size will be primarily used to assign ids to newly
// created models. Each time a new model is created, the size
// will be incremented.
var size = 0;
// since we need to create a store for the models/collections
// we are actually going to invoke the outer function which will
// return this function below as the Backbone.sync that will
// actually be used.
return function(method, model, options) {
// create a new deferred object. standard sync returns the $.ajax
// request, which internally returns a deferred. It's important to
// do this so that people can chain on fetch using the standard .then/.fail
// syntax, rather than just the success/error callbacks.
var deferred = $.Deferred();
// when creating a new model...
if (method === "create") {
// first assign it an id. The server would traditionally do this.
model.id = size;
// store it in our cache
cache[model.id] = model;
// make sure we increment the number of models we now have.
size += 1;
// if a success callback was provided, execute it.
if (options.success) {
options.success(model, model.toJSON(), options);
}
// resolve the deferred.
deferred.resolve(model);
// we are updating a model
} else if (method === "update") {
// as long as the model actually exists in our store
if (cache[model.id]) {
// overwrite what is currently in the store with this model.
cache[model.id] = model;
// if a success callback was provided, execute it.
if (options.success) {
options.success(model, model.toJSON(), options);
}
deferred.resolve(model);
// if this model doesn't exist yet, we can't update it
} else {
if (options.error) {
options.error(model, "Model not found");
}
deferred.reject(model);
}
// if we're trying to read a model...
} else if (method === "read") {
// as long as it exists
if (cache[model.id]) {
// if a success callback was provided, execute it.
if (options.success) {
options.success(model, cache[model.id].toJSON(), options);
}
// resolve
deferred.resolve(model);
} else {
if (options.error) {
options.error(model, "Model not found");
}
deferred.reject(model);
}
// if we're deleting a model...
} else if (method === "delete") {
// first make sure it exists in the cache
if (cache[model.id]) {
// then remove it from the cache
delete cache[model.id];
// and trigger the success callback. Note we're passing an
// empty object as the second argument, because a deletion
// would result in an empty return from the server.
if (options.success) {
options.success(model, {}, options);
}
// resolve the deferred
deferred.resolve(model);
// otherwise, error that the model doesn't exist.
} else {
if (options.error) {
options.error(model, "Model not found");
}
deferred.reject(model);
}
}
return deferred.promise();
}
}();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,376 +0,0 @@
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 {
text-align: center;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
}
.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;
}
}

View file

@ -1,141 +0,0 @@
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}

View file

@ -1,249 +0,0 @@
/* global _ */
(function () {
'use strict';
/* jshint ignore:start */
// Underscore's Template Module
// Courtesy of underscorejs.org
var _ = (function (_) {
_.defaults = function (object) {
if (!object) {
return object;
}
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
var iterable = arguments[argsIndex];
if (iterable) {
for (var key in iterable) {
if (object[key] == null) {
object[key] = iterable[key];
}
}
}
}
return object;
}
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /(.)^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
var render;
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
}
if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
return match;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n";
try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
return template;
};
return _;
})({});
if (location.hostname === 'todomvc.com') {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-31081062-1', 'auto');
ga('send', 'pageview');
}
/* jshint ignore:end */
function redirect() {
if (location.hostname === 'tastejs.github.io') {
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
}
}
function findRoot() {
var base = location.href.indexOf('examples/');
return location.href.substr(0, base);
}
function getFile(file, callback) {
if (!location.host) {
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
}
var xhr = new XMLHttpRequest();
xhr.open('GET', findRoot() + file, true);
xhr.send();
xhr.onload = function () {
if (xhr.status === 200 && callback) {
callback(xhr.responseText);
}
};
}
function Learn(learnJSON, config) {
if (!(this instanceof Learn)) {
return new Learn(learnJSON, config);
}
var template, framework;
if (typeof learnJSON !== 'object') {
try {
learnJSON = JSON.parse(learnJSON);
} catch (e) {
return;
}
}
if (config) {
template = config.template;
framework = config.framework;
}
if (!template && learnJSON.templates) {
template = learnJSON.templates.todomvc;
}
if (!framework && document.querySelector('[data-framework]')) {
framework = document.querySelector('[data-framework]').dataset.framework;
}
this.template = template;
if (learnJSON.backend) {
this.frameworkJSON = learnJSON.backend;
this.frameworkJSON.issueLabel = framework;
this.append({
backend: true
});
} else if (learnJSON[framework]) {
this.frameworkJSON = learnJSON[framework];
this.frameworkJSON.issueLabel = framework;
this.append();
}
this.fetchIssueCount();
}
Learn.prototype.append = function (opts) {
var aside = document.createElement('aside');
aside.innerHTML = _.template(this.template, this.frameworkJSON);
aside.className = 'learn';
if (opts && opts.backend) {
// Remove demo link
var sourceLinks = aside.querySelector('.source-links');
var heading = sourceLinks.firstElementChild;
var sourceLink = sourceLinks.lastElementChild;
// Correct link path
var href = sourceLink.getAttribute('href');
sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http')));
sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML;
} else {
// Localize demo links
var demoLinks = aside.querySelectorAll('.demo-link');
Array.prototype.forEach.call(demoLinks, function (demoLink) {
if (demoLink.getAttribute('href').substr(0, 4) !== 'http') {
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
}
});
}
document.body.className = (document.body.className + ' learn-bar').trim();
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
};
Learn.prototype.fetchIssueCount = function () {
var issueLink = document.getElementById('issue-count-link');
if (issueLink) {
var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
var parsedResponse = JSON.parse(e.target.responseText);
if (parsedResponse instanceof Array) {
var count = parsedResponse.length;
if (count !== 0) {
issueLink.innerHTML = 'This app has ' + count + ' open issues';
document.getElementById('issue-count').style.display = 'inline';
}
}
};
xhr.send();
}
};
redirect();
getFile('learn.json', Learn);
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,34 +1,826 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"backbone": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.3.3.tgz",
"integrity": "sha1-TMgOp8sWMaxHSInOQPL4vGg7KZk=",
"requires": {
"underscore": "1.8.3"
}
"name": "backbone",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "backbone",
"version": "1.0.0",
"dependencies": {
"backbone": "^1.4.1",
"jquery": "^3.6.4",
"todomvc-app-css": "^2.0.0",
"todomvc-common": "^1.0.1",
"underscore": "^1.13.6"
},
"devDependencies": {
"http-server": "^14.1.1"
},
"engines": {
"node": ">=18.13.0",
"npm": ">=8.19.3"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"dependencies": {
"lodash": "^4.17.14"
}
},
"node_modules/backbone": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.1.tgz",
"integrity": "sha512-ADy1ztN074YkWbHi8ojJVFe3vAanO/lrzMGZWUClIP7oDD/Pjy2vrASraUP+2EVCfIiTtCW4FChVow01XneivA==",
"dependencies": {
"underscore": ">=1.8.3"
}
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"dev": true,
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
"dev": true,
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/get-intrinsic": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true,
"bin": {
"he": "bin/he"
}
},
"node_modules/html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
"dev": true,
"dependencies": {
"whatwg-encoding": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true,
"dependencies": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/http-server": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
"dev": true,
"dependencies": {
"basic-auth": "^2.0.1",
"chalk": "^4.1.2",
"corser": "^2.0.1",
"he": "^1.2.0",
"html-encoding-sniffer": "^3.0.0",
"http-proxy": "^1.18.1",
"mime": "^1.6.0",
"minimist": "^1.2.6",
"opener": "^1.5.1",
"portfinder": "^1.0.28",
"secure-compare": "3.0.1",
"union": "~0.5.0",
"url-join": "^4.0.1"
},
"bin": {
"http-server": "bin/http-server"
},
"engines": {
"node": ">=12"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/jquery": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.4.tgz",
"integrity": "sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ=="
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"dev": true,
"bin": {
"opener": "bin/opener-bin.js"
}
},
"node_modules/portfinder": {
"version": "1.0.32",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
"integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
"dev": true,
"dependencies": {
"async": "^2.6.4",
"debug": "^3.2.7",
"mkdirp": "^0.5.6"
},
"engines": {
"node": ">= 0.12.0"
}
},
"node_modules/qs": {
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
"dev": true,
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"dev": true
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"node_modules/secure-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
"dev": true
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/todomvc-app-css": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.1.0.tgz",
"integrity": "sha1-tvJxbTOa+i5feZNH0qSLBTliQqU="
},
"node_modules/todomvc-common": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.3.tgz",
"integrity": "sha1-y6GGgQnKpAG5COv3d5TMEBS3SHM="
},
"node_modules/underscore": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
"node_modules/union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
"dev": true,
"dependencies": {
"qs": "^6.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
"node_modules/whatwg-encoding": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
"dev": true,
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=12"
}
}
},
"jquery": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz",
"integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c="
},
"todomvc-app-css": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.1.0.tgz",
"integrity": "sha1-tvJxbTOa+i5feZNH0qSLBTliQqU="
},
"todomvc-common": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.3.tgz",
"integrity": "sha1-y6GGgQnKpAG5COv3d5TMEBS3SHM="
},
"underscore": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
}
},
"backbone": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.1.tgz",
"integrity": "sha512-ADy1ztN074YkWbHi8ojJVFe3vAanO/lrzMGZWUClIP7oDD/Pjy2vrASraUP+2EVCfIiTtCW4FChVow01XneivA==",
"requires": {
"underscore": ">=1.8.3"
}
},
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"dev": true,
"requires": {
"safe-buffer": "5.1.2"
}
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
"dev": true
},
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"dev": true
},
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"dev": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"get-intrinsic": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
"html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
"dev": true,
"requires": {
"whatwg-encoding": "^2.0.0"
}
},
"http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true,
"requires": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
}
},
"http-server": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
"dev": true,
"requires": {
"basic-auth": "^2.0.1",
"chalk": "^4.1.2",
"corser": "^2.0.1",
"he": "^1.2.0",
"html-encoding-sniffer": "^3.0.0",
"http-proxy": "^1.18.1",
"mime": "^1.6.0",
"minimist": "^1.2.6",
"opener": "^1.5.1",
"portfinder": "^1.0.28",
"secure-compare": "3.0.1",
"union": "~0.5.0",
"url-join": "^4.0.1"
}
},
"iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
},
"jquery": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.4.tgz",
"integrity": "sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ=="
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true
},
"minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true
},
"mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"requires": {
"minimist": "^1.2.6"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
"dev": true
},
"opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"dev": true
},
"portfinder": {
"version": "1.0.32",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
"integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
"dev": true,
"requires": {
"async": "^2.6.4",
"debug": "^3.2.7",
"mkdirp": "^0.5.6"
}
},
"qs": {
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
"dev": true,
"requires": {
"side-channel": "^1.0.4"
}
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"dev": true
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"secure-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
"dev": true
},
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"todomvc-app-css": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.1.0.tgz",
"integrity": "sha1-tvJxbTOa+i5feZNH0qSLBTliQqU="
},
"todomvc-common": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.3.tgz",
"integrity": "sha1-y6GGgQnKpAG5COv3d5TMEBS3SHM="
},
"underscore": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
"union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
"dev": true,
"requires": {
"qs": "^6.4.0"
}
},
"url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
"whatwg-encoding": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
"dev": true,
"requires": {
"iconv-lite": "0.6.3"
}
}
}
}
}

View file

@ -1,10 +1,25 @@
{
"private": true,
"dependencies": {
"backbone": "^1.3.3",
"jquery": "^3.1.1",
"todomvc-app-css": "^2.0.0",
"todomvc-common": "^1.0.1",
"underscore": "^1.7.0"
}
"name": "backbone",
"version": "1.0.0",
"description": "TodoMVC app written with backbone.",
"engines": {
"node": ">=18.13.0",
"npm": ">=8.19.3"
},
"private": true,
"scripts": {
"dev": "http-server ./ -p 7001 -c-1 --cors",
"build": "node scripts/build.js",
"serve": "http-server ./dist -p 7002 -c-1 --cors"
},
"dependencies": {
"backbone": "^1.4.1",
"jquery": "^3.6.4",
"todomvc-app-css": "^2.0.0",
"todomvc-common": "^1.0.1",
"underscore": "^1.13.6"
},
"devDependencies": {
"http-server": "^14.1.1"
}
}

View file

@ -1,11 +1,53 @@
# Speedometer 2.1: Backbone.js TodoMVC example
# Speedometer 3.0: TodoMVC: Backbone
## Test locally
## Description
1. Run `npm install` to install the dependencies.
2. Run a local server from this directory. (You could use [`npm i -g http-server`](https://github.com/indexzero/http-server).)
3. Open the URL pointing to the local server in your web browser of choice.
This application uses Backbone to implement a todo application.
## Build
Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.
There is no build step for this example.
[backbonejs.org](https://backbonejs.org/)
## Implementation details
Backbone.js uses a MV(\*) pattern, which is loosely based on the MVC or MVP patterns.
Backbone.js:\
Model: model files\
View: html file in conjunction with underscore's templating solution\
Controller: view and router files
MVC:\
Model: maintains the data and behavior of an application\
View: displays the model in the ui\
Controller: serves as an interface between view & model components
The storage solution uses an in-memory data object (contained in the sync file).
## Built steps
A simple build script copies all necessary files to a `dist` folder.
It does not rely on compilers or transpilers and serves raw html, css and js files to the user.
```
npm run build
```
## Requirements
The only requirement is an installation of Node, to be able to install dependencies and run scripts to serve a local server.
```
* Node (min version: 18.13.0)
* NPM (min version: 8.19.3)
```
## Local preview
```
terminal:
1. npm install
2. npm run dev
browser:
1. http://localhost:7001/
```

View file

@ -0,0 +1,57 @@
const fs = require("fs").promises;
const rootDirectory = "./";
const sourceDirectory = "./src";
const targetDirectory = "./dist";
const htmlFile = "index.html";
const filesToMove = ["node_modules/todomvc-common/base.css", "node_modules/todomvc-app-css/index.css", "node_modules/jquery/dist/jquery.min.js", "node_modules/underscore/underscore-min.js", "node_modules/backbone/backbone-min.js"];
const copy = async (src, dest) => {
await fs.copyFile(src, dest);
};
const build = async () => {
// remove dist directory if it exists
await fs.rm(targetDirectory, { recursive: true, force: true });
// re-create the directory.
await fs.mkdir(targetDirectory);
// copy src folder
await fs.cp(sourceDirectory, targetDirectory, { recursive: true }, (err) => {
if (err)
console.error(err);
});
// copy html file
await fs.copyFile(`${rootDirectory}${htmlFile}`, `${targetDirectory}/${htmlFile}`);
// copy files to move
for (let i = 0; i < filesToMove.length; i++) {
const fileName = filesToMove[i].split("/").pop();
await copy(filesToMove[i], `${targetDirectory}/${fileName}`);
}
// read html file
let html = await fs.readFile(`${targetDirectory}/${htmlFile}`, "utf8");
// remove base paths from files to move
for (let i = 0; i < filesToMove.length; i++) {
const fileName = filesToMove[i].split("/").pop();
html = html.replace(filesToMove[i], fileName);
}
// remove basePath from source directory
const basePath = `${sourceDirectory.split("/")[1]}/`;
const re = new RegExp(basePath, "g");
html = html.replace(re, "");
// write html files
await fs.writeFile(`${targetDirectory}/${htmlFile}`, html);
console.log("done!!");
};
build();

View file

@ -0,0 +1,21 @@
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
var ENTER_KEY = 13;
var ESC_KEY = 27;
$(function () {
"use strict";
// kick things off by creating the `App`
window.appView = new app.AppView();
/**
* Speedometer specific:
* Adding a dom node after todo app is done initializing.
* Speedometer waits for this dom note to be present in the dom.
*/
const dummyNodeToNotifyAppIsReady = document.createElement("div");
dummyNodeToNotifyAppIsReady.id = "appIsReady";
document.body.appendChild(dummyNodeToNotifyAppIsReady);
});

View file

@ -0,0 +1,39 @@
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function () {
"use strict";
// Todo Collection
// ---------------
const Todos = Backbone.Collection.extend({
// Reference to this collection's model.
model: app.Todo,
// Save all of the todo items under this example's namespace.
// Filter down the list of all todo items that are finished.
completed: function () {
return this.where({ completed: true });
},
// Filter down the list to only todo items that are still not finished.
remaining: function () {
return this.where({ completed: false });
},
// We keep the Todos in sequential order, despite being saved by unordered
// GUID in the database. This generates the next order number for new items.
nextOrder: function () {
return this.length ? this.last().get("order") + 1 : 1;
},
// Todos are sorted by their original insertion order.
comparator: "order",
});
// Create our global collection of **Todos**.
app.todos = new Todos();
})();

View file

@ -0,0 +1,27 @@
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function () {
"use strict";
// Todo Model
// ----------
// Our basic **Todo** model has `title`, `order`, and `completed` attributes.
app.Todo = Backbone.Model.extend({
// Default attributes for the todo
// and ensure that each todo created has `title` and `completed` keys.
defaults: {
title: "",
completed: false,
},
// Toggle the `completed` state of this todo item.
toggle: function () {
this.save({
completed: !this.get("completed"),
});
},
});
})();

View file

@ -0,0 +1,28 @@
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function () {
"use strict";
// Todo Router
// ----------
const TodoRouter = Backbone.Router.extend({
routes: {
"*filter": "setFilter",
},
setFilter: function (param) {
// Set the current filter to be used
app.TodoFilter = param || "";
// Trigger a collection filter event, causing hiding/unhiding
// of Todo view items
app.todos.trigger("filter");
},
});
app.TodoRouter = new TodoRouter();
Backbone.history.start();
})();

View file

@ -0,0 +1,102 @@
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
Backbone.sync = (function (method, model, options) {
// we need to make sure we initialize a store, in this case
// we will just use a JS object.
const cache = {};
// The size will be primarily used to assign ids to newly
// created models. Each time a new model is created, the size
// will be incremented.
let size = 0;
// since we need to create a store for the models/collections
// we are actually going to invoke the outer function which will
// return this function below as the Backbone.sync that will
// actually be used.
return function (method, model, options) {
// create a new deferred object. standard sync returns the $.ajax
// request, which internally returns a deferred. It's important to
// do this so that people can chain on fetch using the standard .then/.fail
// syntax, rather than just the success/error callbacks.
const deferred = $.Deferred();
// when creating a new model...
if (method === "create") {
// first assign it an id. The server would traditionally do this.
model.id = size;
// store it in our cache
cache[model.id] = model;
// make sure we increment the number of models we now have.
size += 1;
// if a success callback was provided, execute it.
if (options.success)
options.success(model, model.toJSON(), options);
// resolve the deferred.
deferred.resolve(model);
// we are updating a model
} else if (method === "update") {
// as long as the model actually exists in our store
if (cache[model.id]) {
// overwrite what is currently in the store with this model.
cache[model.id] = model;
// if a success callback was provided, execute it.
if (options.success)
options.success(model, model.toJSON(), options);
deferred.resolve(model);
// if this model doesn't exist yet, we can't update it
} else {
if (options.error)
options.error(model, "Model not found");
deferred.reject(model);
}
// if we're trying to read a model...
} else if (method === "read") {
// as long as it exists
if (cache[model.id]) {
// if a success callback was provided, execute it.
if (options.success)
options.success(model, cache[model.id].toJSON(), options);
// resolve
deferred.resolve(model);
} else {
if (options.error)
options.error(model, "Model not found");
deferred.reject(model);
}
// if we're deleting a model...
} else if (method === "delete") {
// first make sure it exists in the cache
if (cache[model.id]) {
// then remove it from the cache
delete cache[model.id];
// and trigger the success callback. Note we're passing an
// empty object as the second argument, because a deletion
// would result in an empty return from the server.
if (options.success)
options.success(model, {}, options);
// resolve the deferred
deferred.resolve(model);
// otherwise, error that the model doesn't exist.
} else {
if (options.error)
options.error(model, "Model not found");
deferred.reject(model);
}
}
return deferred.promise();
};
})();

View file

@ -0,0 +1,137 @@
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function ($) {
"use strict";
// The Application
// ---------------
// Our overall **AppView** is the top-level piece of UI.
app.AppView = Backbone.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: ".todoapp",
// Our template for the line of statistics at the bottom of the app.
statsTemplate: _.template($("#stats-template").html()),
// Delegated events for creating new items, and clearing completed ones.
events: {
"keypress .new-todo": "createOnEnter",
"click .clear-completed": "clearCompleted",
"click .toggle-all": "toggleAllComplete",
},
// At initialization we bind to the relevant events on the `Todos`
// collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*.
initialize: function () {
this.allCheckbox = this.$(".toggle-all")[0];
this.$input = this.$(".new-todo");
this.$footer = this.$(".footer");
this.$main = this.$(".main");
this.$list = $(".todo-list");
this.listenTo(app.todos, "add", this.addOne);
this.listenTo(app.todos, "reset", this.addAll);
this.listenTo(app.todos, "change:completed", this.filterOne);
this.listenTo(app.todos, "filter", this.filterAll);
this.listenTo(app.todos, "all", _.debounce(this.render, 0));
// initially hide main and footer section
this.$main.hide();
this.$footer.hide();
// Suppresses 'add' events with {reset: true} and prevents the app view
// from being re-rendered for every model. Only renders when the 'reset'
// event is triggered at the end of the fetch.
app.todos.fetch({ reset: true });
},
// Re-rendering the App just means refreshing the statistics -- the rest
// of the app doesn't change.
render: function () {
const completed = app.todos.completed().length;
const remaining = app.todos.remaining().length;
if (app.todos.length) {
this.$main.show();
this.$footer.show();
this.$footer.html(
this.statsTemplate({
completed: completed,
remaining: remaining,
})
);
this.$(".filters li a")
.removeClass("selected")
.filter(`[href="#/${app.TodoFilter || ""}"]`)
.addClass("selected");
} else {
this.$main.hide();
this.$footer.hide();
}
this.allCheckbox.checked = !remaining;
},
// Add a single todo item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOne: function (todo) {
const view = new app.TodoView({ model: todo });
this.$list.append(view.render().el);
},
// Add all items in the **Todos** collection at once.
addAll: function () {
this.$list.html("");
app.todos.each(this.addOne, this);
},
filterOne: function (todo) {
todo.trigger("visible");
},
filterAll: function () {
app.todos.each(this.filterOne, this);
},
// Generate the attributes for a new Todo item.
newAttributes: function () {
return {
title: this.$input.val().trim(),
order: app.todos.nextOrder(),
completed: false,
};
},
// If you hit return in the main input field, create new **Todo** model,
// persisting it to *memory*.
createOnEnter: function (e) {
if (e.which === ENTER_KEY && this.$input.val().trim()) {
app.todos.create(this.newAttributes());
this.$input.val("");
}
},
// Clear all completed todo items, destroying their models.
clearCompleted: function () {
_.invoke(app.todos.completed(), "destroy");
return false;
},
toggleAllComplete: function () {
const completed = this.allCheckbox.checked;
app.todos.each(function (todo) {
todo.save({
completed: completed,
});
});
},
});
})(jQuery);

View file

@ -0,0 +1,120 @@
/* eslint no-unused-vars: 0 */
/* eslint no-undef: 0 */
var app = app || {};
(function ($) {
"use strict";
// Todo Item View
// --------------
// The DOM element for a todo item...
app.TodoView = Backbone.View.extend({
// ... is a list tag.
tagName: "li",
// Cache the template function for a single item.
template: _.template($("#item-template").html()),
// The DOM events specific to an item.
events: {
"click .toggle": "toggleCompleted",
"dblclick label": "edit",
"click .destroy": "clear",
"keypress .edit": "updateOnEnter",
"keydown .edit": "revertOnEscape",
"blur .edit": "close",
},
// The TodoView listens for changes to its model, re-rendering. Since
// there's a one-to-one correspondence between a **Todo** and a
// **TodoView** in this app, we set a direct reference on the model for
// convenience.
initialize: function () {
this.listenTo(this.model, "change", this.render);
this.listenTo(this.model, "destroy", this.remove);
this.listenTo(this.model, "visible", this.toggleVisible);
},
// Re-render the titles of the todo item.
render: function () {
// Backbone LocalStorage is adding `id` attribute instantly after
// creating a model. This causes our TodoView to render twice. Once
// after creating a model and once on `id` change. We want to
// filter out the second redundant render, which is caused by this
// `id` change. It's known Backbone LocalStorage bug, therefore
// we've to create a workaround.
// https://github.com/tastejs/todomvc/issues/469
if (this.model.changed.id !== undefined)
return null;
this.$el.html(this.template(this.model.toJSON()));
this.$el.toggleClass("completed", this.model.get("completed"));
this.toggleVisible();
this.$input = this.$(".edit");
return this;
},
toggleVisible: function () {
this.$el.toggleClass("hidden", this.isHidden());
},
isHidden: function () {
return this.model.get("completed") ? app.TodoFilter === "active" : app.TodoFilter === "completed";
},
// Toggle the `"completed"` state of the model.
toggleCompleted: function () {
this.model.toggle();
},
// Switch this view into `"editing"` mode, displaying the input field.
edit: function () {
this.$el.addClass("editing");
const temp = this.$input.val();
this.$input.trigger("focus").val("").val(temp);
this.$input.focus();
},
// Close the `"editing"` mode, saving changes to the todo.
close: function () {
const value = this.$input.val();
const trimmedValue = value.trim();
// We don't want to handle blur events from an item that is no
// longer being edited. Relying on the CSS class here has the
// benefit of us not having to maintain state in the DOM and the
// JavaScript logic.
if (!this.$el.hasClass("editing"))
return;
if (trimmedValue)
this.model.save({ title: trimmedValue });
else
this.clear();
this.$el.removeClass("editing");
},
// If you hit `enter`, we're through editing the item.
updateOnEnter: function (e) {
if (e.which === ENTER_KEY)
this.close();
},
// If you're pressing `escape` we revert your change by simply leaving
// the `editing` state.
revertOnEscape: function (e) {
if (e.which === ESC_KEY) {
this.$el.removeClass("editing");
// Also reset the hidden input back to the original value.
this.$input.val(this.model.get("title"));
}
},
// Remove the item, destroy the model from *localStorage* and delete its view.
clear: function () {
this.model.destroy();
},
});
})(jQuery);