For practice and to explore my vanilla JavaScript (no-library) weaknesses, yesterday I made a Todo app, similar to what you see floating around for AngularJS blog demos.
How can I optimize this?
// VanillaJS Todo App with full CRUD
(function crudApp() {
"use strict";
// Look ma... no library!
var itemData = {};
var getSelector = function(targSelector) {
for (var i = 0; i < document.querySelectorAll(targSelector).length; i++) {
return document.querySelectorAll(targSelector)[i];
}
}
var selectors = {
'textInput': '[data-app-todo-input]',
'addItem': '[data-app-add-item]',
'outputZone': '[data-app-list-output]',
'deleteAllItems': '[data-app-delete-all]',
'deleteItem': 'data-app-delete-item', // These are used on dynamic click events, so they don't get backets
'editItem': 'data-app-edit-item'
};
var methods = {
'hasAttribute': function(target, attribute) {
return target.getAttribute(attribute);
},
'addItem': function(item, properties) {
var normalizedItem = item.toLowerCase();
if (!itemData[item] && properties) {
itemData[item] = properties;
} else if (!itemData[item] && !properties) {
itemData[item] = true;
}
// No overwrites for duplicate items
else if (itemData[item] === true) {
alert('Duplicate entry prevented');
}
getSelector(selectors.textInput).value = '';
methods.render(itemData);
},
'deleteAllItems': function() {
itemData = {};
methods.render(itemData);
},
'deleteItem': function(itemName) {
if (itemData[itemName]) {
delete itemData[itemName];
methods.render(itemData);
methods.clearVal(selectors.textInput);
}
},
'readData': function(dataset, property) {
if (property) return dataset[property];
else return dataset;
},
'editItem': function(event, attr) {
var itemName = event.target.getAttribute(attr);
if (itemData[itemName]) {
var newItemName = prompt('New todo item?');
if (newItemName) {
methods.addItem(newItemName);
methods.deleteItem(itemName); // Reversed from natural order for UX (no item disappear just from pressing edit)
methods.render(itemData);
}
}
},
'render': function(dataset) {
methods.clearHtml(selectors.outputZone);
var property;
var currentData = methods.readData(dataset);
for (property in currentData) {
if (currentData.hasOwnProperty(property)) {
getSelector(selectors.outputZone).innerHTML += methods.issueTemplate(property, 'li');
}
}
},
'clearHtml': function(target) {
getSelector(target).innerHTML = '';
},
'clearVal': function(target){
getSelector(target).value = '';
},
'issueTemplate': function(data, elm) {
return '<' + elm + ' data-app-item="' + data + '">' + data + '<button ' + selectors.deleteItem + '="' + data + '">Completed</button><button ' + selectors.editItem + '="' + data + '">Edit</button></' + elm + '>';
}
};
// This should handle both the edit and delete functionality and also click event on dynamically added elms (both come from same elm type so I use the attribute value to decide which function to implement)
getSelector(selectors.outputZone).onclick = function(event) {
if (methods.hasAttribute(event.target, 'data-app-delete-item')) {
var deleteItemAction = new methods.deleteItem(event.target.getAttribute('data-app-delete-item'));
} else if (methods.hasAttribute(event.target, 'data-app-edit-item')) {
var editItemAction = new methods.editItem(event, 'data-app-edit-item');
}
}
// Bind DOM events directly to functionality
getSelector(selectors.deleteAllItems).onclick = function() {
var deleteAllItemsAction = new methods.deleteAllItems();
methods.render(itemData);
};
getSelector(selectors.addItem).onclick = function() {
var addItemAction = new methods.addItem(getSelector(selectors.textInput).value);
};
getSelector('['+selectors.deleteItem+']').onclick = function() {
var deleteItemAction = new methods.deleteItem(getSelector(selectors.textInput).value);
methods.render(itemData);
};
})();
And this is the Jade markup:
#app
.user-entry
input(type="text", placeholder="Enter an item to do", data-app-todo-input)
input(type="submit", value="Add item", data-app-add-item)
input(type="button", value="Delete all items", data-app-delete-all)
input(type="button", value="Delete specific item", data-app-delete-item)
.app-output
h1 Pure javascript todo tracker
ul(data-app-list-output)
Here is the working demo (please forgive no style, I am only focused on the scripting at this moment).
** Based off of IE9+ requirements please **