'use strict'; class Controller { /** * Take a model & view, then act as controller between them * @param {object} model The model instance * @param {object} view The view instance */ constructor(model, view) { this.model = model; this.view = view; this.view.bind('newTodo', title => this.addItem(title)); this.view.bind('itemEdit', item => this.editItem(item.id)); this.view.bind('itemEditDone', item => this.editItemSave(item.id, item.title)); this.view.bind('itemEditCancel', item => this.editItemCancel(item.id)); this.view.bind('itemRemove', item => this.removeItem(item.id)); this.view.bind('itemToggle', item => this.toggleComplete(item.id, item.completed)); this.view.bind('removeCompleted', () => this.removeCompletedItems()); this.view.bind('toggleAll', status => this.toggleAll(status.completed)); } /** * Load & Initialize the view * @param {string} '' | 'active' | 'completed' */ setView(hash){ let route = hash.split('/')[1]; let page = route || ''; this._updateFilter(page); } /** * Event fires on load. Gets all items & displays them */ showAll(){ this.model.read(data => this.view.render('showEntries', data)); } /** * Renders all active tasks */ showActive(){ this.model.read({completed: false}, data => this.view.render('showEntries', data)); } /** * Renders all completed tasks */ showCompleted(){ this.model.read({completed: true}, data => this.view.render('showEntries', data)); } /** * An event to fire whenever you want to add an item. Simply pass in the event * object and it'll handle the DOM insertion and saving of the new item. */ addItem(title){ if (title.trim() === '') { return; } this.model.create(title, () => { this.view.render('clearNewTodo'); this._filter(true); }); } /* * Triggers the item editing mode. */ editItem(id){ this.model.read(id, data => { let title = data[0].title; this.view.render('editItem', {id, title}); }); } /* * Finishes the item editing mode successfully. */ editItemSave(id, title){ title = title.trim(); if (title.length !== 0) { this.model.update(id, {title}, () => { this.view.render('editItemDone', {id, title}); }); } else { this.removeItem(id); } } /* * Cancels the item editing mode. */ editItemCancel(id){ this.model.read(id, data => { let title = data[0].title; this.view.render('editItemDone', {id, title}); }); } /** * Find the DOM element with given ID, * Then remove it from DOM & Storage */ removeItem(id){ this.model.remove(id, () => this.view.render('removeItem', id)); this._filter(); } /** * Will remove all completed items from the DOM and storage. */ removeCompletedItems(){ this.model.read({completed: true}, data => { for (let item of data) { this.removeItem(item.id); } }); this._filter(); } /** * Give it an ID of a model and a checkbox and it will update the item * in storage based on the checkbox's state. * * @param {number} id The ID of the element to complete or uncomplete * @param {object} checkbox The checkbox to check the state of complete * or not * @param {boolean|undefined} silent Prevent re-filtering the todo items */ toggleComplete(id, completed, silent){ this.model.update(id, {completed}, () => { this.view.render('elementComplete', {id, completed}); }); if (!silent) { this._filter(); } } /** * Will toggle ALL checkboxes' on/off state and completeness of models. * Just pass in the event object. */ toggleAll(completed){ this.model.read({completed: !completed}, data => { for (let item of data) { this.toggleComplete(item.id, completed, true); } }); this._filter(); } /** * Updates the pieces of the page which change depending on the remaining * number of todos. */ _updateCount(){ this.model.getCount(todos => { const completed = todos.completed; const visible = completed > 0; const checked = completed === todos.total; this.view.render('updateElementCount', todos.active); this.view.render('clearCompletedButton', {completed, visible}); this.view.render('toggleAll', {checked}); this.view.render('contentBlockVisibility', {visible: todos.total > 0}); }); } /** * Re-filters the todo items, based on the active route. * @param {boolean|undefined} force forces a re-painting of todo items. */ _filter(force){ let active = this._activeRoute; const activeRoute = active.charAt(0).toUpperCase() + active.substr(1); // Update the elements on the page, which change with each completed todo this._updateCount(); // If the last active route isn't "All", or we're switching routes, we // re-create the todo item elements, calling: // this.show[All|Active|Completed]() if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) { this['show' + activeRoute](); } this._lastActiveRoute = activeRoute; } /** * Simply updates the filter nav's selected states */ _updateFilter(currentPage){ // Store a reference to the active route, allowing us to re-filter todo // items as they are marked complete or incomplete. this._activeRoute = currentPage; if (currentPage === '') { this._activeRoute = 'All'; } this._filter(); this.view.render('setFilter', currentPage); } }