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

View file

@ -242,6 +242,7 @@ define([
me.listenStoreEvents= (me.options.listenStoreEvents!==undefined) ? me.options.listenStoreEvents : true; me.listenStoreEvents= (me.options.listenStoreEvents!==undefined) ? me.options.listenStoreEvents : true;
me.allowScrollbar = (me.options.allowScrollbar!==undefined) ? me.options.allowScrollbar : true; me.allowScrollbar = (me.options.allowScrollbar!==undefined) ? me.options.allowScrollbar : true;
me.scrollAlwaysVisible = me.options.scrollAlwaysVisible || false; me.scrollAlwaysVisible = me.options.scrollAlwaysVisible || false;
me.tabindex = me.options.tabindex || 0;
if (me.parentMenu) if (me.parentMenu)
me.parentMenu.options.restoreHeight = (me.options.restoreHeight>0); me.parentMenu.options.restoreHeight = (me.options.restoreHeight>0);
me.rendered = false; me.rendered = false;
@ -678,7 +679,7 @@ define([
if (this.enableKeyEvents && this.handleSelect) { if (this.enableKeyEvents && this.handleSelect) {
var el = $(this.el).find('.inner').addBack().filter('.inner'); var el = $(this.el).find('.inner').addBack().filter('.inner');
el.addClass('canfocused'); 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)); el.on((this.parentMenu && this.useBSKeydown) ? 'dataview:keydown' : 'keydown', _.bind(this.onKeyDown, this));
} }
}, },
@ -1120,7 +1121,7 @@ define([
if (this.enableKeyEvents && this.handleSelect) { if (this.enableKeyEvents && this.handleSelect) {
var el = $(this.el).find('.inner').addBack().filter('.inner'); var el = $(this.el).find('.inner').addBack().filter('.inner');
el.addClass('canfocused'); 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)); 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([ define([
'common/main/lib/component/BaseView', 'common/main/lib/component/BaseView',
'common/main/lib/component/CheckBox' 'common/main/lib/component/CheckBox',
'common/main/lib/component/FocusManager'
], function () { ], function () {
'use strict'; 'use strict';
@ -612,6 +613,9 @@ define([
!this.initConfig.id && (this.initConfig.id = 'window-' + this.cid); !this.initConfig.id && (this.initConfig.id = 'window-' + this.cid);
!this.initConfig.tpl && (this.initConfig.tpl = ''); !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); Common.UI.BaseView.prototype.initialize.call(this, this.initConfig);
}, },

View file

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

View file

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

View file

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

View file

@ -46,7 +46,8 @@ define([
SSE.Views.FormulaWizard = Common.Views.AdvancedSettingsWindow.extend(_.extend({ SSE.Views.FormulaWizard = Common.Views.AdvancedSettingsWindow.extend(_.extend({
options: { options: {
contentWidth: 580, contentWidth: 580,
height: 397 height: 397,
focusManager: true
}, },
initialize : function(options) { initialize : function(options) {
@ -281,6 +282,8 @@ define([
else else
me.args[argcount].lblName.html(me.args[argcount].argName); 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>')); 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) { onInputChanging: function(input, newValue, oldValue, e) {

View file

@ -50,7 +50,8 @@ define([
style: 'min-width: 216px;', style: 'min-width: 216px;',
cls: 'modal-dlg', cls: 'modal-dlg',
id: 'window-page-margins', id: 'window-page-margins',
buttons: ['ok', 'cancel'] buttons: ['ok', 'cancel'],
focusManager: true
}, },
initialize : function(options) { initialize : function(options) {
@ -146,6 +147,12 @@ define([
$window.find('.dlg-btn').on('click', _.bind(this.onBtnClick, this)); $window.find('.dlg-btn').on('click', _.bind(this.onBtnClick, this));
$window.find('input').on('keypress', _.bind(this.onKeyPress, 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(); this.updateMetricUnit();
}, },