diff --git a/src/app.js b/src/app.js
index bea3f58..e17fa0f 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,17 +1,17 @@
-require('todomvc-app-css/index.css')
+import 'todomvc-app-css/index.css'
-var View = require('./view')
-var helpers = require('./helpers')
-var Controller = require('./controller')
-var Model = require('./model')
-var Store = require('./store')
-var Template = require('./template')
+import View from './view'
+import {log} from './helpers'
+import Controller from './controller'
+import Model from './model'
+import Store from './store'
+import Template from './template'
/**
-* Sets up a brand new Todo list.
-*
-* @param {string} name The name of your new to do list.
-*/
+ * Sets up a brand new Todo list.
+ *
+ * @param {string} name The name of your new to do list.
+ */
function Todo(name) {
this.storage = new Store(name)
this.model = new Model(this.storage)
@@ -20,8 +20,8 @@ function Todo(name) {
this.controller = new Controller(this.model, this.view)
}
-module.exports.onLoad = function onLoad() {
- var todo = new Todo('todos-vanillajs')
+export function onLoad() { // eslint-disable-line import/prefer-default-export
+ const todo = new Todo('todos-vanillajs')
todo.controller.setView(document.location.hash)
- helpers.log('view set')
+ log('view set')
}
diff --git a/src/bootstrap.js b/src/bootstrap.js
index 4572f23..5b32a1b 100644
--- a/src/bootstrap.js
+++ b/src/bootstrap.js
@@ -1,6 +1,6 @@
/* eslint no-console:0 */
-var app = require('./app')
-var helpers = require('./helpers')
+import {onLoad} from './app'
+import {$on} from './helpers'
// this is only relevant when using `hot` mode with webpack
// special thanks to Eric Clemmons: https://github.com/ericclemmons/webpack-hot-server-example
@@ -11,7 +11,7 @@ if (module.hot) {
})
if (reloading) {
console.log('🔁 HMR Reloading.')
- app.onLoad()
+ onLoad()
} else {
console.info('✅ HMR Enabled.')
bootstrap()
@@ -22,6 +22,6 @@ if (module.hot) {
}
function bootstrap() {
- helpers.$on(window, 'load', app.onLoad)
- helpers.$on(window, 'hashchange', app.onLoad)
+ $on(window, 'load', onLoad)
+ $on(window, 'hashchange', onLoad)
}
diff --git a/src/controller.js b/src/controller.js
index 292e640..cb7a70c 100644
--- a/src/controller.js
+++ b/src/controller.js
@@ -1,4 +1,4 @@
-module.exports = Controller
+export default Controller
/**
* Takes a model and view and acts as the controller between them
@@ -110,7 +110,7 @@ Controller.prototype.addItem = function(title) {
Controller.prototype.editItem = function(id) {
var that = this
that.model.read(id, function(data) {
- that.view.render('editItem', {id: id, title: data[0].title})
+ that.view.render('editItem', {id, title: data[0].title})
})
}
@@ -120,8 +120,8 @@ Controller.prototype.editItem = function(id) {
Controller.prototype.editItemSave = function(id, title) {
var that = this
if (title.trim()) {
- that.model.update(id, {title: title}, function() {
- that.view.render('editItemDone', {id: id, title: title})
+ that.model.update(id, {title}, function() {
+ that.view.render('editItemDone', {id, title})
})
} else {
that.removeItem(id)
@@ -134,7 +134,7 @@ Controller.prototype.editItemSave = function(id, title) {
Controller.prototype.editItemCancel = function(id) {
var that = this
that.model.read(id, function(data) {
- that.view.render('editItemDone', {id: id, title: data[0].title})
+ that.view.render('editItemDone', {id, title: data[0].title})
})
}
@@ -179,10 +179,10 @@ Controller.prototype.removeCompletedItems = function() {
*/
Controller.prototype.toggleComplete = function(id, completed, silent) {
var that = this
- that.model.update(id, {completed: completed}, function() {
+ that.model.update(id, {completed}, function() {
that.view.render('elementComplete', {
- id: id,
- completed: completed
+ id,
+ completed,
})
})
diff --git a/src/controller.test.js b/src/controller.test.js
index ba67dc9..b349559 100644
--- a/src/controller.test.js
+++ b/src/controller.test.js
@@ -1,4 +1,4 @@
-var Controller = require('./controller')
+import Controller from './controller'
describe('controller', () => {
it('exists', () => {
diff --git a/src/helpers.js b/src/helpers.js
index b8eb212..3830387 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -1,4 +1,4 @@
-module.exports = {qs, qsa, log, $on, $delegate, $parent, remove, leftPad}
+export {qs, qsa, log, $on, $delegate, $parent, remove, leftPad}
// Get element(s) by CSS selector:
function qs(selector, scope) {
@@ -9,9 +9,9 @@ function qsa(selector, scope) {
return (scope || document).querySelectorAll(selector)
}
-function log() {
+function log(...args) {
if (window.console && window.console.log) {
- window.console.log.apply(window.console, arguments) // eslint-disable-line
+ window.console.log(...args)
}
}
@@ -23,6 +23,10 @@ function $on(target, type, callback, useCapture) {
// Attach a handler to event for all elements that match the selector,
// now or in the future, based on a root element
function $delegate(target, selector, type, handler) {
+ // https://developer.mozilla.org/en-US/docs/Web/Events/blur
+ var useCapture = type === 'blur' || type === 'focus'
+ $on(target, type, dispatchEvent, useCapture)
+
function dispatchEvent(event) {
var targetElement = event.target
var potentialElements = qsa(selector, target)
@@ -32,18 +36,13 @@ function $delegate(target, selector, type, handler) {
handler.call(targetElement, event)
}
}
-
- // https://developer.mozilla.org/en-US/docs/Web/Events/blur
- var useCapture = type === 'blur' || type === 'focus'
-
- $on(target, type, dispatchEvent, useCapture)
}
// Find the element's parent with the given tag name:
// $parent(qs('a'), 'div');
function $parent(element, tagName) {
if (!element.parentNode) {
- return
+ return undefined
}
if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) {
return element.parentNode
diff --git a/src/model.js b/src/model.js
index 19ba010..06fa58a 100644
--- a/src/model.js
+++ b/src/model.js
@@ -1,4 +1,4 @@
-module.exports = Model
+export default Model
/**
* Creates a new Model instance and hooks up the storage.
@@ -30,20 +30,19 @@ Model.prototype.create = function(title, callback) {
}
/**
-* Finds and returns a model in storage. If no query is given it'll simply
-* return everything. If you pass in a string or number it'll look that up as
-* the ID of the model to find. Lastly, you can pass it an object to match
-* against.
-*
-* @param {string|number|object} [query] A query to match models against
-* @param {function} [callback] The callback to fire after the model is found
-*
-* @example
-* model.read(1, func); // Will find the model with an ID of 1
-* model.read('1'); // Same as above
-* //Below will find a model with foo equalling bar and hello equalling world.
-* model.read({ foo: 'bar', hello: 'world' });
-*/
+ * Finds and returns a model in storage. If no query is given it'll simply
+ * return everything. If you pass in a string or number it'll look that up as
+ * the ID of the model to find. Lastly, you can pass it an object to match against.
+ *
+ * @param {string|number|object} [query] A query to match models against
+ * @param {function} [callback] The callback to fire after the model is found
+ *
+ * @example
+ * model.read(1, func); // Will find the model with an ID of 1
+ * model.read('1'); // Same as above
+ * //Below will find a model with foo equalling bar and hello equalling world.
+ * model.read({ foo: 'bar', hello: 'world' });
+ */
Model.prototype.read = function(query, callback) {
var queryType = typeof query
callback = callback || function() {
@@ -58,6 +57,7 @@ Model.prototype.read = function(query, callback) {
} else {
this.storage.find(query, callback)
}
+ return undefined
}
/**
diff --git a/src/store.js b/src/store.js
index b012cb8..0278006 100644
--- a/src/store.js
+++ b/src/store.js
@@ -1,13 +1,13 @@
-module.exports = Store
+export default Store
/**
-* Creates a new client side storage object and will create an empty
-* collection if no collection already exists.
-*
-* @param {string} name The name of our DB we want to use
-* @param {function} callback Our fake DB uses callbacks because in
-* real life you probably would be making AJAX calls
-*/
+ * Creates a new client side storage object and will create an empty
+ * collection if no collection already exists.
+ *
+ * @param {string} name The name of our DB we want to use
+ * @param {function} callback Our fake DB uses callbacks because in
+ * real life you probably would be making AJAX calls
+ */
function Store(name, callback) {
callback = callback || function() {
}
@@ -34,8 +34,8 @@ function Store(name, callback) {
*
* @example
* db.find({foo: 'bar', hello: 'world'}, function (data) {
-* // data will return any items that have foo: bar and
-* // hello: world in their properties
+* // data will return any items that have foo: bar and
+* // hello: world in their properties
* });
*/
Store.prototype.find = function(query, callback) {
@@ -85,10 +85,8 @@ Store.prototype.save = function(updateData, callback, id) {
if (id) {
for (var i = 0; i < todos.length; i++) {
if (todos[i].id === id) {
- for (var key in updateData) {
- if (updateData.hasOwnProperty(key)) {
- todos[i][key] = updateData[key]
- }
+ for (var key in updateData) { // eslint-disable-line guard-for-in
+ todos[i][key] = updateData[key]
}
break
}
@@ -117,7 +115,7 @@ Store.prototype.remove = function(id, callback) {
var todos = data.todos
for (var i = 0; i < todos.length; i++) {
- if (todos[i].id == id) { // eslint-disable-line
+ if (todos[i].id === id) {
todos.splice(i, 1)
break
}
diff --git a/src/template.js b/src/template.js
index cf482d1..60c4f6f 100644
--- a/src/template.js
+++ b/src/template.js
@@ -1,4 +1,4 @@
-module.exports = Template
+export default Template
var htmlEscapes = {
'&': '&',
@@ -17,9 +17,11 @@ var reUnescapedHtml = /[&<>"'`]/g
var reHasUnescapedHtml = new RegExp(reUnescapedHtml.source)
var escape = function(string) {
- return (string && reHasUnescapedHtml.test(string)) ?
- string.replace(reUnescapedHtml, escapeHtmlChar) :
- string
+ if (string && reHasUnescapedHtml.test(string)) {
+ return string.replace(reUnescapedHtml, escapeHtmlChar)
+ } else {
+ return string
+ }
}
/**
@@ -28,32 +30,34 @@ var escape = function(string) {
* @constructor
*/
function Template() {
- this.defaultTemplate = '
' +
- '
' +
- '' +
- '' +
- '' +
- '
' +
- '
'
+ this.defaultTemplate = `
+
+
+
+
+
+
+
+ `
}
/**
-* Creates an
HTML string and returns it for placement in your app.
-*
-* NOTE: In real life you should be using a templating engine such as Mustache
-* or Handlebars, however, this is a vanilla JS example.
-*
-* @param {object} data The object containing keys you want to find in the
-* template to replace.
-* @returns {string} HTML String of an
HTML string and returns it for placement in your app.
+ *
+ * NOTE: In real life you should be using a templating engine such as Mustache
+ * or Handlebars, however, this is a vanilla JS example.
+ *
+ * @param {object} data The object containing keys you want to find in the
+ * template to replace.
+ * @returns {string} HTML String of an
element
+ *
+ * @example
+ * view.show({
+ * id: 1,
+ * title: "Hello World",
+ * completed: 0,
+ * });
+ */
Template.prototype.show = function(data) {
var i, l
var view = ''
@@ -80,11 +84,11 @@ Template.prototype.show = function(data) {
}
/**
-* Displays a counter of how many to dos are left to complete
-*
-* @param {number} activeTodos The number of active todos.
-* @returns {string} String containing the count
-*/
+ * Displays a counter of how many to dos are left to complete
+ *
+ * @param {number} activeTodos The number of active todos.
+ * @returns {string} String containing the count
+ */
Template.prototype.itemCounter = function(activeTodos) {
var plural = activeTodos === 1 ? '' : 's'
@@ -92,11 +96,11 @@ Template.prototype.itemCounter = function(activeTodos) {
}
/**
-* Updates the text within the "Clear completed" button
-*
-* @param {[type]} completedTodos The number of completed todos.
-* @returns {string} String containing the count
-*/
+ * Updates the text within the "Clear completed" button
+ *
+ * @param {[type]} completedTodos The number of completed todos.
+ * @returns {string} String containing the count
+ */
Template.prototype.clearCompletedButton = function(completedTodos) {
if (completedTodos > 0) {
return 'Clear completed'
diff --git a/src/view.js b/src/view.js
index 722d4fd..315ed5c 100644
--- a/src/view.js
+++ b/src/view.js
@@ -1,57 +1,184 @@
-/* eslint no-invalid-this: 0 */
-
-var helpers = require('./helpers')
-var qs = helpers.qs
-var qsa = helpers.qsa
-var $on = helpers.$on
-var $parent = helpers.$parent
-var $delegate = helpers.$delegate
-
-module.exports = View
+/* eslint no-invalid-this: 0, complexity:[2, 9] */
+import {qs, qsa, $on, $parent, $delegate} from './helpers'
/**
-* View that abstracts away the browser's DOM completely.
-* It has two simple entry points:
-*
-* - bind(eventName, handler)
-* Takes a todo application event and registers the handler
-* - render(command, parameterObject)
-* Renders the given command with the options
-*/
-function View(template) {
- this.template = template
+ * View that abstracts away the browser's DOM completely.
+ * It has two simple entry points:
+ *
+ * - bind(eventName, handler)
+ * Takes a todo application event and registers the handler
+ * - render(command, parameterObject)
+ * Renders the given command with the options
+ */
+export default class View {
+ constructor(template) {
+ this.template = template
- this.ENTER_KEY = 13
- this.ESCAPE_KEY = 27
+ this.ENTER_KEY = 13
+ this.ESCAPE_KEY = 27
- this.$todoList = qs('.todo-list')
- this.$todoItemCounter = qs('.todo-count')
- this.$clearCompleted = qs('.clear-completed')
- this.$main = qs('.main')
- this.$footer = qs('.footer')
- this.$toggleAll = qs('.toggle-all')
- this.$newTodo = qs('.new-todo')
-}
+ this.$todoList = qs('.todo-list')
+ this.$todoItemCounter = qs('.todo-count')
+ this.$clearCompleted = qs('.clear-completed')
+ this.$main = qs('.main')
+ this.$footer = qs('.footer')
+ this.$toggleAll = qs('.toggle-all')
+ this.$newTodo = qs('.new-todo')
+ }
-View.prototype._removeItem = function(id) {
- var elem = qs('[data-id="' + id + '"]')
+ _removeItem(id) {
+ var elem = qs('[data-id="' + id + '"]')
- if (elem) {
- this.$todoList.removeChild(elem)
+ if (elem) {
+ this.$todoList.removeChild(elem)
+ }
+ }
+
+ _clearCompletedButton(completedCount, visible) {
+ this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount)
+ this.$clearCompleted.style.display = visible ? 'block' : 'none'
+ }
+
+ _editItemDone(id, title) {
+ var listItem = qs('[data-id="' + id + '"]')
+
+ if (!listItem) {
+ return
+ }
+
+ var input = qs('input.edit', listItem)
+ listItem.removeChild(input)
+
+ listItem.className = listItem.className.replace('editing', '')
+
+ qsa('label', listItem).forEach(function(label) {
+ label.textContent = title
+ })
+ }
+
+ render(viewCmd, parameter) {
+ var that = this
+ var viewCommands = {
+ showEntries: function() {
+ that.$todoList.innerHTML = that.template.show(parameter)
+ },
+ removeItem: function() {
+ that._removeItem(parameter)
+ },
+ updateElementCount: function() {
+ that.$todoItemCounter.innerHTML = that.template.itemCounter(parameter)
+ },
+ clearCompletedButton: function() {
+ that._clearCompletedButton(parameter.completed, parameter.visible)
+ },
+ contentBlockVisibility: function() {
+ that.$main.style.display = that.$footer.style.display = parameter.visible ? 'block' : 'none'
+ },
+ toggleAll: function() {
+ that.$toggleAll.checked = parameter.checked
+ },
+ setFilter: function() {
+ _setFilter(parameter)
+ },
+ clearNewTodo: function() {
+ that.$newTodo.value = ''
+ },
+ elementComplete: function() {
+ _elementComplete(parameter.id, parameter.completed)
+ },
+ editItem: function() {
+ _editItem(parameter.id, parameter.title)
+ },
+ editItemDone: function() {
+ that._editItemDone(parameter.id, parameter.title)
+ }
+ }
+
+ viewCommands[viewCmd]()
+ }
+
+ _bindItemEditDone(handler) {
+ var that = this
+ $delegate(that.$todoList, 'li .edit', 'blur', function() {
+ if (!this.dataset.iscanceled) {
+ handler({
+ id: _itemId(this),
+ title: this.value
+ })
+ }
+ })
+
+ $delegate(that.$todoList, 'li .edit', 'keypress', function(event) {
+ if (event.keyCode === that.ENTER_KEY) {
+ // Remove the cursor from the input when you hit enter just like if it
+ // were a real form
+ this.blur()
+ }
+ })
+ }
+
+ _bindItemEditCancel(handler) {
+ var that = this
+ $delegate(that.$todoList, 'li .edit', 'keyup', function(event) {
+ if (event.keyCode === that.ESCAPE_KEY) {
+ this.dataset.iscanceled = true
+ this.blur()
+
+ handler({id: _itemId(this)})
+ }
+ })
+ }
+
+ bind(event, handler) {
+ var that = this
+ if (event === 'newTodo') {
+ $on(that.$newTodo, 'change', function() {
+ handler(that.$newTodo.value)
+ })
+
+ } else if (event === 'removeCompleted') {
+ $on(that.$clearCompleted, 'click', function() {
+ handler()
+ })
+
+ } else if (event === 'toggleAll') {
+ $on(that.$toggleAll, 'click', function() {
+ handler({completed: this.checked})
+ })
+
+ } else if (event === 'itemEdit') {
+ $delegate(that.$todoList, 'li label', 'dblclick', function() {
+ handler({id: _itemId(this)})
+ })
+
+ } else if (event === 'itemRemove') {
+ $delegate(that.$todoList, '.destroy', 'click', function() {
+ handler({id: _itemId(this)})
+ })
+
+ } else if (event === 'itemToggle') {
+ $delegate(that.$todoList, '.toggle', 'click', function() {
+ handler({
+ id: _itemId(this),
+ completed: this.checked
+ })
+ })
+
+ } else if (event === 'itemEditDone') {
+ that._bindItemEditDone(handler)
+
+ } else if (event === 'itemEditCancel') {
+ that._bindItemEditCancel(handler)
+ }
}
}
-View.prototype._clearCompletedButton = function(completedCount, visible) {
- this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount)
- this.$clearCompleted.style.display = visible ? 'block' : 'none'
-}
-
-View.prototype._setFilter = function(currentPage) {
+function _setFilter(currentPage) {
qs('.filters .selected').className = ''
qs('.filters [href="#/' + currentPage + '"]').className = 'selected'
}
-View.prototype._elementComplete = function(id, completed) {
+function _elementComplete(id, completed) {
var listItem = qs('[data-id="' + id + '"]')
if (!listItem) {
@@ -64,7 +191,7 @@ View.prototype._elementComplete = function(id, completed) {
qs('input', listItem).checked = completed
}
-View.prototype._editItem = function(id, title) {
+function _editItem(id, title) {
var listItem = qs('[data-id="' + id + '"]')
if (!listItem) {
@@ -81,140 +208,7 @@ View.prototype._editItem = function(id, title) {
input.value = title
}
-View.prototype._editItemDone = function(id, title) {
- var listItem = qs('[data-id="' + id + '"]')
-
- if (!listItem) {
- return
- }
-
- var input = qs('input.edit', listItem)
- listItem.removeChild(input)
-
- listItem.className = listItem.className.replace('editing', '')
-
- qsa('label', listItem).forEach(function(label) {
- label.textContent = title
- })
-}
-
-View.prototype.render = function(viewCmd, parameter) {
- var that = this
- var viewCommands = {
- showEntries: function() {
- that.$todoList.innerHTML = that.template.show(parameter)
- },
- removeItem: function() {
- that._removeItem(parameter)
- },
- updateElementCount: function() {
- that.$todoItemCounter.innerHTML = that.template.itemCounter(parameter)
- },
- clearCompletedButton: function() {
- that._clearCompletedButton(parameter.completed, parameter.visible)
- },
- contentBlockVisibility: function() {
- that.$main.style.display = that.$footer.style.display = parameter.visible ? 'block' : 'none'
- },
- toggleAll: function() {
- that.$toggleAll.checked = parameter.checked
- },
- setFilter: function() {
- that._setFilter(parameter)
- },
- clearNewTodo: function() {
- that.$newTodo.value = ''
- },
- elementComplete: function() {
- that._elementComplete(parameter.id, parameter.completed)
- },
- editItem: function() {
- that._editItem(parameter.id, parameter.title)
- },
- editItemDone: function() {
- that._editItemDone(parameter.id, parameter.title)
- }
- }
-
- viewCommands[viewCmd]()
-}
-
-View.prototype._itemId = function(element) {
+function _itemId(element) {
var li = $parent(element, 'li')
return parseInt(li.dataset.id, 10)
}
-
-View.prototype._bindItemEditDone = function(handler) {
- var that = this
- $delegate(that.$todoList, 'li .edit', 'blur', function() {
- if (!this.dataset.iscanceled) {
- handler({
- id: that._itemId(this),
- title: this.value
- })
- }
- })
-
- $delegate(that.$todoList, 'li .edit', 'keypress', function(event) {
- if (event.keyCode === that.ENTER_KEY) {
- // Remove the cursor from the input when you hit enter just like if it
- // were a real form
- this.blur()
- }
- })
-}
-
-View.prototype._bindItemEditCancel = function(handler) {
- var that = this
- $delegate(that.$todoList, 'li .edit', 'keyup', function(event) {
- if (event.keyCode === that.ESCAPE_KEY) {
- this.dataset.iscanceled = true
- this.blur()
-
- handler({id: that._itemId(this)})
- }
- })
-}
-
-View.prototype.bind = function(event, handler) { // eslint-disable-line
- var that = this
- if (event === 'newTodo') {
- $on(that.$newTodo, 'change', function() {
- handler(that.$newTodo.value)
- })
-
- } else if (event === 'removeCompleted') {
- $on(that.$clearCompleted, 'click', function() {
- handler()
- })
-
- } else if (event === 'toggleAll') {
- $on(that.$toggleAll, 'click', function() {
- handler({completed: this.checked})
- })
-
- } else if (event === 'itemEdit') {
- $delegate(that.$todoList, 'li label', 'dblclick', function() {
- handler({id: that._itemId(this)})
- })
-
- } else if (event === 'itemRemove') {
- $delegate(that.$todoList, '.destroy', 'click', function() {
- handler({id: that._itemId(this)})
- })
-
- } else if (event === 'itemToggle') {
- $delegate(that.$todoList, '.toggle', 'click', function() {
- handler({
- id: that._itemId(this),
- completed: this.checked
- })
- })
-
- } else if (event === 'itemEditDone') {
- that._bindItemEditDone(handler)
-
- } else if (event === 'itemEditCancel') {
- that._bindItemEditCancel(handler)
- }
-}