genediazjr/hapitodo

View on GitHub
static/js/app.js

Summary

Maintainability
A
0 mins
Test Coverage
'use strict';

jQuery(($) => {

    Handlebars.registerHelper('eq', function (a, b, options) {

        return a === b ? options.fn(this) : options.inverse(this);
    });

    const internals = {
        ESCAPE_KEY: 27,
        ENTER_KEY: 13,
        REST_API: '/api/v1/todo',
        crumb: null
    };

    const App = {
        init: function () {

            this.todoTemplate = Handlebars.templates.todo;
            this.menuTemplate = Handlebars.templates.menu;

            new Router({
                '/:filter': function (filter) {
                    this.todoFilter(filter);
                    this.filter = filter;
                }.bind(this)
            }).init('/all');

            $('#new-todo').on('keyup', this.todoAdd.bind(this));
            $('#toggle-all').on('change', this.todoToggleAll.bind(this));
            $('#todo-list')
                .on('change', '.done', this.todoToggle.bind(this))
                .on('dblclick', 'label', this.todoEditStart.bind(this))
                .on('focusout', '.edit', this.todoEditStop.bind(this))
                .on('keyup', '.edit', this.todoEditGoing.bind(this))
                .on('click', '.remove', this.todoDelete.bind(this));
            $('#menu').on('click', '#clear-completed', this.todoDeleteCompleted.bind(this));
        },
        todoFilter: function (filter) {

            this.restBrowse(filter, (data) => {

                $('#new-todo').focus();
                $('#todo-list').html(this.todoTemplate(data.list));
                $('#toggle-all').prop('checked', data.activeTodoCount === 0);
                $('#main').toggle(data.totalTodoCount > 0);
                $('#menu').toggle(data.totalTodoCount > 0).html(this.menuTemplate({
                    completedTodoCount: data.completedTodoCount,
                    activeTodoCount: data.activeTodoCount,
                    filter: filter
                }));
            });
        },
        todoAdd: function (e) {

            const $input = $(e.target);
            const val = $input.val().trim();

            if (e.which === internals.ENTER_KEY && val) {
                this.restCreate({ content: val }, () => {
                    this.todoFilter(this.filter);
                    $input.val('');
                });
            }
        },
        todoToggle: function (e) {

            const $input = $(e.target);
            const val = $input.prop('checked');
            const id = $input.closest('li').data('id');

            this.restUpdate({ id: id, done: val }, () => {
                this.todoFilter(this.filter);
            });
        },
        todoToggleAll: function (e) {

            const isChecked = $(e.target).prop('checked');

            this.restUpdate({ id: 'all', done: isChecked }, () => {
                this.todoFilter(this.filter);
            });
        },
        todoDelete: function (e) {

            const id = $(e.target).closest('li').data('id');

            this.restRemove(id, () => {
                this.todoFilter(this.filter);
            });
        },
        todoDeleteCompleted: function () {

            this.restRemove('completed', () => {
                if (this.filter === 'all') {
                    this.todoFilter(this.filter);
                }
                else {
                    window.location.href = '#/all';
                }
            });
        },
        todoEditStart: function (e) {

            const $input = $(e.target).closest('li').addClass('editing').find('.edit');

            $input.val($input.val()).focus();
            this.previousValue = $input.val();
        },
        todoEditGoing: function (e) {

            const input = e.target;

            if (e.which === internals.ENTER_KEY) {
                input.blur();
            }

            if (e.which === internals.ESCAPE_KEY) {
                $(input).data('abort', true).blur();
            }
        },
        todoEditStop: function (e) {

            const $input = $(e.target);
            const val = $input.val().trim();
            const id = $input.closest('li').data('id');

            if ($input.data('abort')) {
                this.todoFilter(this.filter);
            }
            else if (val) {
                if (val !== this.previousValue) {
                    this.restUpdate({ id: id, content: val }, () => {
                        this.todoFilter(this.filter);
                    });
                }
                else {
                    this.todoFilter(this.filter);
                }
            }
            else if (!val) {
                this.todoDelete(e);
            }
        },
        restBrowse: function (filter, next) {

            $.getJSON(`${internals.REST_API}/list/${filter}`).always(next);
        },
        restCreate: function (todo, next) {

            $.post(internals.REST_API, todo).always(next);
        },
        restRemove: function (id, next) {

            $.ajax(`${internals.REST_API}/${id}`, { method: 'delete' }).always(next);
        },
        restUpdate: function (todo, next) {

            $.ajax(internals.REST_API, { data: todo, method: 'put' }).always(next);
        }
    };

    $(document).ajaxSend((e, xhr) => {

        if (internals.crumb) {
            xhr.setRequestHeader('x-csrf-token', internals.crumb);
        }
    });

    $(document).ajaxSuccess((e, xhr) => {

        const csrf = xhr.getResponseHeader('x-csrf-token');

        if (csrf) {
            internals.crumb = csrf;
        }
    });

    App.init();
});