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