Added quick-type editing

This commit is contained in:
Dallon Feldner
2012-10-04 11:04:28 -07:00
parent 5fe76323b3
commit db6919f60b
6 changed files with 470 additions and 323 deletions

View File

@@ -10,10 +10,10 @@
<div id="inline-editor-table-overlay" data-bind="style: {top: view.dimensions().top + 'px', left: view.dimensions().left + 'px', bottom: view.dimensions().bottomRelative + 'px', right: view.dimensions().rightRelative + 'px' }">
<div class="mini-edit" data-bind="style: {top: view.selectedCellPos().top + 'px', left: view.selectedCellPos().left + 'px'}, if: inlineEdit.editing, click: view.noOp, clickBubble: false">
<!-- ko if: selectedProp().type === 'string' -->
<input type="text" data-bind="hasfocus: vm.inlineEdit.focusInput, value: inlineEdit.editValue, valueUpdate: 'afterkeydown'" />
<input type="text" data-bind="hasfocus: vm.inlineEdit.focusInput, select: 'selectEditor', value: inlineEdit.editValue, valueUpdate: 'afterkeydown'" />
<!-- /ko -->
<!-- ko if: selectedProp().type === 'number' -->
<input type="number" data-bind="hasfocus: vm.inlineEdit.focusInput, numberValue: inlineEdit.editValue, valueUpdate: 'input'" />
<input type="text" class="number" data-bind="hasfocus: vm.inlineEdit.focusInput, select: 'selectEditor', numberValue: inlineEdit.editValue, valueUpdate: 'afterkeydown'" />
<!-- /ko -->
<!-- ko if: selectedProp().type === 'boolean' -->
<label><input type="checkbox" data-bind="hasfocus: vm.inlineEdit.focusInput, checked: inlineEdit.editValue" /><span data-bind="text: selectedProp().name"></span></label>

View File

@@ -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 = $('<div>')
, $inner = $('<div>');
$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;
});

View File

@@ -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;
}*/

View File

@@ -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"
};

View File

@@ -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 = $('<div>')
, $inner = $('<div>');
$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);
}
};
})();

View File

@@ -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'
]
};