Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Vanilla js improvements #863

Closed
wants to merge 9 commits into from

2 participants

@Daniel-Hug

3 more commits, sorry these are on the same branch. Not sure how to pluck them off into their own.

Daniel-Hug added some commits
@Daniel-Hug Daniel-Hug add scope handling to $live 89a4c60
@Daniel-Hug Daniel-Hug simple indent fix cecb892
@Daniel-Hug Daniel-Hug Simplify filters 8ad3b1a
@Daniel-Hug Daniel-Hug Add scope handling to $ & $$ 5e7a336
@Daniel-Hug Daniel-Hug disambiguate $ & $$: rename to qsa & qs
No more trying to remember which one's which & no more jQuery collisions
73b8056
@Daniel-Hug Daniel-Hug $live returns event.target: cleaner view.js 6f4928b
@Daniel-Hug Daniel-Hug Controller.js Cache this instead of using Fn#bind
improves readability and it's smaller when minified
cadd642
@Daniel-Hug Daniel-Hug Add $on --an addEventListener wrapper 6e4e8da
@Daniel-Hug Daniel-Hug Code cleanup
app.js: DRY functions
store.js: toss unneeded var, return/break early in store#find &
store#save
store.js & model.js: optional id argument in Store#save is last
store.js & view.js: item IDs are never stings anymore
a6ec8b8
@sindresorhus

Thanks :)

@Daniel-Hug Daniel-Hug deleted the Daniel-Hug:vanilla-JS-improvements branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 23, 2014
  1. @Daniel-Hug

    add scope handling to $live

    Daniel-Hug authored
Commits on Feb 24, 2014
  1. @Daniel-Hug

    simple indent fix

    Daniel-Hug authored
  2. @Daniel-Hug

    Simplify filters

    Daniel-Hug authored
  3. @Daniel-Hug
  4. @Daniel-Hug

    disambiguate $ & $$: rename to qsa & qs

    Daniel-Hug authored
    No more trying to remember which one's which & no more jQuery collisions
  5. @Daniel-Hug
  6. @Daniel-Hug

    Controller.js Cache this instead of using Fn#bind

    Daniel-Hug authored
    improves readability and it's smaller when minified
  7. @Daniel-Hug
  8. @Daniel-Hug

    Code cleanup

    Daniel-Hug authored
    app.js: DRY functions
    store.js: toss unneeded var, return/break early in store#find &
    store#save
    store.js & model.js: optional id argument in Store#save is last
    store.js & view.js: item IDs are never stings anymore
This page is out of date. Refresh to see the latest.
View
2  vanilla-examples/vanillajs/index.html
@@ -20,7 +20,7 @@
<span id="todo-count"></span>
<ul id="filters">
<li>
- <a href="#/">All</a>
+ <a href="#/" class="selected">All</a>
</li>
<li>
<a href="#/active">Active</a>
View
11 vanilla-examples/vanillajs/js/app.js
@@ -1,4 +1,4 @@
-/*global app */
+/*global app, $on */
(function () {
'use strict';
@@ -17,10 +17,9 @@
var todo = new Todo('todos-vanillajs');
- window.addEventListener('load', function () {
+ function setView() {
todo.controller.setView(document.location.hash);
- }.bind(this));
- window.addEventListener('hashchange', function () {
- todo.controller.setView(document.location.hash);
- }.bind(this));
+ }
+ $on(window, 'load', setView);
+ $on(window, 'hashchange', setView);
})();
View
160 vanilla-examples/vanillajs/js/controller.js
@@ -9,40 +9,41 @@
* @param {object} view The view instance
*/
function Controller(model, view) {
- this.model = model;
- this.view = view;
+ var that = this;
+ that.model = model;
+ that.view = view;
- this.view.bind('newTodo', function (title) {
- this.addItem(title);
- }.bind(this));
+ that.view.bind('newTodo', function (title) {
+ that.addItem(title);
+ });
- this.view.bind('itemEdit', function (item) {
- this.editItem(item.id);
- }.bind(this));
+ that.view.bind('itemEdit', function (item) {
+ that.editItem(item.id);
+ });
- this.view.bind('itemEditDone', function (item) {
- this.editItemSave(item.id, item.title);
- }.bind(this));
+ that.view.bind('itemEditDone', function (item) {
+ that.editItemSave(item.id, item.title);
+ });
- this.view.bind('itemEditCancel', function (item) {
- this.editItemCancel(item.id);
- }.bind(this));
+ that.view.bind('itemEditCancel', function (item) {
+ that.editItemCancel(item.id);
+ });
- this.view.bind('itemRemove', function (item) {
- this.removeItem(item.id);
- }.bind(this));
+ that.view.bind('itemRemove', function (item) {
+ that.removeItem(item.id);
+ });
- this.view.bind('itemToggle', function (item) {
- this.toggleComplete(item.id, item.completed);
- }.bind(this));
+ that.view.bind('itemToggle', function (item) {
+ that.toggleComplete(item.id, item.completed);
+ });
- this.view.bind('removeCompleted', function () {
- this.removeCompletedItems();
- }.bind(this));
+ that.view.bind('removeCompleted', function () {
+ that.removeCompletedItems();
+ });
- this.view.bind('toggleAll', function (status) {
- this.toggleAll(status.completed);
- }.bind(this));
+ that.view.bind('toggleAll', function (status) {
+ that.toggleAll(status.completed);
+ });
}
/**
@@ -61,27 +62,30 @@
* todo-list
*/
Controller.prototype.showAll = function () {
- this.model.read(function (data) {
- this.view.render('showEntries', data);
- }.bind(this));
+ var that = this;
+ that.model.read(function (data) {
+ that.view.render('showEntries', data);
+ });
};
/**
* Renders all active tasks
*/
Controller.prototype.showActive = function () {
- this.model.read({ completed: false }, function (data) {
- this.view.render('showEntries', data);
- }.bind(this));
+ var that = this;
+ that.model.read({ completed: false }, function (data) {
+ that.view.render('showEntries', data);
+ });
};
/**
* Renders all completed tasks
*/
Controller.prototype.showCompleted = function () {
- this.model.read({ completed: true }, function (data) {
- this.view.render('showEntries', data);
- }.bind(this));
+ var that = this;
+ that.model.read({ completed: true }, function (data) {
+ that.view.render('showEntries', data);
+ });
};
/**
@@ -89,35 +93,39 @@
* object and it'll handle the DOM insertion and saving of the new item.
*/
Controller.prototype.addItem = function (title) {
+ var that = this;
+
if (title.trim() === '') {
return;
}
- this.model.create(title, function () {
- this.view.render('clearNewTodo');
- this._filter(true);
- }.bind(this));
+ that.model.create(title, function () {
+ that.view.render('clearNewTodo');
+ that._filter(true);
+ });
};
/*
* Triggers the item editing mode.
*/
Controller.prototype.editItem = function (id) {
- this.model.read(id, function (data) {
- this.view.render('editItem', {id: id, title: data[0].title});
- }.bind(this));
+ var that = this;
+ that.model.read(id, function (data) {
+ that.view.render('editItem', {id: id, title: data[0].title});
+ });
};
/*
* Finishes the item editing mode successfully.
*/
Controller.prototype.editItemSave = function (id, title) {
+ var that = this;
if (title.trim()) {
- this.model.update(id, {title: title}, function () {
- this.view.render('editItemDone', {id: id, title: title});
- }.bind(this));
+ that.model.update(id, {title: title}, function () {
+ that.view.render('editItemDone', {id: id, title: title});
+ });
} else {
- this.removeItem(id);
+ that.removeItem(id);
}
};
@@ -125,9 +133,10 @@
* Cancels the item editing mode.
*/
Controller.prototype.editItemCancel = function (id) {
- this.model.read(id, function (data) {
- this.view.render('editItemDone', {id: id, title: data[0].title});
- }.bind(this));
+ var that = this;
+ that.model.read(id, function (data) {
+ that.view.render('editItemDone', {id: id, title: data[0].title});
+ });
};
/**
@@ -138,24 +147,26 @@
* storage
*/
Controller.prototype.removeItem = function (id) {
- this.model.remove(id, function () {
- this.view.render('removeItem', id);
- }.bind(this));
+ var that = this;
+ that.model.remove(id, function () {
+ that.view.render('removeItem', id);
+ });
- this._filter();
+ that._filter();
};
/**
* Will remove all completed items from the DOM and storage.
*/
Controller.prototype.removeCompletedItems = function () {
- this.model.read({ completed: true }, function (data) {
+ var that = this;
+ that.model.read({ completed: true }, function (data) {
data.forEach(function (item) {
- this.removeItem(item.id);
- }.bind(this));
- }.bind(this));
+ that.removeItem(item.id);
+ });
+ });
- this._filter();
+ that._filter();
};
/**
@@ -168,15 +179,16 @@
* @param {boolean|undefined} silent Prevent re-filtering the todo items
*/
Controller.prototype.toggleComplete = function (id, completed, silent) {
- this.model.update(id, { completed: completed }, function () {
- this.view.render('elementComplete', {
+ var that = this;
+ that.model.update(id, { completed: completed }, function () {
+ that.view.render('elementComplete', {
id: id,
completed: completed
});
- }.bind(this));
+ });
if (!silent) {
- this._filter();
+ that._filter();
}
};
@@ -185,13 +197,14 @@
* Just pass in the event object.
*/
Controller.prototype.toggleAll = function (completed) {
- this.model.read({ completed: !completed }, function (data) {
+ var that = this;
+ that.model.read({ completed: !completed }, function (data) {
data.forEach(function (item) {
- this.toggleComplete(item.id, completed, true);
- }.bind(this));
- }.bind(this));
+ that.toggleComplete(item.id, completed, true);
+ });
+ });
- this._filter();
+ that._filter();
};
/**
@@ -199,16 +212,17 @@
* number of todos.
*/
Controller.prototype._updateCount = function () {
- this.model.getCount((function(todos) {
- this.view.render('updateElementCount', todos.active);
- this.view.render('clearCompletedButton', {
+ var that = this;
+ that.model.getCount(function (todos) {
+ that.view.render('updateElementCount', todos.active);
+ that.view.render('clearCompletedButton', {
completed: todos.completed,
visible: todos.completed > 0
});
- this.view.render('toggleAll', {checked: todos.completed === todos.total});
- this.view.render('contentBlockVisibility', {visible: todos.total > 0});
- }).bind(this));
+ that.view.render('toggleAll', {checked: todos.completed === todos.total});
+ that.view.render('contentBlockVisibility', {visible: todos.total > 0});
+ });
};
/**
View
25 vanilla-examples/vanillajs/js/helpers.js
@@ -2,9 +2,18 @@
(function (window) {
'use strict';
- // Cache the querySelector/All for easier and faster reuse
- window.$ = document.querySelectorAll.bind(document);
- window.$$ = document.querySelector.bind(document);
+ // Get element(s) by CSS selector:
+ window.qs = function (selector, scope) {
+ return (scope || document).querySelector(selector);
+ };
+ window.qsa = function (selector, scope) {
+ return (scope || document).querySelectorAll(selector);
+ };
+
+ // addEventListener wrapper:
+ window.$on = function (target, type, callback, useCapture) {
+ target.addEventListener(type, callback, !!useCapture);
+ };
// Register events on elements that may or may not exist yet:
// $live('div a', 'click', function (event) {});
@@ -15,11 +24,11 @@
var targetElement = event.target;
eventRegistry[event.type].forEach(function (entry) {
- var potentialElements = document.querySelectorAll(entry.selector);
+ var potentialElements = window.qsa(entry.selector);
var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0;
if (hasMatch) {
- entry.handler(event);
+ entry.handler.call(targetElement, event);
}
});
}
@@ -27,7 +36,7 @@
return function (selector, event, handler) {
if (!eventRegistry[event]) {
eventRegistry[event] = [];
- document.documentElement.addEventListener(event, dispatchEvent, true);
+ window.$on(document.documentElement, event, dispatchEvent, true);
}
eventRegistry[event].push({
@@ -38,7 +47,7 @@
}());
// Find the element's parent with the given tag name:
- // $parent($$('a'), 'div');
+ // $parent(qs('a'), 'div');
window.$parent = function (element, tagName) {
if (!element.parentNode) {
return;
@@ -50,6 +59,6 @@
};
// Allow for looping on nodes by chaining:
- // $('.foo').forEach(function () {})
+ // qsa('.foo').forEach(function () {})
NodeList.prototype.forEach = Array.prototype.forEach;
})(window);
View
2  vanilla-examples/vanillajs/js/model.js
@@ -68,7 +68,7 @@
* @param {function} callback The callback to fire when the update is complete.
*/
Model.prototype.update = function (id, data, callback) {
- this.storage.save(id, data, callback);
+ this.storage.save(data, callback, id);
};
/**
View
37 vanilla-examples/vanillajs/js/store.js
@@ -11,22 +11,19 @@
* real life you probably would be making AJAX calls
*/
function Store(name, callback) {
- var data;
- var dbName;
-
callback = callback || function () {};
- dbName = this._dbName = name;
+ this._dbName = name;
- if (!localStorage[dbName]) {
- data = {
+ if (!localStorage[name]) {
+ var data = {
todos: []
};
- localStorage[dbName] = JSON.stringify(data);
+ localStorage[name] = JSON.stringify(data);
}
- callback.call(this, JSON.parse(localStorage[dbName]));
+ callback.call(this, JSON.parse(localStorage[name]));
}
/**
@@ -50,13 +47,12 @@
var todos = JSON.parse(localStorage[this._dbName]).todos;
callback.call(this, todos.filter(function (todo) {
- var match = true;
for (var q in query) {
if (query[q] !== todo[q]) {
- match = false;
+ return false;
}
}
- return match;
+ return true;
}));
};
@@ -74,33 +70,30 @@
* Will save the given data to the DB. If no item exists it will create a new
* item, otherwise it'll simply update an existing item's properties
*
- * @param {number} id An optional param to enter an ID of an item to update
- * @param {object} data The data to save back into the DB
+ * @param {object} updateData The data to save back into the DB
* @param {function} callback The callback to fire after saving
+ * @param {number} id An optional param to enter an ID of an item to update
*/
- Store.prototype.save = function (id, updateData, callback) {
+ Store.prototype.save = function (updateData, callback, id) {
var data = JSON.parse(localStorage[this._dbName]);
var todos = data.todos;
callback = callback || function () {};
// If an ID was actually given, find the item and update each property
- if (typeof id !== 'object') {
+ if (id) {
for (var i = 0; i < todos.length; i++) {
- if (todos[i].id == id) {
- for (var x in updateData) {
- todos[i][x] = updateData[x];
+ if (todos[i].id === id) {
+ for (var key in updateData) {
+ todos[i][key] = updateData[key];
}
+ break;
}
}
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
} else {
- callback = updateData;
-
- updateData = id;
-
// Generate an ID
updateData.id = new Date().getTime();
View
207 vanilla-examples/vanillajs/js/view.js
@@ -1,4 +1,4 @@
-/*global $, $$, $parent, $live */
+/*global qs, qsa, $on, $parent, $live */
(function (window) {
'use strict';
@@ -18,17 +18,17 @@
this.ENTER_KEY = 13;
this.ESCAPE_KEY = 27;
- this.$todoList = $$('#todo-list');
- this.$todoItemCounter = $$('#todo-count');
- this.$clearCompleted = $$('#clear-completed');
- this.$main = $$('#main');
- this.$footer = $$('#footer');
- this.$toggleAll = $$('#toggle-all');
- this.$newTodo = $$('#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 = $$('[data-id="' + id + '"]');
+ var elem = qs('[data-id="' + id + '"]');
if (elem) {
this.$todoList.removeChild(elem);
@@ -41,19 +41,12 @@
};
View.prototype._setFilter = function (currentPage) {
- // Remove all other selected states. We loop through all of them in case the
- // UI gets in a funky state with two selected.
- $('#filters .selected').forEach(function (item) {
- item.className = '';
- });
-
- $('#filters [href="#/' + currentPage + '"]').forEach(function (item) {
- item.className = 'selected';
- });
+ qs('#filters .selected').className = '';
+ qs('#filters [href="#/' + currentPage + '"]').className = 'selected';
};
View.prototype._elementComplete = function (id, completed) {
- var listItem = $$('[data-id="' + id + '"]');
+ var listItem = qs('[data-id="' + id + '"]');
if (!listItem) {
return;
@@ -62,11 +55,11 @@
listItem.className = completed ? 'completed' : '';
// In case it was toggled from an event and not by clicking the checkbox
- listItem.querySelector('input').checked = completed;
+ qs('input', listItem).checked = completed;
};
View.prototype._editItem = function (id, title) {
- var listItem = $$('[data-id="' + id + '"]');
+ var listItem = qs('[data-id="' + id + '"]');
if (!listItem) {
return;
@@ -83,18 +76,18 @@
};
View.prototype._editItemDone = function (id, title) {
- var listItem = $$('[data-id="' + id + '"]');
+ var listItem = qs('[data-id="' + id + '"]');
if (!listItem) {
return;
}
- var input = listItem.querySelector('input.edit');
+ var input = qs('input.edit', listItem);
listItem.removeChild(input);
listItem.className = listItem.className.replace('editing', '');
- listItem.querySelectorAll('label').forEach(function (label) {
+ qsa('label', listItem).forEach(function (label) {
label.textContent = title;
});
};
@@ -102,135 +95,121 @@
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);
- }
- };
+ 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._itemIdForEvent = function (e) {
- var element = e.target;
+ View.prototype._itemId = function (element) {
var li = $parent(element, 'li');
- var id = li.dataset.id;
-
- return id;
+ return parseInt(li.dataset.id, 10);
};
View.prototype._bindItemEditDone = function (handler) {
- $live('#todo-list li .edit', 'blur', function (e) {
- var input = e.target;
- var id = this._itemIdForEvent(e);
-
- if (!input.dataset.iscanceled) {
+ var that = this;
+ $live('#todo-list li .edit', 'blur', function () {
+ if (!this.dataset.iscanceled) {
handler({
- id: id,
- title: input.value
+ id: that._itemId(this),
+ title: this.value
});
}
- }.bind(this));
+ });
- $live('#todo-list li .edit', 'keypress', function (e) {
- var input = e.target;
- if (e.keyCode === this.ENTER_KEY) {
+ $live('#todo-list 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
- input.blur();
+ this.blur();
}
- }.bind(this));
+ });
};
View.prototype._bindItemEditCancel = function (handler) {
- $live('#todo-list li .edit', 'keyup', function (e) {
- var input = e.target;
- var id = this._itemIdForEvent(e);
-
- if (e.keyCode === this.ESCAPE_KEY) {
-
- input.dataset.iscanceled = true;
- input.blur();
+ var that = this;
+ $live('#todo-list li .edit', 'keyup', function (event) {
+ if (event.keyCode === that.ESCAPE_KEY) {
+ this.dataset.iscanceled = true;
+ this.blur();
- handler({id: id});
+ handler({id: that._itemId(this)});
}
- }.bind(this));
+ });
};
View.prototype.bind = function (event, handler) {
+ var that = this;
if (event === 'newTodo') {
- this.$newTodo.addEventListener('change', function () {
- handler(this.$newTodo.value);
- }.bind(this));
+ $on(that.$newTodo, 'change', function () {
+ handler(that.$newTodo.value);
+ });
} else if (event === 'removeCompleted') {
- this.$clearCompleted.addEventListener('click', function () {
+ $on(that.$clearCompleted, 'click', function () {
handler();
- }.bind(this));
+ });
} else if (event === 'toggleAll') {
- this.$toggleAll.addEventListener('click', function (e) {
- var input = e.target;
-
- handler({completed: input.checked});
- }.bind(this));
+ $on(that.$toggleAll, 'click', function () {
+ handler({completed: this.checked});
+ });
} else if (event === 'itemEdit') {
- $live('#todo-list li label', 'dblclick', function (e) {
- var id = this._itemIdForEvent(e);
-
- handler({id: id});
- }.bind(this));
+ $live('#todo-list li label', 'dblclick', function () {
+ handler({id: that._itemId(this)});
+ });
} else if (event === 'itemRemove') {
- $live('#todo-list .destroy', 'click', function (e) {
- var id = this._itemIdForEvent(e);
-
- handler({id: id});
- }.bind(this));
+ $live('#todo-list .destroy', 'click', function () {
+ handler({id: that._itemId(this)});
+ });
} else if (event === 'itemToggle') {
- $live('#todo-list .toggle', 'click', function (e) {
- var input = e.target;
- var id = this._itemIdForEvent(e);
-
- handler({id: id, completed: input.checked});
- }.bind(this));
+ $live('#todo-list .toggle', 'click', function () {
+ handler({
+ id: that._itemId(this),
+ completed: this.checked
+ });
+ });
} else if (event === 'itemEditDone') {
- this._bindItemEditDone(handler);
+ that._bindItemEditDone(handler);
} else if (event === 'itemEditCancel') {
- this._bindItemEditCancel(handler);
+ that._bindItemEditCancel(handler);
}
};
Something went wrong with that request. Please try again.