Javascript-форум (https://javascript.ru/forum/)
-   ExtJS (https://javascript.ru/forum/extjs/)
-   -   Ext.FocusManager (https://javascript.ru/forum/extjs/63113-ext-focusmanager.html)

khusamov 18.05.2016 17:37

Ext.FocusManager
 
Куда подевался 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

Цитата:

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

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

Хм. Если подумать, то задачка-то забавно нетривиальная даже без учёта вопроса с браузерным окном. Отслеживание активного компонента тут слабо поможет, особенно если вашему приложению нужно работать в 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_input', 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...iner-method-on

Цитата:

Я протестировал по-быстрому в Chrome, но по идее в IE...
Пока задача такова, что браузер я выбираю, а не пользователь.

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

.

nohuhu 24.05.2016 01:54

Цитата:

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

А если честно, то других вариантов с браузерными приложениями и нету вовсе. Из 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...xt.util.KeyMap
http://docs.sencha.com/extjs/6.0/6.0...xt.util.KeyNav

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

nohuhu 31.05.2016 21:49

Цитата:

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

Свою сборку Chromium делать накладно, лучше использовать Electron. Мы так и сделали в 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

Цитата:

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

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

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

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

khusamov 01.06.2016 21:00

Цитата:

перехват клавиатурных событий имеет побочные эффекты
Где бы почитать про эти буки-бяки?

nohuhu 01.06.2016 22:00

Цитата:

Сообщение от khusamov (Сообщение 418271)
Где бы почитать про эти буки-бяки?

Обычно в багрепортах, но можете начать здесь: 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
		// [url]http://javascript.ru/forum/extjs/63113-ext-focusmanager-2.html[/url]
		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).
	 * [url]https://learn.javascript.ru/keyboard-events[/url]
	 */
	 
	// Привести в соответствие со свойством browserEvent
	// [url]http://docs.sencha.com/extjs/6.0/6.0.1-classic/#!/api/Ext.event.Event-property-browserEvent[/url]
	 
	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; // спец. символ
	}

});


Часовой пояс GMT +3, время: 06:45.