From db6919f60be0dedb82929cd4be5d272ba51255bd Mon Sep 17 00:00:00 2001 From: Dallon Feldner Date: Thu, 4 Oct 2012 11:04:28 -0700 Subject: [PATCH] Added quick-type editing --- lib/resources/collection/dashboard/data.html | 4 +- lib/resources/collection/dashboard/js/data.js | 222 +++----------- .../dashboard/js/lib/knockout-util.js | 142 --------- .../dashboard/js/util/key-constants.js | 146 +++++++++ .../dashboard/js/util/knockout-util.js | 276 ++++++++++++++++++ lib/resources/collection/index.js | 3 +- 6 files changed, 470 insertions(+), 323 deletions(-) delete mode 100644 lib/resources/collection/dashboard/js/lib/knockout-util.js create mode 100644 lib/resources/collection/dashboard/js/util/key-constants.js create mode 100644 lib/resources/collection/dashboard/js/util/knockout-util.js diff --git a/lib/resources/collection/dashboard/data.html b/lib/resources/collection/dashboard/data.html index 1575665..4e445be 100644 --- a/lib/resources/collection/dashboard/data.html +++ b/lib/resources/collection/dashboard/data.html @@ -10,10 +10,10 @@
- + - + diff --git a/lib/resources/collection/dashboard/js/data.js b/lib/resources/collection/dashboard/js/data.js index 8337308..8900f35 100644 --- a/lib/resources/collection/dashboard/js/data.js +++ b/lib/resources/collection/dashboard/js/data.js @@ -1,174 +1,4 @@ -(function defineBindings() { - - ko.bindingHandlers.scrollX = { - init: function(element, valueAccessor) { - var val = valueAccessor(); - - if (typeof val === 'function') { - $(element).scroll(function() { - val($(element).scrollLeft()); - }); - } - }, - update: function(element, valueAccessor) { - var val = ko.utils.unwrapObservable(valueAccessor()); - if ($(element).scrollLeft() !== val) { - $(element).scrollLeft(val); - } - } - }; - - ko.bindingHandlers.scrollY = { - init: function(element, valueAccessor) { - var val = valueAccessor(); - - if (typeof val === 'function') { - $(element).scroll(function() { - val($(element).scrollTop()); - }); - } - }, - update: function(element, valueAccessor) { - var val = ko.utils.unwrapObservable(valueAccessor()); - if ($(element).scrollTop() !== val) { - $(element).scrollTop(val); - } - } - }; - - ko.bindingHandlers.screenDimensions = { - init: function(element, valueAccessor, allBindingsAccessor) { - var val = valueAccessor() - , allBindings = allBindingsAccessor(); - - var calc = function() { - val(calculateScreenDimensions(element)); - }; - calc(); - $(document).ready(calc); - $(window).scroll(calc).resize(calc); - - if (allBindings.reflow && allBindings.reflow.subscribe) { - allBindings.reflow.subscribe(function() { - setTimeout(calc, 1); - }); - } - } - }; - - function calculateScreenDimensions(element) { - var $element = $(element) - , $window = $(window) - , top = $element.offset().top - $window.scrollTop() - , left = $element.offset().left - $window.scrollLeft() - , height = $element.height() - , width = $element.width() - , bottom = top + height - , right = left + width - , bottomRelative = $(window).height() - bottom - , rightRelative = $(window).width() - right; - - return { - top: top - , left: left - , bottom: bottom - , right: right - , height: height - , width: width - , bottomRelative: bottomRelative - , rightRelative: rightRelative - }; - } - - ko.bindingHandlers.scrollbarWidth = { - init: function(el, valueAccessor) { - var $el = $(el) - , val = valueAccessor() - , $container = $('
') - , $inner = $('
'); - - $container.css({ - position: 'relative' - , height: '100px' - , 'overflow-y': 'scroll' - }); - - $inner.css({ - position: 'absolute' - , top: 0 - , left: 0 - , bottom: 0 - , right: 0 - }); - - $container.append($inner); - $el.prepend($container); - - var containerWidth = $container.width(); - var innerWidth = $inner.width(); - - $container.remove(); - - val(containerWidth - innerWidth); - } - }; - - ko.bindingHandlers.element = { - init: function(element, valueAccessor) { - var val = valueAccessor(); - val($(element)); - } - }; - - ko.bindingHandlers.numberValue = { - init: function(element, valueAccessor, allBindingsAccessor, vm) { - var prop = valueAccessor() - , allBindings = allBindingsAccessor() - , updateMode = allBindings.valueUpdate || 'blur'; - - $(element).val(ko.utils.unwrapObservable(prop)); - - if (typeof prop === 'function') { - - if (updateMode === 'afterinput') { - var keypress = 0; //Wow this is a hack. - // The intention is to raise an event when - // a number input is scrolled, but not on a keypress. - - $(element).on('keypress', function(e) { - keypress++; - setTimeout(function() { - keypress--; - }, 1); - }); - $(element).on('input', function(e) { - if (keypress === 0) { - setNumberProp(element, prop); - } - }); - } else if (updateMode === 'input') { - $(element).on('input', function(e) { - setNumberProp(element, prop); - }); - } - - $(element).blur(function() { - setNumberProp(element, prop); - }); - - - } - }, update: function(element, valueAccessor) { - $(element).val(ko.utils.unwrapObservable(valueAccessor())); - } - }; - - function setNumberProp(element, prop) { - var num = parseFloat($(element).val()); - prop(num); - } -})(); - +/*globals SHIFT_KEYS:false, TEXT_KEYS:false, NUMBER_KEYS:false*/ (function() { var ROW_HEIGHT = 39; @@ -194,6 +24,8 @@ , propertiesLoaded: ko.observable(false) + , postbox: new ko.subscribable() + , view: { scrollY: ko.observable(0) , scrollX: ko.observable(0) @@ -319,14 +151,23 @@ }).extend({throttle: 1}); vm.inlineEdit.dismiss = function() { - vm.inlineEdit.editing(false); - vm.inlineEdit.editProp(vm.inlineEdit.editValue()); + setTimeout(function() { + vm.inlineEdit.editing(false); + vm.inlineEdit.editProp(vm.inlineEdit.editValue()); + }, 1); }; - vm.inlineEdit.start = function() { + vm.inlineEdit.start = function(newVal) { + vm.inlineEdit.editing(true); vm.inlineEdit.editProp = vm.selectedRow()[vm.selectedProp().name]; - vm.inlineEdit.editValue(vm.inlineEdit.editProp()); + if (typeof newVal === 'string' || typeof newVal === 'number') { + vm.inlineEdit.editValue(newVal); + } else { + vm.inlineEdit.editValue(vm.inlineEdit.editProp()); + vm.postbox.notifySubscribers(true, 'selectEditor'); + } + }; vm.inlineEdit.onKeyDown = function(e) { @@ -340,9 +181,12 @@ return true; }; - vm.inlineEdit.focusInput = ko.computed(function() { - return vm.inlineEdit.editing(); - }, vm.inlineEdit).extend({throttle: 100}); + vm.inlineEdit.focusInput = ko.computed({ + read: function() { + return vm.inlineEdit.editing(); + }, + write: function() {/*no op*/} + }, vm.inlineEdit).extend({throttle: 10}); function createRow(data) { var rowVm = {}; @@ -412,6 +256,7 @@ function bindKeys() { $(window).keydown(function(e) { + var val; var which = e.which; if (vm.inlineEdit.editing()) { @@ -437,6 +282,27 @@ return false; } + if (e.ctrlKey) return true; // No combos below this point + + if (vm.selectedProp().type === 'string') { + if (e.shiftKey) val = SHIFT_KEYS[e.which]; + else val = TEXT_KEYS[e.which]; + + if (val) { + vm.inlineEdit.start(val); + return false; + } + } + + if (vm.selectedProp().type === 'number' && !e.shiftKey) { + val = NUMBER_KEYS[e.which]; + + if (val) { + vm.inlineEdit.start(val); + return false; + } + } + return true; }); diff --git a/lib/resources/collection/dashboard/js/lib/knockout-util.js b/lib/resources/collection/dashboard/js/lib/knockout-util.js deleted file mode 100644 index 2860a83..0000000 --- a/lib/resources/collection/dashboard/js/lib/knockout-util.js +++ /dev/null @@ -1,142 +0,0 @@ -(function() { - -var unwrap = ko.utils.unwrapObservable; - -ko.bindingHandlers.cssNamed = { - update: function(element, valueAccessor) { - var value = ko.utils.unwrapObservable(valueAccessor()); - var lastClass = $(element).data('knockoutCssNamed'); - - $(element).removeClass(lastClass || " ").addClass(value).data('knockoutCssNamed', value); - } -}; - -ko.bindingHandlers.enter = { - init: function(element, valueAccessor, allBindings, viewModel) { - var handler = ko.utils.unwrapObservable(valueAccessor()); - $(element).keypress(function(e) { - if (e.which === 13) { - handler.call(viewModel, viewModel, e); - } - }); - } -}; - -ko.bindingHandlers.escape = { - init: function(element, valueAccessor, allBindings, viewModel) { - var handler = ko.utils.unwrapObservable(valueAccessor()); - $(element).keypress(function(e) { - if (e.which === 23) { - handler.call(viewModel, viewModel, e); - } - }); - } -}; - -ko.bindingHandlers.tooltip = { - init: function(element, valueAccessor) { - var value = ko.toJS(valueAccessor()); - if (typeof value === 'string') { - value = {title: value}; - } - - $(element).tooltip(value); - } - , update: function(element, valueAccessor) { - var value = valueAccessor(); - var title; - - if (typeof value === 'string') { - title = value; - } else { - title = unwrap(value.title); - } - - $(element).attr('data-original-title', title).tooltip('fixTitle'); - } -}; - -ko.bindingHandlers.tooltipEvent = { - init: function(element, valueAccessor) { - var emitter = unwrap(valueAccessor()); - emitter.on('show', function() { - $(element).tooltip('show'); - }); - emitter.on('hide', function() { - $(element).tooltip('hide'); - }); - } -}; - -ko.bindingHandlers.popover = { - init: function(element, valueAccessor) { - var value = ko.toJS(valueAccessor()); - $(element).popover(value); - } - , update: function(element, valueAccessor) { - $(element).attr('data-original-title', unwrap(valueAccessor().title)); - $(element).attr('data-content', unwrap(valueAccessor().content)); - } -}; - -ko.extenders.variableName = function(target) { - - target.subscribe(function(newValue) { - newValue = newValue.replace(/[^A-Za-z0-9]/g, ''); - target(newValue); - }); - - return target; - - // var result = ko.computed({ - // read: target, - // write: function(newValue) { - // var current = target(); - // newValue = newValue.replace(/[^A-Za-z0-9]/g, ''); - - // if (current !== newValue) { - // target(newValue); - // } - // } - // }); - - // result(target()); - - // return result; - -}; - -})(); - -//Copied from http://stackoverflow.com/questions/1068834/object-comparison-in-javascript -/*function objectEquals(x, y){ - var p; - for(p in y) { - if(typeof(x[p])=='undefined') {return false;} - } - - for(p in y) { - if (y[p]) { - switch(typeof(y[p])) { - case 'object': - if (!objectEquals(x[p], y[p])) { return false; } break; - case 'function': - if (typeof(x[p])=='undefined' || - (p != 'equals' && y[p].toString() != x[p].toString())) - return false; - break; - default: - if (y[p] != x[p]) { return false; } - } - } else { - if (x[p]) - return false; - } - } - - for(p in x) { - if(typeof(y[p])=='undefined') {return false;} - } - - return true; -}*/ \ No newline at end of file diff --git a/lib/resources/collection/dashboard/js/util/key-constants.js b/lib/resources/collection/dashboard/js/util/key-constants.js new file mode 100644 index 0000000..38501ed --- /dev/null +++ b/lib/resources/collection/dashboard/js/util/key-constants.js @@ -0,0 +1,146 @@ +var TEXT_KEYS = { + 32: " ", + 46: ".", + 48: "0", + 49: "1", + 50: "2", + 51: "3", + 52: "4", + 53: "5", + 54: "6", + 55: "7", + 56: "8", + 57: "9", + 65: "a", + 66: "b", + 67: "c", + 68: "d", + 69: "e", + 70: "f", + 71: "g", + 72: "h", + 73: "i", + 74: "j", + 75: "k", + 76: "l", + 77: "m", + 78: "n", + 79: "o", + 80: "p", + 81: "q", + 82: "r", + 83: "s", + 84: "t", + 85: "u", + 86: "v", + 87: "w", + 88: "x", + 89: "y", + 90: "z", + 96: "0", + 97: "1", + 98: "2", + 99: "3", + 100: "4", + 101: "5", + 102: "6", + 103: "7", + 104: "8", + 105: "9", + 106: "*", + 107: "+", + 109: "-", + 110: ".", + 111: "/", + 186: ";", + 187: "=", + 188: ",", + 189: "-", + 190: ".", + 191: "/", + 192: "`", + 219: "[", + 220: "\\", + 221: "]", + 222: "'" +}; + +var SHIFT_KEYS = { + 32: " ", + 48: ")", + 49: "!", + 50: "@", + 51: "#", + 52: "$", + 53: "%", + 54: "^", + 55: "&", + 56: "*", + 57: "(", + 65: "A", + 66: "B", + 67: "C", + 68: "D", + 69: "E", + 70: "F", + 71: "G", + 72: "H", + 73: "I", + 74: "J", + 75: "K", + 76: "L", + 77: "M", + 78: "N", + 79: "O", + 80: "P", + 81: "Q", + 82: "R", + 83: "S", + 84: "T", + 85: "U", + 86: "V", + 87: "W", + 88: "X", + 89: "Y", + 90: "Z", + 106: "*", + 107: "+", + 109: "-", + 111: "/", + 186: ":", + 187: "+", + 188: "<", + 189: "_", + 190: ">", + 191: "?", + 192: "~", + 219: "{", + 220: "|", + 221: "}", + 222: "\"" +}; + +var NUMBER_KEYS = { + 48: "0", + 49: "1", + 50: "2", + 51: "3", + 52: "4", + 53: "5", + 54: "6", + 55: "7", + 56: "8", + 57: "9", + 109: "-", + 110: ".", + 96: "0", + 97: "1", + 98: "2", + 99: "3", + 100: "4", + 101: "5", + 102: "6", + 103: "7", + 104: "8", + 105: "9" +}; \ No newline at end of file diff --git a/lib/resources/collection/dashboard/js/util/knockout-util.js b/lib/resources/collection/dashboard/js/util/knockout-util.js new file mode 100644 index 0000000..81a4fbe --- /dev/null +++ b/lib/resources/collection/dashboard/js/util/knockout-util.js @@ -0,0 +1,276 @@ +(function() { + +var unwrap = ko.utils.unwrapObservable; + +ko.bindingHandlers.cssNamed = { + update: function(element, valueAccessor) { + var value = ko.utils.unwrapObservable(valueAccessor()); + var lastClass = $(element).data('knockoutCssNamed'); + + $(element).removeClass(lastClass || " ").addClass(value).data('knockoutCssNamed', value); + } +}; + +ko.bindingHandlers.enter = { + init: function(element, valueAccessor, allBindings, viewModel) { + var handler = ko.utils.unwrapObservable(valueAccessor()); + $(element).keypress(function(e) { + if (e.which === 13) { + handler.call(viewModel, viewModel, e); + } + }); + } +}; + +ko.bindingHandlers.escape = { + init: function(element, valueAccessor, allBindings, viewModel) { + var handler = ko.utils.unwrapObservable(valueAccessor()); + $(element).keypress(function(e) { + if (e.which === 23) { + handler.call(viewModel, viewModel, e); + } + }); + } +}; + +ko.bindingHandlers.tooltip = { + init: function(element, valueAccessor) { + var value = ko.toJS(valueAccessor()); + if (typeof value === 'string') { + value = {title: value}; + } + + $(element).tooltip(value); + } + , update: function(element, valueAccessor) { + var value = valueAccessor(); + var title; + + if (typeof value === 'string') { + title = value; + } else { + title = unwrap(value.title); + } + + $(element).attr('data-original-title', title).tooltip('fixTitle'); + } +}; + +ko.bindingHandlers.tooltipEvent = { + init: function(element, valueAccessor) { + var emitter = unwrap(valueAccessor()); + emitter.on('show', function() { + $(element).tooltip('show'); + }); + emitter.on('hide', function() { + $(element).tooltip('hide'); + }); + } +}; + +ko.bindingHandlers.popover = { + init: function(element, valueAccessor) { + var value = ko.toJS(valueAccessor()); + $(element).popover(value); + } + , update: function(element, valueAccessor) { + $(element).attr('data-original-title', unwrap(valueAccessor().title)); + $(element).attr('data-content', unwrap(valueAccessor().content)); + } +}; + +ko.extenders.variableName = function(target) { + + target.subscribe(function(newValue) { + newValue = newValue.replace(/[^A-Za-z0-9]/g, ''); + target(newValue); + }); + + return target; + +}; + +ko.bindingHandlers.scrollX = { + init: function(element, valueAccessor) { + var val = valueAccessor(); + + if (typeof val === 'function') { + $(element).scroll(function() { + val($(element).scrollLeft()); + }); + } + }, + update: function(element, valueAccessor) { + var val = ko.utils.unwrapObservable(valueAccessor()); + if ($(element).scrollLeft() !== val) { + $(element).scrollLeft(val); + } + } +}; + +ko.bindingHandlers.scrollY = { + init: function(element, valueAccessor) { + var val = valueAccessor(); + + if (typeof val === 'function') { + $(element).scroll(function() { + val($(element).scrollTop()); + }); + } + }, + update: function(element, valueAccessor) { + var val = ko.utils.unwrapObservable(valueAccessor()); + if ($(element).scrollTop() !== val) { + $(element).scrollTop(val); + } + } +}; + +ko.bindingHandlers.screenDimensions = { + init: function(element, valueAccessor, allBindingsAccessor) { + var val = valueAccessor() + , allBindings = allBindingsAccessor(); + + var calc = function() { + val(calculateScreenDimensions(element)); + }; + calc(); + $(document).ready(calc); + $(window).scroll(calc).resize(calc); + + if (allBindings.reflow && allBindings.reflow.subscribe) { + allBindings.reflow.subscribe(function() { + setTimeout(calc, 1); + }); + } + } +}; + +function calculateScreenDimensions(element) { + var $element = $(element) + , $window = $(window) + , top = $element.offset().top - $window.scrollTop() + , left = $element.offset().left - $window.scrollLeft() + , height = $element.height() + , width = $element.width() + , bottom = top + height + , right = left + width + , bottomRelative = $(window).height() - bottom + , rightRelative = $(window).width() - right; + + return { + top: top + , left: left + , bottom: bottom + , right: right + , height: height + , width: width + , bottomRelative: bottomRelative + , rightRelative: rightRelative + }; +} + +ko.bindingHandlers.scrollbarWidth = { + init: function(el, valueAccessor) { + var $el = $(el) + , val = valueAccessor() + , $container = $('
') + , $inner = $('
'); + + $container.css({ + position: 'relative' + , height: '100px' + , 'overflow-y': 'scroll' + }); + + $inner.css({ + position: 'absolute' + , top: 0 + , left: 0 + , bottom: 0 + , right: 0 + }); + + $container.append($inner); + $el.prepend($container); + + var containerWidth = $container.width(); + var innerWidth = $inner.width(); + + $container.remove(); + + val(containerWidth - innerWidth); + } +}; + +ko.bindingHandlers.element = { + init: function(element, valueAccessor) { + var val = valueAccessor(); + val($(element)); + } +}; + +ko.bindingHandlers.numberValue = { + init: function(element, valueAccessor, allBindingsAccessor, vm) { + var prop = valueAccessor() + , allBindings = allBindingsAccessor() + , updateMode = allBindings.valueUpdate || 'blur'; + + $(element).val(ko.utils.unwrapObservable(prop)); + + if (typeof prop === 'function') { + + if (updateMode === 'afterkeydown') { + + $(element).on('keydown', function(e) { + var num = parseFloat($(element).val()); + if (isNaN(num)) return; + + if (e.which == 38) { // up + prop(num + 1); + return false; + } else if (e.which == 40) { // down + prop(num - 1); + return false; + } + + setTimeout(function() { + setNumberProp(element, prop); + }, 0); + }); + } + + $(element).blur(function() { + setNumberProp(element, prop); + }); + + + } + }, update: function(element, valueAccessor) { + var newVal = ko.utils.unwrapObservable(valueAccessor()); + if (!newVal || newVal.toString() !== $(element).val()) { + $(element).val(newVal); + } + } +}; + +function setNumberProp(element, prop) { + var num = parseFloat($(element).val()); + if (!isNaN(num)) prop(num); +} + +ko.bindingHandlers.select = { + init: function(element, valueAccessor, allBindingsAccessor, vm) { + var postbox = vm.postbox || allBindingsAccessor().postbox; + if (!postbox) throw new Error("viewmodel must have a postbox to use select"); + + var selectEvent = valueAccessor(); + postbox.subscribe(function() { + if (!$(element).is(':focus')) { + $(element).select(); + } + }, vm, selectEvent); + } +}; + +})(); diff --git a/lib/resources/collection/index.js b/lib/resources/collection/index.js index 7745e70..6863b88 100644 --- a/lib/resources/collection/index.js +++ b/lib/resources/collection/index.js @@ -43,7 +43,8 @@ Collection.dashboard = { '/js/lib/jquery-ui-1.8.22.custom.min.js' , '/js/lib/knockout-2.1.0.js' , '/js/lib/knockout.mapping.js' - , '/js/lib/knockout-util.js' + , '/js/util/knockout-util.js' + , '/js/util/key-constants.js' , '/js/util.js' ] };