mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Add infrastructure for managing configurable keyboard shortcuts.
This commit is contained in:
parent
7c985695b7
commit
66341e6947
@ -25,10 +25,6 @@ div.pgadmin-preference-body div.ajs-content {
|
|||||||
bottom: 0px !important;
|
bottom: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preferences_content .control-label, .preferences_content .pgadmin-controls {
|
|
||||||
min-width: 100px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pgadmin-preference-body {
|
.pgadmin-preference-body {
|
||||||
min-width: 300px !important;
|
min-width: 300px !important;
|
||||||
min-height: 400px !important;
|
min-height: 400px !important;
|
||||||
@ -40,3 +36,15 @@ div.pgadmin-preference-body div.ajs-content {
|
|||||||
min-height: 480px !important;
|
min-height: 480px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcut-label {
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preferences_content input[type="checkbox"]:focus {
|
||||||
|
outline: 5px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preferences_content .pgadmin-controls input[type="checkbox"] {
|
||||||
|
margin-left: -20px !important;
|
||||||
|
}
|
||||||
|
@ -256,6 +256,8 @@ define('pgadmin.preferences', [
|
|||||||
return 'textarea';
|
return 'textarea';
|
||||||
case 'switch':
|
case 'switch':
|
||||||
return 'switch';
|
return 'switch';
|
||||||
|
case 'keyboardshortcut':
|
||||||
|
return 'keyboardShortcut';
|
||||||
default:
|
default:
|
||||||
if (console && console.warn) {
|
if (console && console.warn) {
|
||||||
// Warning for developer only.
|
// Warning for developer only.
|
||||||
|
@ -2504,5 +2504,206 @@ define([
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var KeyCodeControlFormatter = Backform.KeyCodeControlFormatter = function() {};
|
||||||
|
|
||||||
|
_.extend(KeyCodeControlFormatter.prototype, {
|
||||||
|
fromRaw: function (rawData) {
|
||||||
|
return rawData['char'];
|
||||||
|
},
|
||||||
|
// we don't need toRaw
|
||||||
|
toRaw: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
Backform.KeyCodeControl = Backform.InputControl.extend({
|
||||||
|
defaults: _.defaults({
|
||||||
|
escapeKeyCodes: [16, 17, 18, 27], // Shift, Ctrl, Alt/Option, Escape
|
||||||
|
}, Backform.InputControl.prototype.defaults),
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'keydown input': 'onkeyDown',
|
||||||
|
'keyup input': 'preventEvent',
|
||||||
|
'focus select': 'clearInvalid',
|
||||||
|
},
|
||||||
|
|
||||||
|
formatter: KeyCodeControlFormatter,
|
||||||
|
|
||||||
|
preventEvent: function(e) {
|
||||||
|
var key_code = e.which || e.keyCode,
|
||||||
|
field = _.defaults(this.field.toJSON(), this.defaults);
|
||||||
|
|
||||||
|
if (field.escapeKeyCodes.indexOf(key_code) != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
|
||||||
|
template: _.template([
|
||||||
|
'<label class="<%=Backform.controlLabelClassName%> keyboard-shortcut-label"><%=label%></label>',
|
||||||
|
'<div class="<%=Backform.controlsClassName%>">',
|
||||||
|
' <input type="<%=type%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" oncopy="return false; oncut="return false; onpaste="return false;" maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
|
||||||
|
' <% if (helpMessage && helpMessage.length) { %>',
|
||||||
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
||||||
|
' <% } %>',
|
||||||
|
'</div>',
|
||||||
|
].join('\n')),
|
||||||
|
onkeyDown: function(e) {
|
||||||
|
var self = this,
|
||||||
|
model = this.model,
|
||||||
|
attrArr = this.field.get('name').split('.'),
|
||||||
|
name = attrArr.shift(),
|
||||||
|
changes = {},
|
||||||
|
key_code = e.which || e.keyCode,
|
||||||
|
key,
|
||||||
|
field = _.defaults(this.field.toJSON(), this.defaults);
|
||||||
|
|
||||||
|
if (field.escapeKeyCodes.indexOf(key_code) != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.model.errorModel instanceof Backbone.Model) {
|
||||||
|
this.model.errorModel.unset(name);
|
||||||
|
}
|
||||||
|
key = gettext(e.key);
|
||||||
|
if (key_code == 32) {
|
||||||
|
key = gettext('Space');
|
||||||
|
}
|
||||||
|
changes = {
|
||||||
|
'key_code': e.which || e.keyCode,
|
||||||
|
'char': key,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stopListening(this.model, 'change:' + name, this.render);
|
||||||
|
model.set(name, changes);
|
||||||
|
this.listenTo(this.model, 'change:' + name, this.render);
|
||||||
|
setTimeout(function() {
|
||||||
|
self.$el.find('input').val(key);
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
keyPathAccessor: function(obj, path) {
|
||||||
|
var res = obj;
|
||||||
|
path = path.split('.');
|
||||||
|
for (var i = 0; i < path.length; i++) {
|
||||||
|
if (_.isNull(res)) return null;
|
||||||
|
if (_.isEmpty(path[i])) continue;
|
||||||
|
if (!_.isUndefined(res[path[i]])) res = res[path[i]];
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Backform.KeyboardShortcutControl = Backform.Control.extend({
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
|
||||||
|
Backform.Control.prototype.initialize.apply(this, arguments);
|
||||||
|
|
||||||
|
var fields = this.field.get('fields');
|
||||||
|
|
||||||
|
if (fields == null || fields == undefined) {
|
||||||
|
throw new ReferenceError('"fields" not found in keyboard shortcut');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.innerModel = new Backbone.Model();
|
||||||
|
|
||||||
|
this.controls = [];
|
||||||
|
},
|
||||||
|
cleanup: function() {
|
||||||
|
|
||||||
|
this.stopListening(this.innerModel, 'change', this.onInnerModelChange);
|
||||||
|
|
||||||
|
_.each(this.controls, function(c) {
|
||||||
|
c.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.controls.length = 0;
|
||||||
|
},
|
||||||
|
template: _.template([
|
||||||
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
||||||
|
'<div class="<%=Backform.controlsClassName%>">',
|
||||||
|
'</div>',
|
||||||
|
].join('\n')),
|
||||||
|
|
||||||
|
onInnerModelChange: function() {
|
||||||
|
|
||||||
|
var name = this.field.get('name'),
|
||||||
|
val = $.extend(true, {}, this.model.get(name));
|
||||||
|
|
||||||
|
this.stopListening(this.model, 'change:' + name, this.render);
|
||||||
|
|
||||||
|
this.model.set(name,
|
||||||
|
$.extend(true, val, this.innerModel.toJSON())
|
||||||
|
);
|
||||||
|
|
||||||
|
this.listenTo(this.model, 'change:' + name, this.render);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
this.cleanup();
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
initial_value = {},
|
||||||
|
field = _.defaults(this.field.toJSON(), this.defaults),
|
||||||
|
value = self.model.get(field['name']),
|
||||||
|
innerFields = field['fields'];
|
||||||
|
|
||||||
|
this.$el.html(self.template(field)).addClass(field.name);
|
||||||
|
|
||||||
|
var $container = $(self.$el.find('.pgadmin-controls'));
|
||||||
|
|
||||||
|
_.each(innerFields, function(field) {
|
||||||
|
initial_value[field['name']] = value[field['name']];
|
||||||
|
});
|
||||||
|
|
||||||
|
self.innerModel.set(initial_value);
|
||||||
|
|
||||||
|
self.listenTo(self.innerModel, 'change', self.onInnerModelChange);
|
||||||
|
|
||||||
|
_.each(innerFields, function(fld) {
|
||||||
|
|
||||||
|
var f = new Backform.Field(
|
||||||
|
_.extend({}, {
|
||||||
|
id: fld['name'],
|
||||||
|
name: fld['name'],
|
||||||
|
control: fld['type'],
|
||||||
|
label: fld['label'],
|
||||||
|
})
|
||||||
|
),
|
||||||
|
cntr = new (f.get('control')) ({
|
||||||
|
field: f,
|
||||||
|
model: self.innerModel,
|
||||||
|
});
|
||||||
|
|
||||||
|
cntr.render();
|
||||||
|
|
||||||
|
if(fld['type'] == 'checkbox') {
|
||||||
|
if (fld['name'] == 'alt_option') {
|
||||||
|
$container.append($('<div class="pg-el-sm-3 pg-el-xs-12"></div>').append(cntr.$el));
|
||||||
|
} else {
|
||||||
|
$container.append($('<div class="pg-el-sm-2 pg-el-xs-12"></div>').append(cntr.$el));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$container.append($('<div class="pg-el-sm-5 pg-el-xs-12"></div>').append(cntr.$el));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will keep track of all the controls rendered at the
|
||||||
|
// moment.
|
||||||
|
self.controls.push(cntr);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return self;
|
||||||
|
},
|
||||||
|
remove: function() {
|
||||||
|
/* First do the clean up */
|
||||||
|
this.cleanup();
|
||||||
|
Backform.Control.prototype.remove.apply(this, arguments);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return Backform;
|
return Backform;
|
||||||
});
|
});
|
||||||
|
@ -13,6 +13,7 @@ module within the system.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import decimal
|
import decimal
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
import dateutil.parser as dateutil_parser
|
import dateutil.parser as dateutil_parser
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
@ -31,7 +32,7 @@ class _Preference(object):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, cid, name, label, _type, default, help_str=None, min_val=None,
|
self, cid, name, label, _type, default, help_str=None, min_val=None,
|
||||||
max_val=None, options=None, select2=None
|
max_val=None, options=None, select2=None, fields=None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
__init__
|
__init__
|
||||||
@ -54,6 +55,8 @@ class _Preference(object):
|
|||||||
:param max_val: maximum value
|
:param max_val: maximum value
|
||||||
:param options: options (Array of list objects)
|
:param options: options (Array of list objects)
|
||||||
:param select2: select2 options (object)
|
:param select2: select2 options (object)
|
||||||
|
:param fields: field schema (if preference has more than one field to
|
||||||
|
take input from user e.g. keyboardshortcut preference)
|
||||||
|
|
||||||
:returns: nothing
|
:returns: nothing
|
||||||
"""
|
"""
|
||||||
@ -67,6 +70,7 @@ class _Preference(object):
|
|||||||
self.max_val = max_val
|
self.max_val = max_val
|
||||||
self.options = options
|
self.options = options
|
||||||
self.select2 = select2
|
self.select2 = select2
|
||||||
|
self.fields = fields
|
||||||
|
|
||||||
# Look into the configuration table to find out the id of the specific
|
# Look into the configuration table to find out the id of the specific
|
||||||
# preference.
|
# preference.
|
||||||
@ -137,6 +141,12 @@ class _Preference(object):
|
|||||||
if self._type == 'text':
|
if self._type == 'text':
|
||||||
if res.value == '':
|
if res.value == '':
|
||||||
return self.default
|
return self.default
|
||||||
|
if self._type == 'keyboardshortcut':
|
||||||
|
try:
|
||||||
|
return json.loads(res.value)
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.exeception(e)
|
||||||
|
return self.default
|
||||||
|
|
||||||
return res.value
|
return res.value
|
||||||
|
|
||||||
@ -196,6 +206,14 @@ class _Preference(object):
|
|||||||
|
|
||||||
if not has_value and self.select2 and not self.select2['tags']:
|
if not has_value and self.select2 and not self.select2['tags']:
|
||||||
return False, gettext("Invalid value for an options option.")
|
return False, gettext("Invalid value for an options option.")
|
||||||
|
elif self._type == 'keyboardshortcut':
|
||||||
|
try:
|
||||||
|
value = json.dumps(value)
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.exeception(e)
|
||||||
|
return False, gettext(
|
||||||
|
"Invalid value for a keyboard shortcut option."
|
||||||
|
)
|
||||||
|
|
||||||
pref = UserPrefTable.query.filter_by(
|
pref = UserPrefTable.query.filter_by(
|
||||||
pid=self.pid
|
pid=self.pid
|
||||||
@ -231,7 +249,8 @@ class _Preference(object):
|
|||||||
'max_val': self.max_val,
|
'max_val': self.max_val,
|
||||||
'options': self.options,
|
'options': self.options,
|
||||||
'select2': self.select2,
|
'select2': self.select2,
|
||||||
'value': self.get()
|
'value': self.get(),
|
||||||
|
'fields': self.fields,
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@ -371,7 +390,7 @@ class Preferences(object):
|
|||||||
def register(
|
def register(
|
||||||
self, category, name, label, _type, default, min_val=None,
|
self, category, name, label, _type, default, min_val=None,
|
||||||
max_val=None, options=None, help_str=None, category_label=None,
|
max_val=None, options=None, help_str=None, category_label=None,
|
||||||
select2=None
|
select2=None, fields=None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
register
|
register
|
||||||
@ -393,6 +412,8 @@ class Preferences(object):
|
|||||||
:param help_str:
|
:param help_str:
|
||||||
:param category_label:
|
:param category_label:
|
||||||
:param select2: select2 control extra options
|
:param select2: select2 control extra options
|
||||||
|
:param fields: field schema (if preference has more than one field to
|
||||||
|
take input from user e.g. keyboardshortcut preference)
|
||||||
"""
|
"""
|
||||||
cat = self.__category(category, category_label)
|
cat = self.__category(category, category_label)
|
||||||
if name in cat['preferences']:
|
if name in cat['preferences']:
|
||||||
@ -402,12 +423,13 @@ class Preferences(object):
|
|||||||
assert _type is not None, "Type for a preference cannot be none!"
|
assert _type is not None, "Type for a preference cannot be none!"
|
||||||
assert _type in (
|
assert _type in (
|
||||||
'boolean', 'integer', 'numeric', 'date', 'datetime',
|
'boolean', 'integer', 'numeric', 'date', 'datetime',
|
||||||
'options', 'multiline', 'switch', 'node', 'text'
|
'options', 'multiline', 'switch', 'node', 'text',
|
||||||
|
'keyboardshortcut'
|
||||||
), "Type cannot be found in the defined list!"
|
), "Type cannot be found in the defined list!"
|
||||||
|
|
||||||
(cat['preferences'])[name] = res = _Preference(
|
(cat['preferences'])[name] = res = _Preference(
|
||||||
cat['id'], name, label, _type, default, help_str, min_val,
|
cat['id'], name, label, _type, default, help_str, min_val,
|
||||||
max_val, options, select2
|
max_val, options, select2, fields
|
||||||
)
|
)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -0,0 +1,434 @@
|
|||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// pgAdmin 4 - PostgreSQL Tools
|
||||||
|
//
|
||||||
|
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||||
|
// This software is released under the PostgreSQL Licence
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'backbone',
|
||||||
|
'pgadmin.backform',
|
||||||
|
], function ($, Backbone, Backform) {
|
||||||
|
describe('KeyboardshortcutControl', function () {
|
||||||
|
let field, innerFields, control, model, event;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
innerFields = [
|
||||||
|
{'name': 'key', 'type': 'keyCode', 'label': 'Key'},
|
||||||
|
{'name': 'alt_option', 'type': 'checkbox',
|
||||||
|
'label': 'Alt/Option'},
|
||||||
|
{'name': 'control', 'type': 'checkbox',
|
||||||
|
'label': 'Ctrl'},
|
||||||
|
{'name': 'shift', 'type': 'checkbox', 'label': 'Shift'},
|
||||||
|
];
|
||||||
|
|
||||||
|
model = new Backbone.Model({
|
||||||
|
'shortcut': {
|
||||||
|
'control': true,
|
||||||
|
'shift': false,
|
||||||
|
'alt_option': true,
|
||||||
|
'key': {
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
field = new Backform.Field({
|
||||||
|
id: 'shortcut',
|
||||||
|
name: 'shortcut',
|
||||||
|
control: 'keyboardShortcut',
|
||||||
|
label: 'Keyboard shortcut',
|
||||||
|
fields: innerFields,
|
||||||
|
});
|
||||||
|
|
||||||
|
control = new (field.get('control')) ({
|
||||||
|
field: field,
|
||||||
|
model: model,
|
||||||
|
});
|
||||||
|
|
||||||
|
control.render();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('keyboardShortcut UI setup', function () {
|
||||||
|
|
||||||
|
it('keyboard shortcut control should be rendered with inner fields', function () {
|
||||||
|
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onModelChange', function () {
|
||||||
|
beforeEach((done) => {
|
||||||
|
|
||||||
|
done();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when model "key" value changes UI and innerModel should update new "key" value', function (done) {
|
||||||
|
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||||
|
expect(control.innerModel.get('key')).toEqual({
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
});
|
||||||
|
|
||||||
|
var val = $.extend(true, {}, model.get(field.get('name')));
|
||||||
|
|
||||||
|
model.set(field.get('name'),
|
||||||
|
$.extend(true, val, {
|
||||||
|
'key': {
|
||||||
|
'key_code': 65,
|
||||||
|
'char': 'A',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
// this should change
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('A');
|
||||||
|
expect(control.innerModel.get('key')).toEqual({
|
||||||
|
'key_code': 65,
|
||||||
|
'char': 'A',
|
||||||
|
});
|
||||||
|
|
||||||
|
// below three should not change.
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||||
|
expect(control.innerModel.get('alt_option')).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||||
|
expect(control.innerModel.get('control')).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||||
|
expect(control.innerModel.get('shift')).toBeFalsy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when model "control" value changes UI and innerModel should update new "control" value', function (done) {
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||||
|
expect(control.innerModel.get('control')).toBeTruthy();
|
||||||
|
|
||||||
|
var val = $.extend(true, {}, model.get(field.get('name')));
|
||||||
|
|
||||||
|
model.set(field.get('name'),
|
||||||
|
$.extend(true, val, {
|
||||||
|
'control': false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
// this should change
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeFalsy();
|
||||||
|
expect(control.innerModel.get('control')).toBeFalsy();
|
||||||
|
|
||||||
|
// below three should not change.
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||||
|
expect(control.innerModel.get('alt_option')).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||||
|
expect(control.innerModel.get('key')).toEqual({
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||||
|
expect(control.innerModel.get('shift')).toBeFalsy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when model "shift" value changes UI and innerModel should update new "shift" value', function (done) {
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||||
|
expect(control.innerModel.get('shift')).toBeFalsy();
|
||||||
|
|
||||||
|
var val = $.extend(true, {}, model.get(field.get('name')));
|
||||||
|
|
||||||
|
model.set(field.get('name'),
|
||||||
|
$.extend(true, val, {
|
||||||
|
'shift': true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
// this should change
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeTruthy();
|
||||||
|
expect(control.innerModel.get('shift')).toBeTruthy();
|
||||||
|
|
||||||
|
// below three should not change.
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||||
|
expect(control.innerModel.get('alt_option')).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||||
|
expect(control.innerModel.get('key')).toEqual({
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||||
|
expect(control.innerModel.get('control')).toBeTruthy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when model "alt_option" value changes UI and innerModel should update new "alt_option" value', function (done) {
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||||
|
expect(control.innerModel.get('alt_option')).toBeTruthy();
|
||||||
|
|
||||||
|
var val = $.extend(true, {}, model.get(field.get('name')));
|
||||||
|
|
||||||
|
model.set(field.get('name'),
|
||||||
|
$.extend(true, val, {
|
||||||
|
'alt_option': false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
// this should change
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeFalsy();
|
||||||
|
expect(control.innerModel.get('alt_option')).toBeFalsy();
|
||||||
|
|
||||||
|
// below three should not change.
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||||
|
expect(control.innerModel.get('shift')).toBeFalsy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||||
|
expect(control.innerModel.get('key')).toEqual({
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||||
|
expect(control.innerModel.get('control')).toBeTruthy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onInnerModelChange', function () {
|
||||||
|
beforeEach((done) => {
|
||||||
|
|
||||||
|
done();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when innerModel "key" value changes UI and model should update new "key" value', function (done) {
|
||||||
|
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||||
|
expect(model.get(field.get('name'))).toEqual({
|
||||||
|
'control': true,
|
||||||
|
'shift': false,
|
||||||
|
'alt_option': true,
|
||||||
|
'key': {
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
control.innerModel.set('key',
|
||||||
|
{
|
||||||
|
'key_code': 65,
|
||||||
|
'char': 'A',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
// this should change
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('A');
|
||||||
|
expect(model.get(field.get('name'))).toEqual({
|
||||||
|
'control': true,
|
||||||
|
'shift': false,
|
||||||
|
'alt_option': true,
|
||||||
|
'key': {
|
||||||
|
'key_code': 65,
|
||||||
|
'char': 'A',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// below three should not change.
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when innerModel "control" value changes UI and model should update new "control" value', function (done) {
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||||
|
expect(model.get(field.get('name'))).toEqual({
|
||||||
|
'control': true,
|
||||||
|
'shift': false,
|
||||||
|
'alt_option': true,
|
||||||
|
'key': {
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
control.innerModel.set('control', false);
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
// this should change
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeFalsy();
|
||||||
|
expect(model.get(field.get('name'))).toEqual({
|
||||||
|
'control': false,
|
||||||
|
'shift': false,
|
||||||
|
'alt_option': true,
|
||||||
|
'key': {
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// below three should not change.
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when innerModel "shift" value changes UI and model should update new "shift" value', function (done) {
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||||
|
expect(model.get(field.get('name'))).toEqual({
|
||||||
|
'control': true,
|
||||||
|
'shift': false,
|
||||||
|
'alt_option': true,
|
||||||
|
'key': {
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
control.innerModel.set('shift', true);
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
// this should change
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeTruthy();
|
||||||
|
expect(model.get(field.get('name'))).toEqual({
|
||||||
|
'control': true,
|
||||||
|
'shift': true,
|
||||||
|
'alt_option': true,
|
||||||
|
'key': {
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// below three should not change.
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when innerModel "alt_option" value changes UI and model should update new "alt_option" value', function (done) {
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||||
|
expect(model.get(field.get('name'))).toEqual({
|
||||||
|
'control': true,
|
||||||
|
'shift': false,
|
||||||
|
'alt_option': true,
|
||||||
|
'key': {
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
control.innerModel.set('alt_option', false);
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
// this should change
|
||||||
|
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeFalsy();
|
||||||
|
expect(model.get(field.get('name'))).toEqual({
|
||||||
|
'control': true,
|
||||||
|
'shift': false,
|
||||||
|
'alt_option': false,
|
||||||
|
'key': {
|
||||||
|
'key_code': 73,
|
||||||
|
'char': 'I',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// below three should not change.
|
||||||
|
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||||
|
|
||||||
|
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||||
|
|
||||||
|
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remove keyboardShortcut control', function () {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
|
||||||
|
spyOn(control, 'cleanup').and.callThrough();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when removed it should remove all of it\' controls', function () {
|
||||||
|
|
||||||
|
control.remove();
|
||||||
|
|
||||||
|
expect(control.cleanup).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(control.controls.length).toBe(0);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
199
web/regression/javascript/backform_controls/keycode_spec.js
Normal file
199
web/regression/javascript/backform_controls/keycode_spec.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// pgAdmin 4 - PostgreSQL Tools
|
||||||
|
//
|
||||||
|
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||||
|
// This software is released under the PostgreSQL Licence
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
define([
|
||||||
|
'backbone',
|
||||||
|
'pgadmin.backform',
|
||||||
|
], function (Backbone, Backform) {
|
||||||
|
describe('keyCodeControl', function () {
|
||||||
|
let field, control, model, event;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
model = new Backbone.Model({
|
||||||
|
'key': {
|
||||||
|
'key_code': 65,
|
||||||
|
'char': 'A',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
field = new Backform.Field({
|
||||||
|
id: 'key',
|
||||||
|
name: 'key',
|
||||||
|
control: 'keyCode',
|
||||||
|
label: 'Key',
|
||||||
|
});
|
||||||
|
|
||||||
|
control = new (field.get('control')) ({
|
||||||
|
field: field,
|
||||||
|
model: model,
|
||||||
|
});
|
||||||
|
|
||||||
|
control.render();
|
||||||
|
|
||||||
|
event = {
|
||||||
|
which: -1,
|
||||||
|
keyCode: -1,
|
||||||
|
key: '',
|
||||||
|
preventDefault: jasmine.createSpy('preventDefault'),
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onkeyDown', function () {
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
|
||||||
|
spyOn(model, 'set').and.callThrough();
|
||||||
|
|
||||||
|
spyOn(control, 'onkeyDown').and.callThrough();
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when key with escapeKeyCode is pressed model should not update', function (done) {
|
||||||
|
event.which = 16;
|
||||||
|
event.keyCode = 16;
|
||||||
|
event.key = 'Shift';
|
||||||
|
|
||||||
|
control.onkeyDown(event);
|
||||||
|
|
||||||
|
expect(control.onkeyDown).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(model.set).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(model.get('key')).toEqual({
|
||||||
|
'key_code': 65,
|
||||||
|
'char': 'A',
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
expect(control.$el.find('input')[0].value).toBe('A');
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when key other than escapeKeyCode is pressed model should update', function (done) {
|
||||||
|
event.which = 66;
|
||||||
|
event.keyCode = 66;
|
||||||
|
event.key = 'B';
|
||||||
|
|
||||||
|
control.onkeyDown(event);
|
||||||
|
|
||||||
|
expect(control.onkeyDown).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(model.set).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(event.preventDefault).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(model.get('key')).toEqual({
|
||||||
|
'key_code': 66,
|
||||||
|
'char': 'B',
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
expect(control.$el.find('input')[0].value).toBe('B');
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onkeyUp', function () {
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
spyOn(control, 'preventEvent').and.callThrough();
|
||||||
|
|
||||||
|
event.stopPropagation = jasmine.createSpy('stopPropagation');
|
||||||
|
|
||||||
|
event.stopImmediatePropagation = jasmine.createSpy('stopImmediatePropagation');
|
||||||
|
|
||||||
|
done();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when key with escapeKeyCode is pressed and released event should be propagated', function (done) {
|
||||||
|
event.which = 17;
|
||||||
|
event.keyCode = 17;
|
||||||
|
event.key = 'Ctrl';
|
||||||
|
|
||||||
|
control.preventEvent(event);
|
||||||
|
|
||||||
|
expect(control.preventEvent).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(event.stopPropagation).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(event.stopImmediatePropagation).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
expect(control.$el.find('input')[0].value).toBe('A');
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when key other than escapeKeyCode is pressed and released event should not be propagated', function (done) {
|
||||||
|
event.which = 66;
|
||||||
|
event.keyCode = 66;
|
||||||
|
event.key = 'B';
|
||||||
|
|
||||||
|
control.preventEvent(event);
|
||||||
|
|
||||||
|
expect(control.preventEvent).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(event.preventDefault).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(event.stopPropagation).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(event.stopImmediatePropagation).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
expect(control.$el.find('input')[0].value).toBe('A');
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onModelChange', function () {
|
||||||
|
beforeEach((done) => {
|
||||||
|
|
||||||
|
done();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when model changes UI should update', function (done) {
|
||||||
|
|
||||||
|
expect(control.$el.find('input')[0].value).toBe('A');
|
||||||
|
|
||||||
|
model.set('key', {
|
||||||
|
'key_code': 67,
|
||||||
|
'char': 'C',
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait until UI updates.
|
||||||
|
setTimeout(function() {
|
||||||
|
expect(control.$el.find('input')[0].value).toBe('C');
|
||||||
|
done();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -12,6 +12,7 @@ module.exports = {
|
|||||||
jQuery: 'jquery',
|
jQuery: 'jquery',
|
||||||
_: 'underscore',
|
_: 'underscore',
|
||||||
'underscore.string': 'underscore.string',
|
'underscore.string': 'underscore.string',
|
||||||
|
'window.jQuery': 'jquery',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -60,6 +61,13 @@ module.exports = {
|
|||||||
'alertify': path.join(__dirname, './node_modules/alertifyjs/build/alertify'),
|
'alertify': path.join(__dirname, './node_modules/alertifyjs/build/alertify'),
|
||||||
'jquery.event.drag': path.join(__dirname, './node_modules/slickgrid/lib/jquery.event.drag-2.2'),
|
'jquery.event.drag': path.join(__dirname, './node_modules/slickgrid/lib/jquery.event.drag-2.2'),
|
||||||
'jquery.ui': path.join(__dirname, './node_modules/slickgrid/lib/jquery-ui-1.11.3'),
|
'jquery.ui': path.join(__dirname, './node_modules/slickgrid/lib/jquery-ui-1.11.3'),
|
||||||
|
'spectrum': path.join(__dirname, './node_modules/spectrum-colorpicker/spectrum'),
|
||||||
|
'bignumber': path.join(__dirname, './node_modules/bignumber.js/bignumber'),
|
||||||
|
'bootstrap.datetimepicker': path.join(__dirname, './node_modules/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min'),
|
||||||
|
'bootstrap.switch': path.join(__dirname, './node_modules/bootstrap-switch/dist/js/bootstrap-switch'),
|
||||||
|
'backbone': path.join(__dirname, './node_modules/backbone/backbone'),
|
||||||
|
'backform': path.join(__dirname, './node_modules/backform/src/backform'),
|
||||||
|
'backgrid': path.join(__dirname, './node_modules/backgrid/lib/backgrid'),
|
||||||
'sources': sourcesDir + '/js',
|
'sources': sourcesDir + '/js',
|
||||||
'translations': regressionDir + '/javascript/fake_translations',
|
'translations': regressionDir + '/javascript/fake_translations',
|
||||||
'pgadmin.browser.endpoints': regressionDir + '/javascript/fake_endpoints',
|
'pgadmin.browser.endpoints': regressionDir + '/javascript/fake_endpoints',
|
||||||
@ -70,6 +78,8 @@ module.exports = {
|
|||||||
'pgadmin': sourcesDir + '/js/pgadmin',
|
'pgadmin': sourcesDir + '/js/pgadmin',
|
||||||
'pgadmin.sqlfoldcode': sourcesDir + '/js/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
'pgadmin.sqlfoldcode': sourcesDir + '/js/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
||||||
'pgadmin.alertifyjs': sourcesDir + '/js/alertify.pgadmin.defaults',
|
'pgadmin.alertifyjs': sourcesDir + '/js/alertify.pgadmin.defaults',
|
||||||
|
'pgadmin.backgrid': sourcesDir + '/js/backgrid.pgadmin',
|
||||||
|
'pgadmin.backform': sourcesDir + '/js/backform.pgadmin',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
|
Loading…
Reference in New Issue
Block a user