Add focus manager component for using in modal windows.

[SSE] Use focus manager for windows with input fields, comboboxes, listview.
This commit is contained in:
Julia Radzhabova 2020-09-25 14:44:15 +03:00
parent 2c1a84f727
commit a597d3376c
9 changed files with 193 additions and 22 deletions

View file

@ -86,7 +86,8 @@ define([
displayField: 'displayValue',
valueField : 'value',
search : false,
scrollAlwaysVisible: false
scrollAlwaysVisible: false,
takeFocusOnClose: false
},
template: _.template([
@ -340,6 +341,10 @@ define([
this.cmpEl.find('.dropdown-toggle').blur();
this.trigger('hide:after', this, e, isFromInputControl);
Common.NotificationCenter.trigger('menu:hide', this, isFromInputControl);
if (this.options.takeFocusOnClose) {
var me = this;
setTimeout(function(){me._input.focus();}, 1);
}
},
onAfterKeydownMenu: function(e) {

View file

@ -242,6 +242,7 @@ define([
me.listenStoreEvents= (me.options.listenStoreEvents!==undefined) ? me.options.listenStoreEvents : true;
me.allowScrollbar = (me.options.allowScrollbar!==undefined) ? me.options.allowScrollbar : true;
me.scrollAlwaysVisible = me.options.scrollAlwaysVisible || false;
me.tabindex = me.options.tabindex || 0;
if (me.parentMenu)
me.parentMenu.options.restoreHeight = (me.options.restoreHeight>0);
me.rendered = false;
@ -678,7 +679,7 @@ define([
if (this.enableKeyEvents && this.handleSelect) {
var el = $(this.el).find('.inner').addBack().filter('.inner');
el.addClass('canfocused');
el.attr('tabindex', '0');
el.attr('tabindex', this.tabindex.toString());
el.on((this.parentMenu && this.useBSKeydown) ? 'dataview:keydown' : 'keydown', _.bind(this.onKeyDown, this));
}
},
@ -1120,7 +1121,7 @@ define([
if (this.enableKeyEvents && this.handleSelect) {
var el = $(this.el).find('.inner').addBack().filter('.inner');
el.addClass('canfocused');
el.attr('tabindex', '0');
el.attr('tabindex', this.tabindex.toString());
el.on((this.parentMenu && this.useBSKeydown) ? 'dataview:keydown' : 'keydown', _.bind(this.onKeyDown, this));
}
},

View file

@ -0,0 +1,130 @@
/*
*
* (c) Copyright Ascensio System SIA 2010-2020
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
/**
* FocusManager.js
*
* Created by Julia Radzhabova on 24.09.2020
* Copyright (c) 2020 Ascensio System SIA. All rights reserved.
*
*/
if (Common === undefined)
var Common = {};
if (Common.UI === undefined) {
Common.UI = {};
}
Common.UI.FocusManager = function (tabindex, parent) {
var register = function(fields, options, tabindex) {
var arr = [],
selector,
el;
if (typeof options==='string') {
selector = options;
} else {
el = options;
}
if (!fields.forEach) {
fields = [fields];
if (el)
el = [el];
}
fields.forEach(function(cmp, index) {
var elem = selector ? (cmp.$el || $(cmp.el)).find(selector) : (el && el[index] ? el[index] : cmp);
elem && elem.attr && elem.attr('tabindex', tabindex.toString());
arr.push({
cmp: cmp,
el: elem,
selector: selector
});
});
return arr;
};
return {
tabindex: tabindex || 0,
parent: parent,
fields: [],
add: function(fields, options) { // options may be selector or component.el
this.fields = this.fields.concat(register(fields, options, this.tabindex));
!this.trapFirst && this.addTraps();
},
insert: function(field, index, options) {
this.fields.splice(index, 0, register(field, options, this.tabindex));
},
remove: function(index) {
this.fields.splice(index, 1);
},
addTraps: function() {
if (!this.parent || !this.parent.$window) return;
var me = this;
this.trapFirst = $('<span aria-hidden="true" tabindex="' + this.tabindex + '"></span>');
this.trapFirst.on('focus', function() {
for (var i=0; i<me.fields.length; i++) {
var field = me.fields[i];
if (field.cmp.isVisible ? field.cmp.isVisible() : field.cmp.is(':visible')) {
var el = (field.selector) ? (field.cmp.$el || $(field.cmp.el)).find(field.selector) : field.el;
el.focus();
break;
}
}
});
this.parent.$window.prepend(this.trapFirst);
this.trapLast = $('<span aria-hidden="true" tabindex="' + (this.tabindex+1) + '"></span>');
this.trapLast.on('focus', function() {
for (var i=me.fields.length-1; i>=0; i--) {
var field = me.fields[i];
if (field.cmp.isVisible ? field.cmp.isVisible() : field.cmp.is(':visible')) {
var el = (field.selector) ? (field.cmp.$el || $(field.cmp.el)).find(field.selector) : field.el;
el.focus();
break;
}
}
});
this.parent.$window.append(this.trapLast);
},
setTabIndex: function (tabindex) {
this.tabindex = tabindex;
}
}
};

View file

@ -137,7 +137,8 @@
define([
'common/main/lib/component/BaseView',
'common/main/lib/component/CheckBox'
'common/main/lib/component/CheckBox',
'common/main/lib/component/FocusManager'
], function () {
'use strict';
@ -612,6 +613,9 @@ define([
!this.initConfig.id && (this.initConfig.id = 'window-' + this.cid);
!this.initConfig.tpl && (this.initConfig.tpl = '');
if (options.focusManager)
this.focusManager = Common.UI.FocusManager(1, this);
Common.UI.BaseView.prototype.initialize.call(this, this.initConfig);
},

View file

@ -52,7 +52,8 @@ define([
type: 0, // 0 - category, 1 - series
width : 350,
cls : 'modal-dlg',
buttons: ['ok', 'cancel']
buttons: ['ok', 'cancel'],
focusManager: true
},
initialize : function(options) {
@ -171,9 +172,10 @@ define([
$window.find('.dlg-btn').on('click', _.bind(this.onBtnClick, this));
_.defer(function(){
me.inputRange1.cmpEl.find('input').focus();
}, 10);
this.focusManager.add([me.inputRange1, me.inputRange2, me.inputRange3], '.form-control');
setTimeout(function(){
me.inputRange1.cmpEl.find('input').focus();
}, 10);
},
onPrimary: function() {

View file

@ -49,7 +49,8 @@ define([
SSE.Views.FormatSettingsDialog = Common.Views.AdvancedSettingsWindow.extend(_.extend({
options: {
contentWidth: 284,
height: 340
height: 340,
focusManager: true
},
initialize : function(options) {
@ -171,7 +172,8 @@ define([
cls: 'input-group-nr',
menuStyle: 'min-width: 264px;',
editable: false,
data: this.numFormatData
data: this.numFormatData,
takeFocusOnClose: true
});
this.cmbFormat.setValue(this.FormatType);
this.cmbFormat.on('selected', _.bind(this.onFormatSelect, this));
@ -182,7 +184,8 @@ define([
menuStyle: 'min-width: 264px;max-height:235px;',
editable: false,
data: [],
scrollAlwaysVisible: true
scrollAlwaysVisible: true,
takeFocusOnClose: true
});
this.cmbNegative.on('selected', _.bind(this.onNegativeSelect, this));
@ -210,7 +213,8 @@ define([
menuStyle: 'min-width: 264px;max-height:235px;',
editable: false,
data: [],
scrollAlwaysVisible: true
scrollAlwaysVisible: true,
takeFocusOnClose: true
});
this.cmbSymbols.on('selected', _.bind(this.onSymbolsSelect, this));
@ -220,7 +224,8 @@ define([
menuStyle: 'min-width: 264px;max-height:235px;',
editable: false,
data: [],
scrollAlwaysVisible: true
scrollAlwaysVisible: true,
takeFocusOnClose: true
});
this.cmbType.on('selected', _.bind(this.onTypeSelect, this));
@ -230,7 +235,8 @@ define([
menuStyle: 'min-width: 310px;max-height:235px;',
editable: false,
data: [],
scrollAlwaysVisible: true
scrollAlwaysVisible: true,
takeFocusOnClose: true
});
this.cmbCode.on('selected', _.bind(this.onCodeSelect, this));
@ -243,11 +249,17 @@ define([
this.lblExample = this.$window.find('#format-settings-label-example');
this.focusManager.add([this.cmbFormat, this.spnDecimal, this.cmbSymbols, this.cmbNegative, this.cmbType, this.cmbCode], '.form-control');
this.afterRender();
},
afterRender: function() {
this._setDefaults(this.props);
var cmp = this.cmbFormat;
setTimeout(function(){
(cmp.$el || $(cmp.el)).find('.form-control').focus();
}, 10);
},
show: function() {
@ -489,7 +501,7 @@ define([
this.lblExample.text(this.api.asc_getLocaleExample(this.Format));
this._decimalPanel.toggleClass('hidden', !hasDecimal);
this._negativePanel.css('visibility', hasNegative ? '' : 'hidden');
this._negativePanel.toggleClass('hidden', !hasNegative);
this._separatorPanel.toggleClass('hidden', !hasSeparator);
this._typePanel.toggleClass('hidden', !hasType);
this._symbolsPanel.toggleClass('hidden', !hasSymbols);

View file

@ -63,7 +63,8 @@ define([
contentTemplate : '',
title : t.txtTitle,
items : [],
buttons: null
buttons: null,
focusManager: true
}, options);
this.template = options.template || [
@ -114,6 +115,7 @@ define([
}
me.filterFormulas();
});
this.focusManager.add(this.inputSearch._input);
this.btnOk = new Common.UI.Button({
el: $('#formula-dlg-btn-ok')
@ -149,9 +151,10 @@ define([
if (this.cmbListFunctions) {
this.inputSearch.setValue('');
_.delay(function (me) {
var me = this;
setTimeout(function () {
me.inputSearch.$el.find('input').focus();
}, 100, this);
}, 100);
}
this._preventCloseCellEditor = false;
},
@ -242,10 +245,11 @@ define([
menuStyle : 'min-width: 100%;',
cls : 'input-group-nr',
data : groupsListItems,
editable : false
editable : false,
takeFocusOnClose: true
});
this.cmbFuncGroup.on('selected', _.bind(this.onSelectGroup, this));
this.focusManager.add(this.cmbFuncGroup, '.form-control');
} else {
this.cmbFuncGroup.setData(groupsListItems);
}
@ -266,6 +270,7 @@ define([
this.cmbListFunctions = new Common.UI.ListView({
el: $('#formula-dlg-combo-functions'),
store: this.functions,
tabindex: 1,
itemTemplate: _.template('<div id="<%= id %>" class="list-item" style="pointer-events:none;"><%= value %></div>')
});
@ -275,6 +280,8 @@ define([
this.cmbListFunctions.onKeyDown = _.bind(this.onKeyDown, this.cmbListFunctions);
this.cmbListFunctions.scrollToRecord = _.bind(this.onScrollToRecordCustom, this.cmbListFunctions);
this.onUpdateFocus();
this.focusManager.add(this.cmbListFunctions, '.listview');
}
if (this.functions) {

View file

@ -46,7 +46,8 @@ define([
SSE.Views.FormulaWizard = Common.Views.AdvancedSettingsWindow.extend(_.extend({
options: {
contentWidth: 580,
height: 397
height: 397,
focusManager: true
},
initialize : function(options) {
@ -281,6 +282,8 @@ define([
else
me.args[argcount].lblName.html(me.args[argcount].argName);
me.args[argcount].lblValue.html('= '+ ( argres!==null && argres!==undefined ? argres : '<span style="opacity: 0.6; font-weight: bold;">' + me.args[argcount].argTypeName + '</span>'));
this.focusManager.add(txt._input);
},
onInputChanging: function(input, newValue, oldValue, e) {

View file

@ -50,7 +50,8 @@ define([
style: 'min-width: 216px;',
cls: 'modal-dlg',
id: 'window-page-margins',
buttons: ['ok', 'cancel']
buttons: ['ok', 'cancel'],
focusManager: true
},
initialize : function(options) {
@ -146,6 +147,12 @@ define([
$window.find('.dlg-btn').on('click', _.bind(this.onBtnClick, this));
$window.find('input').on('keypress', _.bind(this.onKeyPress, this));
this.focusManager.add(this.spinners, '.form-control');
var cmp = this.spnTop;
setTimeout(function(){
(cmp.$el || $(cmp.el)).find('.form-control').focus();
}, 10);
this.updateMetricUnit();
},