misc/class
lib/jquery_pnotify, lib/moment, lib/lodash, misc/notification, site/engine, misc/social
if( $.browser.msie && $.browser.version <= 8 ) include('lib/respond'); $._social.__cfg = {"init":[{"service":"basic"},{"fb_app_id":"1997094873850041","service":"fb"},{"vk_app_id":"2978320","service":"vk"},{"service":"twi"}],"like":[{"service":"fb"},{"service":"vk"},{"via":"","channel":"","hash_tag":"","service":"twi"}]}; window._SiteEngine = new classes.SiteEngine( { user_id: 0, controller: 'content_tape', action: 'view', content_css_version: '1459538664', social_enabled: 0} );

Faiwer

Блог web-программиста

React 16 & Context decorators

13 апреля 2018

С обновлением React до 16 версии мы получили новый вид контекста, на замену старому. Теперь это не мутное API, как было раньше, а утверждённый вариант, с которым можно смело в бой. В чём же ключевые различия?

  • Старый вариант был многословным. Новый вариант компактнее.
  • Старый вариант не обновлял связанные с контекстом компоненты при изменении контекста. По сути контекст должен был быть имутабельным. Новый вариант обновляет связанные с ним компоненты.
  • Старый вариант можно было использовать в любом методе компонента. Новый только в render-е.
  • Старый вариант не имел пенальти по производительности и не требовал использования HOC-ов, не увеличивал иерархию древа. Новый вариант всё это делает. Скажем для connect-а из redux-а потребуется аж 2 дополнительных уровня иерархии.
  • Старый вариант был оформлен в виде static полей класса, новый вариант в виде function as tag content. И то и другое слегка уродливо. Или не слегка. JSX в целом страшненький. А тут...

В итоге послевкусие специфическое. С одной стороны API хотя бы устаканили. С другой стороны сделали его каким-то очень неудобным и вообще кривоватым. Такое впечатление возникает в целом, глядя на новые возможности из 16+. Но тут я остановлюсь на работе с контекстом.

Писать вот так:

render()
{
	return <Consumer>{arg1 =>
		<Consumer2>{arg2 =>
			<div>/* some code with them */</div>
		}</Consumer2>
	}</Consumer>
}

… невыносимо. Хочется удобства. В итоге это вылилось в такой вариант:

@renderContext('arg1', 'arg2')
render(arg1, arg2)
{
	return <div>/* some code with them */</div>;
}

По сути я воспользовался декораторами из ES7, которые пока под некоторым вопросом. Но т.к. JSX в стандарте тоже нет, то плевать. Что делает данный декоратор? Он оборачивает метод render обёртками из кода выше. Но делает это автоматически.  А в исходный render метод передаёт все контексты как аргументы функции в том же порядке.

Никакой магии в этом нет и все эти обёртки над обёртками никуда не испарились, но теперь хотя бы скрыты с глаз долой. Помимо прочего я написал ещё несколько декораторов. Один для <Provider/>-ов, один для HOC, один для удобтва i18n. Чувствую в декораторы затянет меня с головой. Ух злая эта штука. Это не к добру :)

Сам декоратор устроен вот так:

const renderContext = (...keys) =>
function(target, methodName, descriptor)
{
	const render = descriptor.value;
	descriptor.value = keys
		.map(key => list[key].Consumer)
		.reverse()
		.reduce((inner, Consumer) =>
		{
			return function(...args)
			{
				return <Consumer>
					{arg => inner.call(this, ...args, arg)}
				</Consumer>;
			};
		}, render);
	return descriptor;
};

Забирайте кому надо ;)

Sequelize и тестирование

8 февраля 2018

Добрался до тестирования серверной части (node). Встал вопрос: а как проще всего протестировать вещи, связанные с БД? Подумал о том, что должен быть для Sequelize какой-нибудь драйвер, который отрабатывает прямо в ОЗУ. Сразу не нашёл, но зато нашёл вариант с sqlite и storage=memory. Сработало. В купе с хаками для mocha в виде beforeEach и afterEach, получилось довольно удобно. Работает молниеносно, подключается тривиально. Единственное ― приходится тягать пакет с sqlite тоже. В моём случае это оказалось совершенно не критичным. 

{ dialect: 'sqlite', storage: ':memory:', }

onMouseMove & movementX & movementY

16 ноября 2017

Ковыряясь с доступными в Event полями я наткнулся на два незнакомых, которые ныне есть во всех современных браузерах. А именно: movementX & movementY. Показывают сдвиг мыши относительно предыдущего вызова callback-а. Чертовски удобно, т.к. с ними можно не считать эти сдвиги вручную. Эх, ещё бы выдали штатную возможность узнать положение мыши относительно заданного DOMElement-а без груды кода с offsetTop-ми и прочими костылями.

Размеры SVG-элементов в реальных пикселах

16 ноября 2017

При использовании динамической SVG-графики, может возникнуть ситуация, когда удобно работать с каким-нибудь конкретным viewBox-ом, указав ту координатную сетку, с координатами и размерами которой будет удобно работать. Однако не все векторные примитивы вам может захотеться рисовать в масштабе этой сетки. Какие-то из них вы пожелаете сохранить в пикселном представлении. Что же делать? 

  • Можно поменять viewBox таким образом, что бы 1-ца сетки в нём соответсвовала 1px на канве. Что далеко не всегда бывает удобным вариантом.
  • Можно подсчитать кол-во реальных px-й на 1 сетки viewBox-а и исходить из них. 

В этом посту я остановлюсь на 2-ом варианте. Итак, с чего начнём? Начнём с того, что для границ-рамок-как-угодно-называйте, в общем для stroke, есть уже готовый механизм: vector-effect: non-scaling-stroke. Указав это свойство ваши stroke-width будут задаваться в реальных пикселах, а не в масштабе SVG.

А что делать, скажем, с радиусом для circle? Положим мы хотим нарисовать точку, но хотим чтобы её размер в px-ах не зависел от размеров канвы и масштаба на ней. Ну для начала посчитаем кол-во пикселей на 1-цу сетки:

const { width: wpx, height: hpx } = svg.getBoundingClientRect();

const kx = wpx / wi;
const ky = hpx / hi;

Где wi и hi это координатные width и height для viewBox. Если kx === 10, то на 1-цу сетки у вас 10 пикселей.

Ок, что дальше? Можно использовать эти коэффициенты на стороне JS, а можно отдать почти всё на откуп CSS, воспользовавшись CSS-variables. Например так:

<svg viewBox="" style={{ '--var-kx': kx }}/>
 

а в CSS используем calc:

circle { r: calc(5 / var(--var-kx)); }

UPD: Firefox не умеет r в css :(

Хотелось бы, конечно, чего-то вроде vector-effect: non-scaling и для других полей. Но меня устроил и этот вариант.

Тернистый путь расширений в Chrome

2 ноября 2017

Довольно часто я сталкиваюсь с тем, что сторонние сайты работают не совсем так, как я бы хотел. И будучи web-программистом я пытаюсь на это дело как-то повлиять. Но одно дело, скажем, стили сайту подправить, и совсем другое вмешаться в его работу JavaScript-ом. Эта заметка о втором.

Итак. На этот раз жертвой пал puzzle-english. Ресурс для изучения английского. Среди прочего там есть возможно уникальная возможность тренировать аудирование на небольших заранее заготовленных фразах, записанных от нескольких дикторов. Предлагается на выбор по 3-5 слов на каждое произнесённое слово. Итого ты или кликаешь по кнопкам мышью, собирая фразу, или с клавиатуры клавишами цифр. Всё бы ничего, если бы не отсутствие поддержки чисел с num-панели. Поддерживаются только те, что над буквами. Что, лично для меня, страшно неудобно. Ну что ж, приступим.

Для начала я решил узнать как оно там вообще работает. Посмотрел все eventListener-ы на document и нашёл нужный методом перебора при debug-е. Он слушает keydown и несколько неуклюже обрабатывает event.which, поддерживая только один диапазон для чисел. Поиграл с ним и понял, что при "патченном" which num-цифры работают прекрасно. Написал небольшой скрипт в консоли примерно следующего толку и решил что дело в шляпе:

document.addEventListener('keydown',  evt =>
{
	if(evt.which > 96 && evt.which < 105)
	{
		const which = evt.which - 48;

		$.event.trigger(
		{
			type: 'keydown',
			which,
			originalEvent: { target: document.body }
		});
	}
});

Забыл упомянуть, puzzle-english агрессивно использует jQuery. Чем я не применул воспользоваться. Там ведь свои обёртки и свои рецепты. Обернул этот код расширением и... Обломался. Chrome не позволяет использовать со страницы жертвы ничего кроме DOM. Т.е. доступа к jQuery нет, доступа к любым кастомным window-полям тоже нет. Ничего нет. 

Ну не беда, подумал я и решил организовать keydown средствами браузера. Что у нас там есть? document.createEvent и initKeyboardEvent. Поковырялся — работает плохо. Да ещё и deprecated. Ок, а как ещё можно? Ага, у нас есть класс KeyboadEvent. Ok:

new KeyboardEvent('keydown', { which: 50 });

Ага. Нифига. В документации одно, в браузере другое. Плевал он на which и прочие поля. Что пишут нам интернеты? Ух, дичь какая... Зато работает:

window.evt = evt = document.createEvent("KeyboardEvent");
evt.initKeyboardEvent('keydown',
	true, false, null, 0, false,
	0, false, which, 0);
evt.__defineGetter__('which', () => which);
document.dispatchEvent(evt);

Правда только из консоли. Почему? Потому что браузер заменяет отправленные из расширения события их копиями в основной тред. В целях безопасности. И того мы снова теряем which.

Что в итоге? В итоге вспомнил, что там ещё мышью можно было кликать. Стало быть можно эмулировать клик. Просто вызвав у DOMElement-а метод click. С этим уже граблей не было.

В общем мораль такова: расширения скованы цепями. Попытки сделать что-то просто — могут упереться в открытые баги или просто запреты. Так и живём ;)