diff --git a/web/pgadmin/preferences/static/css/preferences.css b/web/pgadmin/preferences/static/css/preferences.css
index 9f8de62d1..df748bc6b 100644
--- a/web/pgadmin/preferences/static/css/preferences.css
+++ b/web/pgadmin/preferences/static/css/preferences.css
@@ -25,10 +25,6 @@ div.pgadmin-preference-body div.ajs-content {
bottom: 0px !important;
}
-.preferences_content .control-label, .preferences_content .pgadmin-controls {
- min-width: 100px !important;
-}
-
.pgadmin-preference-body {
min-width: 300px !important;
min-height: 400px !important;
@@ -40,3 +36,15 @@ div.pgadmin-preference-body div.ajs-content {
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;
+}
diff --git a/web/pgadmin/preferences/static/js/preferences.js b/web/pgadmin/preferences/static/js/preferences.js
index d025be153..7014d625d 100644
--- a/web/pgadmin/preferences/static/js/preferences.js
+++ b/web/pgadmin/preferences/static/js/preferences.js
@@ -256,6 +256,8 @@ define('pgadmin.preferences', [
return 'textarea';
case 'switch':
return 'switch';
+ case 'keyboardshortcut':
+ return 'keyboardShortcut';
default:
if (console && console.warn) {
// Warning for developer only.
diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js
index 1005482ef..2bbaa174e 100644
--- a/web/pgadmin/static/js/backform.pgadmin.js
+++ b/web/pgadmin/static/js/backform.pgadmin.js
@@ -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([
+ '',
+ '
',
+ ' <%=required ? "required" : ""%> />',
+ ' <% if (helpMessage && helpMessage.length) { %>',
+ ' <%=helpMessage%>',
+ ' <% } %>',
+ '
',
+ ].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([
+ '',
+ '',
+ '
',
+ ].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($('').append(cntr.$el));
+ } else {
+ $container.append($('').append(cntr.$el));
+ }
+ } else {
+ $container.append($('').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;
});
diff --git a/web/pgadmin/utils/preferences.py b/web/pgadmin/utils/preferences.py
index 2598da1a4..367caa9c1 100644
--- a/web/pgadmin/utils/preferences.py
+++ b/web/pgadmin/utils/preferences.py
@@ -13,6 +13,7 @@ module within the system.
"""
import decimal
+import simplejson as json
import dateutil.parser as dateutil_parser
from flask import current_app
@@ -31,7 +32,7 @@ class _Preference(object):
def __init__(
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__
@@ -54,6 +55,8 @@ class _Preference(object):
:param max_val: maximum value
:param options: options (Array of list objects)
: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
"""
@@ -67,6 +70,7 @@ class _Preference(object):
self.max_val = max_val
self.options = options
self.select2 = select2
+ self.fields = fields
# Look into the configuration table to find out the id of the specific
# preference.
@@ -137,6 +141,12 @@ class _Preference(object):
if self._type == 'text':
if res.value == '':
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
@@ -196,6 +206,14 @@ class _Preference(object):
if not has_value and self.select2 and not self.select2['tags']:
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(
pid=self.pid
@@ -231,7 +249,8 @@ class _Preference(object):
'max_val': self.max_val,
'options': self.options,
'select2': self.select2,
- 'value': self.get()
+ 'value': self.get(),
+ 'fields': self.fields,
}
return res
@@ -371,7 +390,7 @@ class Preferences(object):
def register(
self, category, name, label, _type, default, min_val=None,
max_val=None, options=None, help_str=None, category_label=None,
- select2=None
+ select2=None, fields=None
):
"""
register
@@ -393,6 +412,8 @@ class Preferences(object):
:param help_str:
:param category_label:
: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)
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 in (
'boolean', 'integer', 'numeric', 'date', 'datetime',
- 'options', 'multiline', 'switch', 'node', 'text'
+ 'options', 'multiline', 'switch', 'node', 'text',
+ 'keyboardshortcut'
), "Type cannot be found in the defined list!"
(cat['preferences'])[name] = res = _Preference(
cat['id'], name, label, _type, default, help_str, min_val,
- max_val, options, select2
+ max_val, options, select2, fields
)
return res
diff --git a/web/regression/javascript/backform_controls/keyboardshortcut_spec.js b/web/regression/javascript/backform_controls/keyboardshortcut_spec.js
new file mode 100644
index 000000000..02f0715cb
--- /dev/null
+++ b/web/regression/javascript/backform_controls/keyboardshortcut_spec.js
@@ -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);
+
+ });
+
+ });
+
+ });
+});
\ No newline at end of file
diff --git a/web/regression/javascript/backform_controls/keycode_spec.js b/web/regression/javascript/backform_controls/keycode_spec.js
new file mode 100644
index 000000000..d71ef48c6
--- /dev/null
+++ b/web/regression/javascript/backform_controls/keycode_spec.js
@@ -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);
+
+ });
+
+ });
+
+ });
+});
\ No newline at end of file
diff --git a/web/webpack.test.config.js b/web/webpack.test.config.js
index 2767db8a8..966793a54 100644
--- a/web/webpack.test.config.js
+++ b/web/webpack.test.config.js
@@ -12,6 +12,7 @@ module.exports = {
jQuery: 'jquery',
_: 'underscore',
'underscore.string': 'underscore.string',
+ 'window.jQuery': 'jquery',
}),
],
@@ -60,6 +61,13 @@ module.exports = {
'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.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',
'translations': regressionDir + '/javascript/fake_translations',
'pgadmin.browser.endpoints': regressionDir + '/javascript/fake_endpoints',
@@ -70,6 +78,8 @@ module.exports = {
'pgadmin': sourcesDir + '/js/pgadmin',
'pgadmin.sqlfoldcode': sourcesDir + '/js/codemirror/addon/fold/pgadmin-sqlfoldcode',
'pgadmin.alertifyjs': sourcesDir + '/js/alertify.pgadmin.defaults',
+ 'pgadmin.backgrid': sourcesDir + '/js/backgrid.pgadmin',
+ 'pgadmin.backform': sourcesDir + '/js/backform.pgadmin',
},
},
externals: {