Skip to content

Instantly share code, notes, and snippets.

/page.html Secret

Created June 26, 2017 23:37
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/e842ddb3ae0f8750b65fa2814ff99e54 to your computer and use it in GitHub Desktop.
Save anonymous/e842ddb3ae0f8750b65fa2814ff99e54 to your computer and use it in GitHub Desktop.
<!-- Handlebars.js template -->
<script id="todo-single-template" type="text/x-handlebars-template">
<div class="todo-list-item" data-todo-id="{{ id }}">
<input type="checkbox" {{#if done}}checked{{/if}} onclick="modules.todo.toggleDone({{ id }})" />
<p {{#if done}}class="done"{{/if}} data-todo-id="{{ id }}" class="todo-editable">{{ task }}</p>
<i class="fa fa-trash" aria-hidden="true" onclick="modules.todo.remove({{ id }})"></i>
<i class="fa fa-calendar-check-o" aria-hidden="true" onclick="modules.todo.showDatePicker($(this), {{id}})"></i>
</div>
</script>
<!-- The todo tab -->
<div class="todo-tab" align="center" id="item-todo" style="display:none;">
<div>
<input id="todo-input" autocapitalize="none" autocomplete="off" autocorrect="off" spellcheck="false">
</div>
</div>
<!-- The datepicker -->
<div class="todo-datepicker" id="todo-datepicker" style="display: none;">
<ul class="list">
<p id="todo-current-due"></p>
<li data-value="0">None</li>
<li data-value="1">4 Hours</li>
<li data-value="2">10 Hours</li>
<li data-value="3">Tomorrow</li>
<li data-value="4">3 Days</li>
<li data-value="5">Next Week</li>
</ul>
</div>
todo: {
name: 'todo', // can ignore
runtime: 2, // can ignore
view: 'item-todo', // can ignore.
init: function() {
// Call this immediately on page load. Check to see if any of the items in the todo
// list are overdue, and send a notification. (Use your own notification module).
this.ls = JSON.parse(localStorage.getItem('todo')) || [];
this.ls.forEach((obj, index) => {
if (obj.due && new Date(obj.due) < Date.now()) {
let msg = obj.task && obj.task.length > 50 ? obj.task.substring(0, 50) + "..." : obj.task;
modules.notify.msg(`Todo item #${index + 1} due: ${msg}`);
}
});
},
run: function() {
// Run this function when you want the actual todo list to load.
// I make use of Handlebars.js, but you can just use html5 templates or something else.
try {
this.sort();
let source = $('#todo-single-template').html();
let template = Handlebars.compile(source);
$.each(this.ls, function(index, value) {
var html = template(value);
$('#item-todo').append(html);
});
} catch (e) {
// might be empty list.
console.error(e);
this.ls = [];
}
this.registerHandlers();
},
registerHandlers: function() {
let clazz = this;
// Enter pressed in `todo` tab input, save as new note.
$('#todo-input').on('keyup', (e) => {
if (e.keyCode == 13) {
var text = $('#todo-input').val();
this.add(text);
$('#todo-input').val('');
}
});
// Note text clicked, make editable and savable.
$(document).on('click', '.todo-editable', function(e) {
function editHandler(e) {
if (e.type == 'focusout') {
let text = $(this).val();
let id = $(this).closest('div').attr('data-todo-id');
clazz.edit(id, text);
let p = $("<p></p>", {text: text, class: "todo-editable"});
$(this).replaceWith(p);
}
}
let input = $("<textarea>", {
val: $(this).text(),
rows: $(this).text().split('\n').length,
//keyup: editHandler,
focusout: editHandler
});
$(this).replaceWith(input);
input.select();
});
},
add: function(description) {
let last = this.ls[this.ls.length - 1];
let task = {
id: last ? last.id + 1 : 1,
task: description,
done: false,
created: new Date(),
};
this.ls.push(task);
this.save();
let source = $('#todo-single-template').html();
let template = Handlebars.compile(source);
let html = template(task);
$('#item-todo').append(html);
},
remove: function(id) {
this.ls = this.ls.filter(function(obj) {
return obj.id != id;
});
this.save();
$('div').find('[data-todo-id="' + id + '"]').remove();
},
edit: function(id, text) {
let index = this.ls.map(function(x) { return x.id; }).indexOf(Number(id));
this.ls[index].task = text;
this.ls[index].updated = new Date();
this.save();
},
toggleDone: function(id) {
let index = this.ls.map(function(x) { return x.id; }).indexOf(id);
this.ls[index].done = !this.ls[index].done;
this.ls[index].updated = new Date();
this.save();
let div = $('div').find("[data-todo-id='" + id + "']");
let p = div.children('p');
this.ls[index].done ? p.addClass('done') : p.removeClass('done');
},
formatDueDateTime: function(date) {
let now = new Date();
let delta = Math.abs(date - now) / 1000;
let days = Math.floor(delta / 86400);
delta -= days * 86400;
let hours = Math.floor(delta / 3600) % 24;
delta -= hours * 3600;
let minutes = Math.floor(delta / 60) % 60;
return `Due in ${days} days, ${hours} hours, ${minutes} minutes`;
},
showDatePicker: function(btn, id) {
let offset = btn.offset();
let index = this.ls.map(function(x) { return x.id; }).indexOf(Number(id));
$('#todo-current-due').html('');
if (this.ls[index].due) {
let date = new Date(this.ls[index].due);
$('#todo-current-due').html(this.formatDueDateTime(date));
}
$('#todo-datepicker').fadeIn().css({
left: Math.min(offset.left, $(window).innerWidth()-$('#todo-datepicker').outerWidth()) + 29,
top: (offset.top + btn.innerHeight()) - 40,
//display: 'block'
});
$(document).keyup(e => {
if (e.which == 27) {
$('#todo-datepicker').fadeOut(400);
}
});
$('#todo-datepicker').mouseleave(function() {
$('#todo-datepicker').fadeOut(400);
});
$('#todo-datepicker .list li').click(e => {
$('#todo-datepicker').fadeOut(400);
let value = Number($(e.target).attr('data-value'));
switch (value) {
case 0: // none -- clear due date.
delete this.ls[index].due;
break;
case 1: // 4 hours
this.ls[index].due = new Date(Date.now() + 1000 * 60 * 60 * 4);
break;
case 2: // 10 hours
this.ls[index].due = new Date(Date.now() + (1000 * 60 * 60 * 10));
break;
case 3: // 1 day
this.ls[index].due = new Date(Date.now() + (1000 * 60 * 60 * 24));
break;
case 4: // 3 days
this.ls[index].due = new Date(Date.now() + (1000 * 60 * 60 * 24 * 3));
break;
case 5: // 1 week
this.ls[index].due = new Date(Date.now() + (1000 * 60 * 60 * 24 * 7));
break;
default:
modules.notify.msg('Something went wrong, no date set', 5000, true)
}
this.ls[index].updated = new Date();
this.save();
});
},
sort() {
this.ls = this.ls.sort(function(a, b) { return (a > b) ? 1 : ((b > a) ? -1 : 0); });
},
save() {
localStorage.setItem('todo', JSON.stringify(this.ls));
}
}
@drtheuns
Copy link

The datepicker would apply the chosen value to any of the clicked todo items. This is because the event handler was being registered everything showDatePicker was clicked. Changed that to only register once and added the id of the currently selected note as a data attribute to the datepicker.

todo: {
        name: 'todo',
        runtime: 2,
        view: 'item-todo',
        init: function() {
            this.ls = JSON.parse(localStorage.getItem('todo')) || [];
            this.ls.forEach((obj, index) => {
                if (obj.due && new Date(obj.due) < Date.now()) {
                    let msg = obj.task && obj.task.length > 50 ? obj.task.substring(0, 50) + "..." : obj.task;
                    modules.notify.msg(`Todo item #${index + 1} due: ${msg}`);
                }
            });
        },
        run: function() {
            try {
                this.sort();

                let source = $('#todo-single-template').html();
                let template = Handlebars.compile(source);

                $.each(this.ls, function(index, value) {
                    var html = template(value);
                    $('#item-todo').append(html);
                });
            } catch (e) {
                // might be empty list.
                console.error(e);
                this.ls = [];
            }

            this.registerHandlers();
        },
        registerHandlers: function() {
            let clazz = this;

            // Enter pressed in `todo` tab input, save as new note.
            $('#todo-input').on('keyup', (e) => {
                if (e.keyCode == 13) {
                    var text = $('#todo-input').val();
                    this.add(text);
                    $('#todo-input').val('');
                }
            });

            // Note text clicked, make edit and savable.
            $(document).on('click', '.todo-editable', function(e) {
                function editHandler(e) {
                    if (e.type == 'focusout') {
                        let text = $(this).val();
                        let id = $(this).closest('div').attr('data-todo-id');
                        
                        clazz.edit(id, text);

                        let p = $("<p></p>", {text: text, class: "todo-editable"});
                        $(this).replaceWith(p);
                    }
                }

                let input = $("<textarea>", { 
                    val: $(this).text(),
                    rows: $(this).text().split('\n').length,
                    //keyup: editHandler,
                    focusout: editHandler
                });
                
                $(this).replaceWith(input);
                input.select();
            });

            // Date picker.
            $('#todo-datepicker .list li').click(e => {
                let id = $('#todo-datepicker').attr('data-id');
                let index = this.ls.findIndex(x => x.id == id);

                $('#todo-datepicker').fadeOut(400);

                let value = Number($(e.target).attr('data-value'));
                switch (value) {
                    case 0: // none -- clear due date.
                        delete this.ls[index].due;
                        break;
                    case 1: // 4 hours
                        this.ls[index].due = new Date(Date.now() + 1000 * 60 * 60 * 4);
                        break;
                    case 2: // 10 hours
                        this.ls[index].due = new Date(Date.now() + (1000 * 60 * 60 * 10));
                        break;
                    case 3: // 1 day
                        this.ls[index].due = new Date(Date.now() + (1000 * 60 * 60 * 24));
                        break;
                    case 4: // 3 days
                        this.ls[index].due = new Date(Date.now() + (1000 * 60 * 60 * 24 * 3));
                        break;
                    case 5: // 1 week
                        this.ls[index].due = new Date(Date.now() + (1000 * 60 * 60 * 24 * 7));
                        break;
                    default:
                        modules.notify.msg('Something went wrong, no date set', 5000, true)
                }

                this.ls[index].updated = new Date();
                this.save();
            });

            $('#todo-datepicker').mouseleave(function() {
                $('#todo-datepicker').fadeOut(400);
            });
        },
        add: function(description) {
            let last = this.ls[this.ls.length - 1];
            let task = {
                id: last ? last.id + 1 : 1,
                task: description,
                done: false,
                created: new Date(),
            };
            this.ls.push(task);
            this.save();

            let source = $('#todo-single-template').html();
            let template = Handlebars.compile(source);
            let html = template(task);
            $('#item-todo').append(html);
        },
        remove: function(id) {
            this.ls = this.ls.filter(function(obj) {
                return obj.id != id;
            });
            this.save();

            $('div').find('[data-todo-id="' + id + '"]').remove();
        },
        edit: function(id, text) {
            let index = this.ls.findIndex(x => x.id == id);
            this.ls[index].task = text;
            this.ls[index].updated = new Date();
            this.save();
        },
        toggleDone: function(id) {
            // Get index of todo list item with `id` in the todo list.
            let index = this.ls.findIndex(x => x.id == id);

            this.ls[index].done = !this.ls[index].done;
            this.ls[index].updated = new Date();
            this.save();

            let div = $('div').find("[data-todo-id='" + id + "']");
            let p = div.children('p');
            this.ls[index].done ? p.addClass('done') : p.removeClass('done');
        },
        formatDueDateTime: function(date) {
            let now = new Date();
            let delta = Math.abs(date - now) / 1000;
            
            let days = Math.floor(delta / 86400);
            delta -= days * 86400;

            let hours = Math.floor(delta / 3600) % 24;
            delta -= hours * 3600;

            let minutes = Math.floor(delta / 60) % 60;
            
            return `Due in ${days} days, ${hours} hours, ${minutes} minutes`;
        },
        showDatePicker: function(btn, id) {
            let offset = btn.offset();
            let index = this.ls.findIndex(x => x.id == id);

            $('#todo-current-due').html('');
            if (this.ls[index].due) {
                let date = new Date(this.ls[index].due);
                $('#todo-current-due').html(this.formatDueDateTime(date));
            }

            $('#todo-datepicker').attr('data-id', id);
            
            $('#todo-datepicker').fadeIn().css({
                left: Math.min(offset.left, $(window).innerWidth()-$('#todo-datepicker').outerWidth()) + 29,
                top: (offset.top + btn.innerHeight()) - 40,
                //display: 'block'
            });
        },
        sort() {
            this.ls = this.ls.sort(function(a, b) { return (a > b) ? 1 : ((b > a) ? -1 : 0); });
        },
        save() {
            localStorage.setItem('todo', JSON.stringify(this.ls));
        }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment