X-Git-Url: https://git.ktnx.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Flsl.js;h=bb9778e6e73b354cea06b1e516dac8134c03239c;hb=4fbeac38e712fc7f9c8f77f60eefe62a8a0af13c;hp=a21cb3ba1b53514ec4b635a8c70b08cb5b37e73f;hpb=f1ab8554042dbe7c02a7e53b2a3391378b887308;p=lsl.git diff --git a/public/javascripts/lsl.js b/public/javascripts/lsl.js index a21cb3b..bb9778e 100644 --- a/public/javascripts/lsl.js +++ b/public/javascripts/lsl.js @@ -1,33 +1,60 @@ "use strict"; (function(){ +var uri_base, environment, devel_env; // filled on page load from an HTML attribute +var start_time = Date.now(); + var ui_icon_class_re = new RegExp('\\bui-icon-\\S+\\b'); -var uri_base; // filled on page load from an HTML attribute +var uri_id_re = new RegExp('/(\\d+)$'); var lists_version = -1; var lists = []; var selected_list; +var last_item_action, last_item_action_stamp, item_action_streak = 0; + +function debug(...args) { + if (devel_env) + console.debug.apply(console, args); +} +function uri_id(uri) { + var m = uri.match(uri_id_re); + return m ? m[1] : null; +} function add_list_item(data) { - var item = $('
  • ').addClass('list-item-row').data('item', data); + var item = $('
  • ').addClass('list-item-row').data({ + 'lsl-uri': data.uri, + 'lsl-version': data.version, + 'lsl-description': data.description, + 'lsl-done': data.done}); var cb = $(''); if (data.done) cb.prop('checked', true); item.append(cb); item.append($('').text(data.description || '')); item.append($('').text('…')); - $('#list-items').append(item).addClass('have-list-items'); + $('#list-items').append(item); + $('#list-contents').addClass('have-list-items'); } function got_lists_version(new_version) { if (new_version != lists_version) window.setTimeout(load_lists); } +function got_list_version(new_version) { + if (new_version != selected_list.data('lsl-version')) + window.setTimeout( + () => load_list_items(selected_list.data('lsl-uri'), selected_list)); +} function load_list_items(uri, target) { $.get(uri) .done(item_data => { - target.data('items', item_data); - if (selected_list) - selected_list.removeClass('selected'); + target.data('lsl-items', item_data.items); + target.data('lsl-version', item_data.version); - var item_list = $('#list-items').empty().removeClass('have-list-items'); + // TODO: merge new items into existing ones + // keep track of the last existing item, and append new one + // after it, prepending if there is no last existing item + // the newly prepended/inserted item becomes the last existing + $('#list-contents').removeClass('have-list-items'); + var item_list = $('#list-items').empty(); $.each(item_data.items, (i,item) => { add_list_item(item); @@ -37,55 +64,192 @@ function load_list_items(uri, target) { }); } function select_list(new_selected_list) { - if (new_selected_list == selected_list) + if (new_selected_list && selected_list + && new_selected_list.length && selected_list.length + && new_selected_list.get(0) == selected_list.get(0) + ) return; - var id_data = new_selected_list.data('id'); + if (new_selected_list && !new_selected_list.length) + new_selected_list = null; + + $('#page').toggleClass('have-lists', !!new_selected_list); + + if (selected_list) + selected_list.removeClass('selected'); + + if (new_selected_list) { + load_list_items(new_selected_list.data('lsl-uri'), new_selected_list); + new_selected_list.addClass('selected'); + $('#selected-list-name').text(new_selected_list.data('lsl-name')); + } - load_list_items(id_data.uri, new_selected_list); selected_list = new_selected_list; - selected_list.addClass('selected'); - $('#selected-list-name').text(id_data.name); +} +function edit_list() { + var d = $('
    ') + .append( + $('
    ') + .append( + $('').text('List name'), + $('') + .on('keypress', (ev) => { + if (13 == ev.keyCode) { + save_list(d); + return false; + } + + return true; + }) + .val(selected_list.data('lsl-name')) + ) + ); + + d.dialog({ + dialogClass: 'edit-list-dialog', + autoOpen: true, + modal: true, + title: 'Edit list', + width: 'min(calc(100% - 2em), 20em)', + close: (ev) => { + $(ev.target).dialog('destroy'); + }, + buttons: [ + { + class: 'btn-delete', + icon: 'ui-icon-trash', + click: () => { + delete_list(d); + }, + }, + { + icon: 'ui-icon-disk', + text: 'OK', + click: () => { + save_list(d); + }, + }, + ], + }); +} +function delete_list(dlg) { + // TODO: store all data including list items in a "Deleted" + // pop-up with an "Undo" link that can be used to restore the list + var lists_ver = lists_version; + $.ajax( selected_list.data('lsl-uri'), + { type: 'DELETE' } + ) + .done((d)=>{ + lists_version = lists_ver + 1; + var deleted_list = selected_list; + + var new_selected = deleted_list.next(); + if (new_selected.length) { + select_list(new_selected) + } + else { + new_selected = deleted_list.prev(); + if (new_selected.length) { + select_list(new_selected); + } + else { + $('#page').removeClass('have-lists'); + } + } + + deleted_list.remove(); + dlg.dialog('destroy'); + got_lists_version(d.lists_version); + }); +} +function save_list(dlg) { + var lists_ver = lists_version; + var new_name = dlg.find('input[type="text"]').val(); + + $.ajax( selected_list.data('lsl-uri'), + { type: 'PUT', + data: JSON.stringify({ name: new_name, + version: selected_list.data('lsl-version')}) + } + ) + .done((d)=>{ + lists_version = lists_ver + 1; + selected_list.find('.list-name').text(new_name); + selected_list.data('lsl-name', new_name); + $('#selected-list-name').text(new_name); + + dlg.dialog('destroy'); + got_lists_version(d.lists_version); + }); } function load_lists() { - $.get(uri_base + '/api/v1/list') + return $.get(uri_base + '/api/v1/list') + .always(()=>{ + var splash = $('#splash'); + if (splash.length) { + var dur = splash.css('transition-duration'); + if (dur && dur.endsWith('s')) { + dur = 1000 * parseFloat(dur.substring(0, dur.length-1)); + } + else + dur = 1000; + + var now = Date.now(); + window.setTimeout(() => { + splash.addClass('done'); + debug('scheduling splash removal in '+dur+'ms'); + window.setTimeout(()=>{ splash.remove(); }, dur); + }, + Math.max(0, 500 - (now - start_time)) + ); + } + }) .done(data => { lists_version = data.lists_version; - if (data.lists.length) { - var lists = $('#lists'); - var new_selected_list; + var lists = $('#lists'); + lists.find('>li').addClass('old'); - $.each(data.lists, (i, list) => { - var my_id = 'shopping-list-id'+i; + $.each(data.lists, (i, list) => { + var list_id = uri_id(list.uri); + var existing = lists.find('li#list-'+list_id); + if (existing.length) { + existing.data('lsl-version', list.version) + .data('lsl-name', list.name) + .removeClass('old'); + existing.find('span.list-name').text(list.name); + } + else { var list_item = $('
  • ') - .data('id', list) - .prop('id', my_id) - .text(list.name); - + .prop('id', 'list-'+list_id) + .data('lsl-uri', list.uri) + .data('lsl-version', list.version) + .data('lsl-name', list.name) + .append( + $('') + .text(list.name) + ); lists.append(list_item); + } + }); - if (!new_selected_list - ||selected_list && selected_list.uri == list.uri) - new_selected_list = list_item; - - }); - - select_list(new_selected_list); + if (!selected_list || selected_list.hasClass('old')) + select_list(lists.find('>li').eq(0)); - $('#page').addClass('have-lists'); - selected_list = new_selected_list; - } - else { - $('#page').removeClass('have-lists'); - selected_list = null; - } + lists.find('li.old').remove(); }); } function new_list_submission_done(data) { + $('input[name="list_name"]').val('').trigger('change'); + if (data.lists_version != lists_version) { - load_lists(); + load_lists() + .done((d)=>{ + var new_list = $('li#list-'+uri_id(data.uri)); + if (new_list.length) + select_list(new_list); + }); return; } @@ -99,35 +263,110 @@ function handle_new_list_submission(){ return false; } function new_list_item_submission_done(data) { - if (data.lists_version != lists_version) { - load_lists(); - return; - } - - var item_data = selected_list.data('items'); + got_lists_version(data.lists_version); + selected_list.data('lsl-version', selected_list.data('lsl-version') + 1 ); + got_list_version(data.list_version); - if (data.list_version != item_data.version + 1) { - load_list_items(current_list); - } - else { - var new_item = { - description: $('#new-list-item input[type="text"]').val().trim(), - done: $('#new-list-item input[type="checkbox"]').prop('checked'), - version: 1, - }; - item_data.items.push(new_item); - item_data.version = data.list_version; - add_list_item(new_item); - } + var new_item = { + description: $('#new-list-item input[type="text"]').val().trim(), + done: $('#new-list-item input[type="checkbox"]').prop('checked'), + version: 1, + uri: data.uri, + }; + selected_list.data('lsl-items').push(new_item); + add_list_item(new_item); $('#new-list-item input').val(''); } +function delete_list_item(dlg, li) { + $.ajax(li.data('lsl-uri'), + { type: 'DELETE' }) + .done((resp) => { + li.remove(); + dlg.dialog('destroy'); + selected_list.data('lsl-version', selected_list.data('lsl-version')+1); + got_list_version(resp.list_version); + got_lists_version(resp.lists_version); + }); +} +function save_list_item(dlg,li) { + var new_description = dlg.find('input[type="text"]').val(); + + $.ajax( li.data('lsl-uri'), + { type: 'PUT', + data: JSON.stringify({ + description: new_description, + version: li.data('lsl-version')}) + } + ) + .done((d)=>{ + selected_list.data('lsl-version', selected_list.data('lsl-version') + 1); + + li.data('lsl-description', new_description); + li.data('lsl-version', d.version); + li.find('.description').text(new_description); + + dlg.dialog('destroy'); + got_list_version(d.list_version); + got_lists_version(d.lists_version); + }); +} +function edit_list_item(li) { + var d = $('
    ') + .append( + $('
    ') + .append( + $('').text('Item name'), + $('') + .on('keypress', (ev) => { + if (13 == ev.keyCode) { + save_list_item(d, li); + return false; + } + + return true; + }) + .val(li.data('lsl-description')) + ) + ); + + d.dialog({ + dialogClass: 'edit-item-dialog', + autoOpen: true, + modal: true, + title: 'Edit item', + width: 'min(calc(100% - 2em), 20em)', + close: (ev) => { + $(ev.target).dialog('destroy'); + }, + buttons: [ + { + class: 'btn-delete', + icon: 'ui-icon-trash', + click: () => { + delete_list_item(d, li); + }, + }, + { + icon: 'ui-icon-disk', + text: 'OK', + click: () => { + save_list_item(d, li); + }, + }, + ], + }); +} +function handle_list_item_edit(ev) { + var li = $(ev.target).closest('li'); + edit_list_item(li); + return false; +} function handle_new_list_item_submission(){ var description = $('#new-list-item input[type="text"]').val().trim(); if (description.length == 0) return; - var id_data = selected_list.data('id'); - $.post(id_data.uri, + $.post(selected_list.data('lsl-uri'), JSON.stringify({ description: description, done: $('#new-list-item input[type="checkbox"]').prop('checked') @@ -135,21 +374,75 @@ function handle_new_list_item_submission(){ ) .done(new_list_item_submission_done); } +function reposition_item_multi_action() { + var box = $('#item-multi-action'); + var cnt = $('#content'); + var cnt_x = cnt.offset().left; + var cnt_w = cnt.css('width').replace('px', ''); + var box_w = box.width(); + + box.css('left', cnt_x + cnt_w / 2 - box_w / 2); +} function handle_list_item_state_changed(ev) { var item = $(ev.target).closest('li'); var cb = item.find('input[type="checkbox"]'); - var item_data = item.data('item'); - $.ajax(item_data.uri, + var action = cb.prop('checked') ? 'check' : 'clear'; + var now = new Date(); + var since_last_action = now - last_item_action_stamp; + + console.debug(action, last_item_action, now - last_item_action_stamp, item_action_streak); + if (last_item_action + && last_item_action == action + && since_last_action < 3000) + { + item_action_streak ++; + + if (item_action_streak > 2 + && ( action == 'check' && $('#list-items input[type="checkbox"]').not(':checked').length + || action == 'clear' && $('#list-items input[type="checkbox"]').filter(':checked').length + ) + ) { + var box = $('#item-multi-action') + .removeClass('checking clearing') + .addClass(action+'ing') + .addClass('in-view'); + reposition_item_multi_action(); + var old_timer = box.data('lsl-hide-timer'); + if (old_timer) + window.clearTimeout(old_timer); + + box.data('lsl-hide-timer', window.setTimeout(() => { + box.removeClass('in-view').data('lsl-hide-timer', false); + }, 3000)); + } + } + else { + item_action_streak = 1; + } + + last_item_action = action; + last_item_action_stamp = now; + + $.ajax(item.data('lsl-uri'), { type: 'PUT', data: JSON.stringify({ - version: item_data.version, + version: item.data('lsl-version'), done: cb.prop('checked')}) } - ); + ) + .done(function(resp){ + item.data('lsl-version', resp.version); + got_lists_version(resp.lists_version); + selected_list.data('lsl-version', selected_list.data('lsl-version')+1); + got_list_version(resp.list_version); + }); } $(function(){ uri_base = $('#page').attr('lsl-uri-base'); + environment = $('#page').attr('lsl-environment'); + devel_env = environment == 'development'; + $(document).ajaxStart(function(){ $(document).addClass('blocked'); window.setTimeout( @@ -182,9 +475,15 @@ $(function(){ return true; }); - $('ul#lists').on('click', 'li', ev=>{ - select_list($(ev.target)); + $('#lists') + .on('click', 'li', ev=>{ + select_list($(ev.target).closest('li')); + }); + $('#list-edit-trigger').on('click', ev => { + edit_list(); + return false; }); + $('#list-items').on('click', '>li .edit-trigger', handle_list_item_edit); $('#new-list-item button').on('click', handle_new_list_item_submission); $('#new-list-item input').on('keypress', ev => { if (13 == ev.keyCode) { @@ -195,5 +494,17 @@ $(function(){ }); $('#list-items').on('change', '.list-item-row input[type="checkbox"]', handle_list_item_state_changed); load_lists(); + $('body').on('click', '.ui-widget-overlay', (ev) => { + $(ev.target).siblings('.ui-dialog').find('.ui-dialog-content').dialog('close'); + }); + $(window).on('resize', () => { reposition_item_multi_action(); }); + $('#btn-check-all').click((ev) => { + $('#list-items input[type="checkbox"]').not(':checked').prop('checked', true).trigger('change'); + return false; + }); + $('#btn-clear-all').click((ev) => { + $('#list-items input[type="checkbox"]').filter(':checked').prop('checked', false).trigger('change'); + return false; + }); }); })();