Вход

Просмотр полной версии : Ext.FocusManager


khusamov
18.05.2016, 17:37
Куда подевался Ext.FocusManager?
Как теперь понять, на чем фокус стоит?
Как отслеживать перемещение фокуса?

nohuhu
18.05.2016, 21:35
Ext.FocusManager убит, расчленён, проткнут осиновым колом в его чёрное сердце, выжжен напалмом, пепел захоронен под Чернобыльским саркофагом, и имя его забыто. Я это сделал собственноручно и с превеликим удовольствием, поэтому как доктор говорю.

Как понять, на чём фокус стоит:


// всегда было
var focusedElement = document.activeElement;

// Вернёт то же самое, если вам так удобнее
focusedElement = Ext.Element.getActiveElement();

// Вернёт компонент, которому принадлежит сфокусированный элемент
// Легко может оказаться null
var focusedComponent = Ext.ComponentManager.getActiveComponent();


Как отслеживать перемещение фокуса... Вот это вопрос на миллион долларов, с радиоактивными тентаклями которого я бодаюсь уже четвёртый год. В тривиальном случае:


component.on('focusenter', function() {
console.log('компонент сфокусирован');
});

component.on('focusleave', function() {
console.log('компонент потерял фокус');
});


Очень надеюсь, что менее тривиальные случаи вас не побеспокоят. ;)

khusamov
19.05.2016, 13:54
focusenter мне не поможет отследить фокус.

Потому что component не известен. И собственно отслеживание фокуса нужно как раз для того, чтобы узнать component, на котором фокус.

Вот это Ext.ComponentManager.getActiveComponent(); конечно уже лучше. Но как отследить изменение фокуса?

Хотя можно по идее делать по цепочке.
1) узнать текущий фокус Ext.ComponentManager.getActiveComponent();
2) подписаться на focusleave
3) как только происходит focusleave тут же перейти в пункт 1.
Муторный способ однако...

nohuhu
19.05.2016, 22:17
Объясните подробнее, чего хотите добиться.

khusamov
20.05.2016, 16:15
Хочу отслеживать фокус. Например, при смене фокуса, чтобы в консоли появлялось сообщение: фокус изменен с компонента1 на компонент2

nohuhu
20.05.2016, 21:09
Отслеживание фокуса само по себе особого смысла не имеет, только если из праздного любопытства. Скорее всего вам это нужно для каких-то конкретных целей, например валидирования данных в форме или ещё чего-нибудь. Вот это я и пытаюсь выяснить: какова ваша задача, в чём цель отслеживания фокуса?

Это я не педантизма ради, просто accessibility в целом и управление фокусом в частности это как раз одно из моих основных направлений работы в Sencha. Я могу вам подсказать, как сделать то или это, но мне нужно понимать, чего вы хотите добиться, чтобы посоветовать оптимальный вариант.

khusamov
20.05.2016, 23:10
Вы наверное все секреты можете выведать. Да, мне нужно прикрутить чертов сканер штрих-кодов к браузеру. Таблицы grid к сожалению не пропускают пробелы и Enter-ы. Вот сижу и думаю как бы эту неприятность обойти.

nohuhu
21.05.2016, 01:34
Мне ваши секреты ни к чему, правда. :) Но я рад, что мы постепенно продвигаемся к цели.

Grid не "не пропускает" пробелы и Enter, эти клавиши имеют специальное значение. Пробел выделяет строку или ячейку, Enter переводит таблицу из навигационного режима (Navigable mode) в активизационный режим (Actionable mode). Названия и поведение таблицы взяты прямиком отсюда: https://www.w3.org/TR/wai-aria-practices/#grid. Изменять это поведение в общем случае я бы не рекомендовал, но если есть чёткое понимание, как UI должен работать, то можно сделать исключение. Хотя и непонятно пока, зачем.

Вам нужно со сканера данные получать и вставлять в таблицу? В этом случае можно не обращать внимание на фокус и просто добавлять записи в Store. Grid знает, что нужно делать, когда в нём сфокусирована ячейка и надо обновить таблицу.

Или задача в чём-то ещё?

khusamov
21.05.2016, 18:22
Сканер отправляет строку с префиксом и постфиксом. В качестве постфикса используется код 13 Enter. И если случайно (пользователь все может) фокус будет на каком-либо гриде, то 13-й код не пройдет и штрих-код не будет считан (точнее его конец не будет считан).

У меня работа со сканером глобальная. То есть не привязана ни к какому элементу. Поэтому пользователю не требуется перед сканированием указывать фокус (то есть специально выбирать элемент, куда будет произведен ввод со сканера).

Infarch
22.05.2016, 13:00
А какой префикс? И вообще, какие символы приходят со сканера и сколько?

khusamov
22.05.2016, 15:08
$<цифры-буквы><13>
С баксом проблем нет, грид его пропускает. И все остальное тоже, кроме 13.

.

khusamov
22.05.2016, 15:10
...Enter переводит таблицу из навигационного режима (Navigable mode) в активизационный режим (Actionable mode).

Об это есть где подробнее прочесть? Пока не смог найти.

Изменять это поведение в общем случае я бы не рекомендовал, но если есть чёткое понимание, как UI должен работать, то можно сделать исключение. Хотя и непонятно пока, зачем.

А как отключить этот перехват без патчей?

Пробовал найти такую опцию в классах:
Ext.view.AbstractView
Ext.view.View
Ext.panel.Table
Ext.grid.Panel

.

nohuhu
23.05.2016, 22:53
У меня работа со сканером глобальная. То есть не привязана ни к какому элементу. Поэтому пользователю не требуется перед сканированием указывать фокус (то есть специально выбирать элемент, куда будет произведен ввод со сканера).

Всё страньше и страньше, сказала Алиса... Т.е. у вас общение со сканером односторонее и по факту сканер эмулирует клавиатурный ввод на уровне ОС в активное приложение? А что будет, если окно браузера не в фокусе и "нажатия клавиш" улетят мимо?

Хм. Если подумать, то задачка-то забавно нетривиальная даже без учёта вопроса с браузерным окном. Отслеживание активного компонента тут слабо поможет, особенно если вашему приложению нужно работать в IE. Я бы пошёл другим путём: можно установить обработчик события keydown на уровне документа со срабатыванием в фазе перехвата, и при опознавании префикса "подставлять" под следующие за ним "нажатия клавиш" некое специальное поле. Примерно так:


Ext.define('My.app.Application', {
extend: 'Ext.app.Application',

launch: function() {
this.scannerField = new Ext.form.field.Text({
renderTo: Ext.getBody(),
floating: true,
hidden: true,
focusOnToFront: true,
cls: 'x-hidden-clip',
shadow: false,
listeners: {
specialkey: function(field, e) {

if (e.getKey() === e.ENTER) {
e.preventDefault();
e.stopEvent();

// Это просто чтобы не заморачиваться с разрешением
// scope через references в этом примере
Ext.app.Application.instance.fireEvent('scanner_in put', this.getValue());
this.setRawValue('');
this.hide();
}
}
}
});

Ext.getDoc().on('keydown', function(e) {
// 52 это код клавиши для '$'
if (e.getKey() === 52) {
e.preventDefault();
e.stopEvent();
Ext.app.Application.instance.scannerField.show();
return false;
}
}, { capture: true });
}
});


Это решение не на 100% свободно от побочных эффектов, но в вашем случае такого решения скорее всего вообще нет. Для пущего спокойствия душевного, я бы проделал серию бесчеловечных экспериментов над живым сканером; главный вопрос это насколько быстро случается "ввод", т.е. каков разрыв по времени между префиксом и постфиксом. Я очень сильно подозреваю, что разрыв близок к 0 мс, но могут быть варианты. Если разрыв минимален, то вышеописанное решение должно иметь приемлемую надёжность. Я протестировал по-быстрому в Chrome, но по идее в IE тоже должно сработать.

Учтите также, что несмотря на кажущуюся простоту кода, механизмы здесь задействуются сложные и колдунство вельми сильное. Если нужно что-нибудь поменять, лучше сперва спросите. Я с удовольствием объясню, что и как, но это может оказаться надолго. :)

khusamov
23.05.2016, 23:04
Т.е. у вас общение со сканером односторонее и по факту сканер эмулирует клавиатурный ввод на уровне ОС в активное приложение? А что будет, если окно браузера не в фокусе и "нажатия клавиш" улетят мимо?

У меня браузер в режиме киоска. Да и вообще, этот вопрос не самый главный. Пока считаем, что проблем с активным приложением нет или ее решает сам пользователь.

...со срабатыванием в фазе перехвата

1) Вот этого точно у меня сейчас нет. Я так понимаю, этот режим включает опция capture: true... Но что за режим такое вообще "фаза перехвата"?

Здесь я ничего не нашел:
http://docs.sencha.com/extjs/6.0/6.0.1-classic/#!/api/Ext.container.Container-method-on

Я протестировал по-быстрому в Chrome, но по идее в IE...

Пока задача такова, что браузер я выбираю, а не пользователь.

2) И еще вопрос:
А зачем после $ надо показывать поле Ext.app.Application.instance.scannerField.show(); а потом после 13 - скрывать?
Из-за этой опции focusOnToFront: true? Хитро однако...

.

nohuhu
24.05.2016, 01:54
У меня браузер в режиме киоска. Да и вообще, этот вопрос не самый главный. Пока считаем, что проблем с активным приложением нет или ее решает сам пользователь.


А если честно, то других вариантов с браузерными приложениями и нету вовсе. Из JavaScript всё равно никак не сфокусировать браузерное окно, поэтому остаётся только предполагать, что оно будет в фокусе.


1) Вот этого точно у меня сейчас нет. Я так понимаю, этот режим включает опция capture: true... Но что за режим такое вообще "фаза перехвата"?


Capture phase, первая стадия прохождения DOM событий: http://javascript.info/tutorial/bubbling-and-capturing. В нашем случае перехватчик устанавливается на самом верху иерархии, документе, поэтому срабатывает перед любыми другими обработчиками событий на элементах в теле документа.


Пока задача такова, что браузер я выбираю, а не пользователь.


Это хорошо. Я не стал упоминать, что это решение не будет работать в IE < 9, тихо понадеявшись на то, что такой вопрос даже не встанет. :)


2) И еще вопрос:
А зачем после $ надо показывать поле Ext.app.Application.instance.scannerField.show(); а потом после 13 - скрывать?
Из-за этой опции focusOnToFront: true? Хитро однако...

.

Это поле нам нужно только для ввода сканерных событий, и фактически только на период между началом ввода префиксом '$' и окончанием ввода (Enter). На мой взгляд, не имеет смысла держать это поле постоянно видимым на экране - у пользователей ручки шаловливые и киоск или не киоск, а проблем будет меньше, если этим ручкам не давать повода для шалостей.

Поэтому поле сделано невидимым, но фокусируемым, с помощью CSS правила clip: rect(0,0,0,0), которое применяется стилем 'x-hidden-clip'. Остальные хитрости как раз для автомагического управления фокусом: поле у нас "плавающий" компонент с абсолютным позиционированием в теле документа, поэтому в раскладках не участвует и на другие компоненты не влияет. Когда мы это поле показываем через scannerField.show(), глобальный ZIndexManager сортирует свою коллекцию плавучих компонентов и выпихивает наверх наше поле, попутно его фокусируя. Поле, оказываясь наверху стека и получая фокус, запоминает предыдущий сфокусированный элемент и отпуливает фокус обратно, когда мы это поле скрываем. Флаг focusOnToFront должен быть true по умолчанию для всех плавучек, но я просто подстраховался, т.к. некоторые классы его меняют и я не помню точно, какие именно.

Примерно так, если без нюансов. :)

А ещё если подумать, то надо очистку поля через setRawValue('') передвинуть из обработчика specialkey в глобальный перехватчик. И вопрос о длительности интервала между $ и Enter был вовсе не праздным, промышленную версию этого решения неплохо бы обвешать предохранителями на тему ложных срабатываний. Как говорил незабвенный поручик, случаи разные бывают...

khusamov
24.05.2016, 10:46
А если честно, то других вариантов с браузерными приложениями и нету вовсе. Из JavaScript всё равно никак не сфокусировать браузерное окно, поэтому остаётся только предполагать, что оно будет в фокусе.

Я еще нашел вариант: это собрать свою сборку Хромиума на С++. Там можно многое добавить из мира нативных десктопных программ (если вообще не все возможности). И наверное можно добавить дополнительный API, доступный внутри JS-кода.

Это для задач, где пользователь не решает, какой браузер ему использовать.

Capture phase, первая стадия прохождения DOM событий: http://javascript.info/tutorial/bubbling-and-capturing. В нашем случае перехватчик устанавливается на самом верху иерархии, документе, поэтому срабатывает перед любыми другими обработчиками событий на элементах в теле документа.

Хе, а зачем все остальные хитрости? Это как раз то, что нужно. Я свой обработчик, что сейчас есть, туда выставлю и он перехватит коды 13 и пробел, которые теряются в гриде!!!

Я попробовал в действии опцию { capture: true }. Для keydown она работает, грид не может перехватить и заблокировать событие, а вот для keypress эта опция не сработала, грид заблокировал событие, почему такая разница???

В итоге, мне в общем-то достаточно опции capture, чтобы решить проблему, разве что событие keypress придется поменять на keydown. Но, единственный недостаток решения это то, что capture это недокументированная опция, надо заметить...

.

khusamov
24.05.2016, 20:05
Здесь тоже есть опция capture, но недокументированная

http://docs.sencha.com/extjs/6.0/6.0.1-classic/#!/api/Ext.util.KeyMap
http://docs.sencha.com/extjs/6.0/6.0.1-classic/#!/api/Ext.util.KeyNav

плюс еще опция priority тоже есть.

nohuhu
31.05.2016, 21:49
Я еще нашел вариант: это собрать свою сборку Хромиума на С++. Там можно многое добавить из мира нативных десктопных программ (если вообще не все возможности). И наверное можно добавить дополнительный API, доступный внутри JS-кода.


Свою сборку Chromium делать накладно, лучше использовать Electron (http://electron.atom.io). Мы так и сделали в Sencha Test.


Хе, а зачем все остальные хитрости? Это как раз то, что нужно. Я свой обработчик, что сейчас есть, туда выставлю и он перехватит коды 13 и пробел, которые теряются в гриде!!!


Остальные хитрости нужны затем, чтобы максимально использовать возможности браузера и существующий код фреймворка без необходимости изобретать велосипед.


Я попробовал в действии опцию { capture: true }. Для keydown она работает, грид не может перехватить и заблокировать событие, а вот для keypress эта опция не сработала, грид заблокировал событие, почему такая разница???


Потому что keypress это синтетическое событие для "печатных" символов, оно не имеет действия по умолчанию: http://www.quirksmode.org/dom/events/keys.html


Но, единственный недостаток решения это то, что capture это недокументированная опция, надо заметить...


Это баг, пофиксим. Спасибо, что нашли. ;)

khusamov
01.06.2016, 07:21
Остальные хитрости нужны затем, чтобы максимально использовать возможности браузера и существующий код фреймворка без необходимости изобретать велосипед.

Странно. Все эти хитрости, направлены на то, чтобы при помощи фокуса отлавливать нажатия кнопок. Чтобы никто не смог заблокировать.

Но ведь опция { capture: true } гарантированно решает эту проблему.

Зачем "максимально использовать возможности браузера", если проблема перехвата так легко решается? Хотя бы ценой смены события keypress на keydown.

nohuhu
01.06.2016, 20:30
Зачем "максимально использовать возможности браузера", если проблема перехвата так легко решается? Хотя бы ценой смены события keypress на keydown.

Затем, что проблема на самом деле нетривиальна и перехват клавиатурных событий имеет побочные эффекты, которые могут выйти боком. Затем, что "очевидный" способ решить эту проблему - это конечный автомат, которых надо избегать, как огня, потому что любой такой автомат кончает прицельным выстрелом в вашу ногу. Затем, что в браузере вы не можете читать поток ввода, когда вам удобно, а вместо этого вынуждены реагировать на события - а асинхронный конечный автомат бьёт по площадям дробью размером с пушечное ядро.

А самое главное: для обработки этих событий вам нужно писать код, а код === баги === стоимость поддержки. Единственный способ уменьшить количество багов, это уменьшить количество кода.

Но вы не подумайте чего, я не буду вас уговаривать. Ваш проект, ваша головная боль... ;)

khusamov
01.06.2016, 21:00
перехват клавиатурных событий имеет побочные эффекты

Где бы почитать про эти буки-бяки?

nohuhu
01.06.2016, 22:00
Где бы почитать про эти буки-бяки?

Обычно в багрепортах, но можете начать здесь: http://www.w3.org/TR/uievents/#events-keyboardevents.

khusamov
15.04.2017, 14:12
Первая версия универсального контроллера ввода штрих-кодов. Для его использования по идее надо сделать контроллер обработки, который подписывается на событие barcode.


/* global Ext */

/**
* Контроллер ввода штрих-кодов.
*/

Ext.define("Ews.controller.Barcode", {

extend: "Ext.app.Controller",

requires: ["Ews.util.EventUtil"],

barcodePrefix: "$",

barcodeSuffix: "&",
//barcodeSuffixCode: 13,

mode: "listen", // listen | scan

value: "",

init: function() {
var me = this;

// Событие keypress похоже надо заменить на keydown
// http://javascript.ru/forum/extjs/63113-ext-focusmanager-2.html
Ext.getWin().on("keypress", "onWindowKeyPress", me, { capture: true });

console.group("Слежение за штрих-кодами включено.");
console.log("Префикс:", me.barcodePrefix);
console.log("Суффикс:", me.barcodeSuffix);
console.groupEnd();
},

onWindowKeyPress: function(event) {
var me = this;
var keyChar = Ews.util.EventUtil.getCharFromEvent(event);
if (me.mode == "scan") {
if (keyChar == me.barcodeSuffix) {
//if (event.keyCode == me.barcodeSuffixCode) {
me.end();
} else {
me.resume(keyChar);
}
} else {
if (keyChar == me.barcodePrefix) me.begin();
}
},

begin: function() {
var me = this;
me.value = "";
me.mode = "scan";
console.log("Начат ввод штрих-кода.");
},

resume: function(keyChar) {
var me = this;
me.value += keyChar;
console.log("Ввод штрих-кода продолжается:", keyChar, me.value);
},

end: function() {
var me = this;
me.mode = "listen";
me.onEnterBarcode(me.value);
console.log("Ввод штрих-кода завершен. Введенный код:", me.value);
},

onEnterBarcode: function(barcodeValue) {
var me = this;
me.fireEvent("barcode", barcodeValue);
}

});


И вспомогательная утилитка:


/* global Ext */

Ext.define("Ews.util.EventUtil", {

singleton: true,

/**
* Получение кода символа из события keypress.
* Вспомогательная функция.
* event.type должен быть keypress (функция работает только с keypress).
* https://learn.javascript.ru/keyboard-events
*/

// Привести в соответствие со свойством browserEvent
// http://docs.sencha.com/extjs/6.0/6.0.1-classic/#!/api/Ext.event.Event-property-browserEvent

getCharFromEvent: function(event) {

//event = event instanceof Ext.event.Event ? event.browserEvent : event;


if (event.which == null) { // IE
if (event.keyCode < 32) return null; // спец. символ
return String.fromCharCode(event.keyCode);
}

if (event.which != 0 && event.charCode != 0) { // все кроме IE
if (event.which < 32) return null; // спец. символ
return String.fromCharCode(event.which); // остальные
}

return null; // спец. символ
}

});