• Design
  • Shotty
  • Blog
  • Reading
  • Photos
Menu

Jacob Ruiz

Product Designer
  • Design
  • Shotty
  • Blog
  • Reading
  • Photos

Improving the render method in TodoMVC

June 5, 2018

Today's exercise was to improve the render method in TodoMVC. The problem with the render method was that it did more than just render the view. It also stored data to local storage. Here's a look at the method:

render: function () {
  var todos = this.getFilteredTodos();
  $('#todo-list').html(this.todoTemplate(todos));
  $('#main').toggle(todos.length > 0);
  $('#toggle-all').prop('checked', this.getActiveTodos().length === 0);
  this.renderFooter();
  $('#new-todo').focus();
  util.store('todos-jquery', this.todos);
}

We can add comments to group these lines by functionality:

render: function () {
  // get data
  var todos = this.getFilteredTodos();

  // update the view
  $('#todo-list').html(this.todoTemplate(todos));
  $('#main').toggle(todos.length > 0);
  $('#toggle-all').prop('checked', this.getActiveTodos().length === 0);
  this.renderFooter();
  $('#new-todo').focus();
  
  // save data
  util.store('todos-jquery', this.todos);
}

So to clean up the render method, we can move the call to util.store into its own method simply named save.

save: function() {
  util.store('todos-jquery', this.todos);
}

Then, everywhere where we call render in the app, we can follow it with a call to save. The intention here is to make for more readable and honest code.

Here's a look at the commit that shows the difference between the old app.js file and the new one: See commit

The full code is below.

/*global jQuery, Handlebars, Router */
jQuery(function ($) {
    'use strict';

    Handlebars.registerHelper('eq', function (a, b, options) {
        return a === b ? options.fn(this) : options.inverse(this);
    });

    var ENTER_KEY = 13;
    var ESCAPE_KEY = 27;

    var util = {
        uuid: function () {
            /*jshint bitwise:false */
            var i, random;
            var uuid = '';

            for (i = 0; i < 32; i++) {
                random = Math.random() * 16 | 0;
                if (i === 8 || i === 12 || i === 16 || i === 20) {
                    uuid += '-';
                }
                uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16);
            }

            return uuid;
        },
        pluralize: function (count, word) {
            return count === 1 ? word : word + 's';
        },
        store: function (namespace, data) {
            if (arguments.length > 1) {
                return localStorage.setItem(namespace, JSON.stringify(data));
            } else {
                var store = localStorage.getItem(namespace);
                return (store && JSON.parse(store)) || [];
            }
        }
    };

    var App = {
        init: function () {
            this.todos = util.store('todos-jquery');
            this.todoTemplate = Handlebars.compile($('#todo-template').html());
            this.footerTemplate = Handlebars.compile($('#footer-template').html());
            this.bindEvents();

            new Router({
                '/:filter': function (filter) {
                    this.filter = filter;
                    this.render();
                }.bind(this)
            }).init('/all');
        },
        bindEvents: function () {
            $('#new-todo').on('keyup', this.create.bind(this));
            $('#toggle-all').on('change', this.toggleAll.bind(this));
            $('#footer').on('click', '#clear-completed', this.destroyCompleted.bind(this));
            $('#todo-list')
                .on('change', '.toggle', this.toggle.bind(this))
                .on('dblclick', 'label', this.edit.bind(this))
                .on('keyup', '.edit', this.editKeyup.bind(this))
                .on('focusout', '.edit', this.update.bind(this))
                .on('click', '.destroy', this.destroy.bind(this));
        },
        render: function () {
            // get data
            var todos = this.getFilteredTodos();

            // update the view
            $('#todo-list').html(this.todoTemplate(todos));
            $('#main').toggle(todos.length > 0);
            $('#toggle-all').prop('checked', this.getActiveTodos().length === 0);
            this.renderFooter();
            $('#new-todo').focus();
        },
        save: function() {
            util.store('todos-jquery', this.todos);
        },
        renderFooter: function () {
            var todoCount = this.todos.length;
            var activeTodoCount = this.getActiveTodos().length;
            var template = this.footerTemplate({
                activeTodoCount: activeTodoCount,
                activeTodoWord: util.pluralize(activeTodoCount, 'item'),
                completedTodos: todoCount - activeTodoCount,
                filter: this.filter
            });

            $('#footer').toggle(todoCount > 0).html(template);
        },
        toggleAll: function (e) {
            var isChecked = $(e.target).prop('checked');

            this.todos.forEach(function (todo) {
                todo.completed = isChecked;
            });

            this.render();
            this.save();
        },
        getActiveTodos: function () {
            return this.todos.filter(function (todo) {
                return !todo.completed;
            });
        },
        getCompletedTodos: function () {
            return this.todos.filter(function (todo) {
                return todo.completed;
            });
        },
        getFilteredTodos: function () {
            if (this.filter === 'active') {
                return this.getActiveTodos();
            }

            if (this.filter === 'completed') {
                return this.getCompletedTodos();
            }

            return this.todos;
        },
        destroyCompleted: function () {
            this.todos = this.getActiveTodos();
            this.filter = 'all';
            this.render();
            this.save();
        },
        // accepts an element from inside the `.item` div and
        // returns the corresponding index in the `todos` array
        indexFromEl: function (el) {
            var id = $(el).closest('li').data('id');
            var todos = this.todos;
            var i = todos.length;

            while (i--) {
                if (todos[i].id === id) {
                    return i;
                }
            }
        },
        create: function (e) {
            var $input = $(e.target);
            var val = $input.val().trim();

            if (e.which !== ENTER_KEY || !val) {
                return;
            }

            this.todos.push({
                id: util.uuid(),
                title: val,
                completed: false
            });

            $input.val('');

            this.render();
            this.save();
        },
        toggle: function (e) {
            var i = this.indexFromEl(e.target);
            this.todos[i].completed = !this.todos[i].completed;
            this.render();
            this.save();
        },
        edit: function (e) {
            var $input = $(e.target).closest('li').addClass('editing').find('.edit');
            $input.val($input.val()).focus();
        },
        editKeyup: function (e) {
            if (e.which === ENTER_KEY) {
                e.target.blur();
            }

            if (e.which === ESCAPE_KEY) {
                $(e.target).data('abort', true).blur();
            }
        },
        update: function (e) {
            var el = e.target;
            var $el = $(el);
            var val = $el.val().trim();

            if (!val) {
                this.destroy(e);
                return;
            }

            if ($el.data('abort')) {
                $el.data('abort', false);
            } else {
                this.todos[this.indexFromEl(el)].title = val;
            }

            this.render();
            this.save();
        },
        destroy: function (e) {
            this.todos.splice(this.indexFromEl(e.target), 1);
            this.render();
            this.save();
        }
    };

    App.init();
});
Source: https://github.com/gordonmzhu/beasts/issue...
In Javascript, Code Tags mastering javascript fundamentals
← "Designing Fluid Interfaces" - A Great Talk From WWDC 2018How To Add An Animated GIF to a Still Image →
shotty-skinny2x.jpg

Shotty - Faster Access To Your Screenshots on Mac

Shotty is an award-winning Mac app I created to give you instant access to all your recent screenshots, right from the menu bar. You can even add annotations on-the-fly. Stop wasting time digging through Finder for your screenshots. I promise it’ll change your workflow forever (just read the App Store reviews!).



Most popular

information-architecture

Information Architecture: The Most Important Part of Design You're Probably Overlooking

Follow @JacobRuizDesign