Русский
Русский
English
Статистика
Реклама

Web development

Перевод Современные решения старых CSS-задач (1 часть) Удержание футера внизу страницы

21.07.2020 20:05:11 | Автор: admin

Приветствую. Представляю вашему вниманию перевод статьи Keep the Footer at the Bottom: Flexbox vs. Grid, опубликованной 8 апреля 2020 года за авторством Stephanie Eckles



Это первая статья из серии, посвящённой ознакомлению с современными способами решения CSS-проблем, с которыми я сталкивалась на протяжении более 13 лет в роли фронтенд-разработчика.


В течение многих лет я регулярно возвращалась к этой статье Matthew James Taylor за способом удержания футера страницы в нижней её части, даже если высота основного содержимого меньше высоты окна. Данный метод основывался на явном задании высоты футера, что являлось не масштабируемым, но очень хорошим решением (до появления Flexbox).


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


  • страницах авторизации
  • статьях блогов или новостей
  • страницах списка товаров
  • подробностях событий календаря

Существует два способа решить эту проблему с помощью современного CSS: Flexbox и Grid.


Ниже представлена демонстрация кода с использованием метода на основе Flexbox. Если перейти на страницу Codepen, можно поменять переменную $method на grid, чтобы увидеть альтернативное решение.


Описание каждого метода приведено ниже после демонстрации кода.



Метод с использованием Flexbox


Эффект достигается путём задания:


body {  min-height: 100vh;  display: flex;  flex-direction: column;}footer {  margin-top: auto;}/* Опционально */main {  margin: 0 auto;  /* или: align-self: center */  max-width: 80ch;}

Как это работает


Во-первых, мы гарантируем, что благодаря свойству min-height: 100vh, высота элемента body будет равняться как минимум, высоте экрана. Это не приведёт к переполнению, если содержимого слишком мало (исключение определённые мобильные браузеры), но если понадобится, позволит body увеличиться по высоте.


Установка свойства flex-direction: column задаёт поведение как у нормального потока документа в том плане, что блочные элементы занимают всё доступное место по всей ширине.


Преимуществом Flexbox является поведение при добавлении свойства margin: auto. Этот трюк приводит к тому, что внешние отступы элемента, к которому применено это свойство, забирают всё доступное пространство в указанном направлении (или во всех направлениях). Например, задание margin-top: auto оттолкнёт футер к нижней части экрана от расположенных выше элементов.


Проблема


В демонстрационном Codepen выше я добавила к элементу main свойство outline, чтобы продемонстрировать, что при использовании метода Flexbox, main не заполняет всю доступную высоту. Вот почему мы должны использовать именно трюк с margin-top: auto.


Вряд ли это будет иметь для вас большое значение, но если это так, посмотрите вариант с использованием Grid, который растягивает элемент main, заполняя всё доступное пространство.


Метод с использованием Grid


Эффект достигается путём задания:


body {  min-height: 100vh;  display: grid;  grid-template-rows: auto 1fr auto;}/*Опционально*/main {  margin: 0 auto;  max-width: 80ch;}

Как это работает


В этом случае мы также используем свойство min-height: 100vh, но затем добавляем grid-template-rows для правильного расположения элементов.


Ключевым трюком этого метода является использование специальной Grid единицы измерения fr. Единица измерения fr означает "фракция / доля" и с её помощью браузер вычисляет "долю" пространства, доступную данному элементу в определённой колонке или строке. В нашем случае элемент заполнит всё доступное пространство между хедером и футером, что одновременно является решением "проблемы" из метода Flexbox.


Что лучше?


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


С другой стороны, метод с Flexbox может использоваться в различных шаблонах с множеством блочных элементов в средней секции например, набор элементов <article> вместо одного <main>.


В итоге, как и в случае с другими технологиями, всё зависит от проекта. Но думаю, вы согласитесь с тем, что это удивительно иметь в распоряжении все эти современные методики.

Подробнее..

Перевод Современные решения старых CSS-задач (2 часть) Элементы одинаковой высоты Flexbox vs Grid

22.07.2020 14:19:29 | Автор: admin

Приветствую. Представляю вашему вниманию перевод статьи Equal Height Elements: Flexbox vs. Grid, опубликованной 9 апреля 2020 года автором Stephanie Eckles



Это вторая статья из серии, посвящённой ознакомлению с современными способами решения CSS-проблем, с которыми я сталкивалась на протяжении более 13 лет в роли фронтенд-разработчика.


Однажды (приблизительно 7 лет назад) я написала JQuery-плагин, который работал с тремя колонками, расположенными на одной строке. А именно, рассчитывал и задавал одинаковую высоту для элементов, независимо от количества содержимого в каждом из них. Метод вёрстки на float, который был тогда основным, не мог справиться с этой проблемой.


Решение с помощью Flexbox


С появлением Flexbox достижение такого поведения стало возможно благодаря добавлению всего одного свойства:


.flexbox {  display: flex;}

Удивительно! В этом случае прямые потомки по умолчанию выстраиваются в строку и имеют одинаковую высоту.



Но если содержимое будет храниться в дополнительном элементе, вложенном в .column, высота колонок с разным количеством контента снова будет отличаться.



Чтобы исправить это, вложенному элементу следует задать высоту 100%


.flexbox {  display: flex;}/* Гарантия, что вложенные элементы с содержимым будут заполнять высоту колонок */.element {    height: 100%;}

Колонки снова станут равной высоты и будут расти вместе с содержимым вложенного элемента .element.


Решение с помощью Grid


Используя Grid, мы сталкиваемся с похожим поведением:


.grid {  display: grid;  /* Смена оси автоматического размещения элементов */  grid-auto-flow: column;}

Подобно Flexbox, прямые потомки будут иметь одинаковую высоту, но их дети нуждаются в явном определении высоты, как и в способе с использованием Flexbox:


.flexbox {  display: grid;  grid-auto-flow: column;}/* Гарантия, что элементы с содержимым будут заполнять элементы колонок */.element {    height: 100%;}

Ниже представлена Codepen-демонстрация обоих решений для разного количества колонок в строке:



Какой способ лучше?


Для решения проблемы с равной высотой элементов, преимущество Flexbox в том, что ось по умолчанию позволяет сразу выстроить колонки рядом друг с другом, тогда как в Grid её нужно менять явно. Кроме того, элементы также не будут иметь одинаковую ширину (что может быть преимуществом для определённых типов содержимого, например, навигационных ссылок).


Преимуществом Grid в том, что столбцам можно задать равную ширину, если в этом есть необходимость. Также, в случае необходимости, можно указать желаемое максимальное количество столбцов на "строку". В этом случае Grid-макет с легкостью справляется с расчётами для автоматического распределения пространства между столбцами, в то время как Flexbox требует ручного задания расчётов для ограничения количества колонок.


Обновим наше решение с использованием Grid для 3 элементов .column на строке:


&.col-3 {  grid-gap: $col_gap;  grid-template-columns: repeat(3, 1fr);}

В то время, как для Flexbox самый простой вариант был бы таким:


$col_gap: 1rem;.flexbox.col-3 {  /* Требует явного разрешения переноса не помещающихся элементов на новую строку */  flex-wrap: wrap;  .column {    /* Альтернатива свойству "gap" */    margin: $col_gap/2;    /* Расчёты ширины колонок    max-width: calc((100% / 3) - #{$col_gap});  }}

Также, для каждого способа необходимо переопределять параметры с учётом адаптивности, но это выходит за рамки данной статьи

Подробнее..

Перевод Нововведения CSS Июль 2020 (Gap, Aspect ratio, Masonry, Subgrid)

31.07.2020 20:21:00 | Автор: admin

Приветствую. Представляю вашему вниманию перевод статьи CSS News July 2020, опубликованной 7 июля 2020 года автором Rachel Andrew



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


Gap для Flexbox


Давайте начнём с того, что уже реализовано в релизной версии одного браузера и бета-версиях других браузеров.


На момент перевода статьи поддерживается всеми современными браузерами (насчёт Safari точно неизвестно)

В CSS Grid мы можем использовать свойства gap, column-gap и row-gap для определения отступов между рядами, колонками или и тем и другим одновременно. Свойство column-gap также встречается в Мультиколонках для создания отступов между этими самыми колонками.


Хотя для создания отступов между grid-элементами можно использовать margin, плюсом свойства gap является то, что вы получаете отступы только между элементами, отступы от края grid-контейнера при этом не образуются. До сих пор для создания отступов между flex-элементами мы, как раз, и использовали margin. При этом, чтобы не допустить появления отступов от края flex-контейнера, приходилось применять к нему отрицательные margin.


Было бы неплохо иметь такое же свойство gap и для Flexbox, не так ли? Хорошая новость в том, что это произошло его поддержка уже реализована в Firefox и Chrome.


В следующем CodePen вы можете увидеть все три варианта. В первом варианте flex-элементы используют margin с каждой стороны. Это создаёт отступ в начале и в конце flex-контейнера. Во втором варианте для flex-контейнера используется отрицательный margin, чтобы вытащить этот выступающий margin за пределы границ контейнера. В третьем примере margin не используются, а вместо них задаётся gap: 20px, создающий отступ только между элементами.



Mind the Gap


Реализация gap для Flexbox выделяет несколько интересных моментов. Во-первых, как вы можете помнить, когда функционал отступов был впервые представлен в Grid Layout, были свойства:


  • grid-gap
  • grid-row-gap
  • grid-column-gap

Эти свойства были доступны сразу, как только браузеры получили поддержку CSS Grid. Но как свойства выравнивания (justify-content, align-content, align-items) сначала появились в Flexbox, а потом стали доступны в Grid, так и свойства gap со временем были переименованы и стали доступны не только в CSS Grid.


Вместе со свойствами выравнивания, свойства gap теперь относятся к спецификации "Box Alignment". Данная спецификация работает с выравниванием и распределением пространства, поэтому является лучшим местом для них. Чтобы уберечь нас от получения множества свойств, с префиксами из каждой спецификации, они также были переименованы, чтобы избавиться от префикса "grid".


Если же в вашем коде встречаются свойства с префиксом grid, не стоит беспокоиться. Такой способ записи был оставлен как псевдоним для этих свойств, поэтому ваш код не сломается.


Проверка поддержки свойств "gap" для Flexbox


Вы могли подумать, что можете свободно применять свойства "gap" для Flexbox, а для проверки наличия его поддержки использовать функциональные запросы "Feature Queries" с фолбэком с использованием "margin". К сожалению, это не так, поскольку функциональные запросы проверяют имя и значение. Например, если я хочу проверить наличие поддержки CSS Grid, я могу использовать следующий запрос:


@supports (display: grid) {  .grid {    /* grid layout code here */  }}

При проверке же поддержки свойства gap: 20px, я бы получила положительный ответ от Chrome, который на момент составления статьи не поддерживал "gap" в Flexbox, но поддерживал в CSS Grid. Всё, что делают функциональные запросы, так это проверяют, распознаёт ли браузер свойство и значение. У них нет возможности проверить наличие поддержки данного свойства в режиме раскладки Flexbox. Я подняла этот вопрос как проблему в рабочей группе CSS, однако это оказалось не так просто исправить.


Соотношение сторон


Порой нам хочется, чтобы даже при адаптивном изменении размеров, элемент сохранял фиксированное соотношение сторон. Например, в случае с изображениями или видео. Если вы размещаете на странице изображения или видео непосредственно с помощью HTML-тегов img или video, они по умолчанию сохраняют изначально присущее им соотношение сторон (если только вы принудительно не измените ширину или высоту). Однако, порой нам нужно добавить элемент, у которого одна сторона гибко заполняет доступное пространство, а другая изменяется исходя из заданного соотношения сторон. Чаще всего это применяется при встраивании в разметку видео через фрейм, однако помимо этого, вы можете захотеть сделать, например, идеальные квадратные области на сетке (или что-то другое, что требует, чтобы размер одной стороны изменялся в зависимости от изменения размера другой).


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


Новое свойство "aspect-ratio" решает эту задачу, позволяя нам указать необходимое соотношение сторон элемента. Chrome реализовал это в версии "Canary", поэтому вы можете увидеть пример работы данного функционала, если активируете флаг "Experimental Web Platform Features".


Я создала grid-раскладку и задала элементам сетки соотношение сторон "1 / 1". Ширина элементов определяется шириной их колоночного трека. Высота рассчитывается из ширины, чтобы образовать квадрат. Затем я добавила небольшой поворот.



В Chrome Canary можно ознакомиться с демо, чтобы увидеть, как даже при увеличении или сужении трека, элементы остаются квадратными благодаря тому, что их высота рассчитывается в пропорции "1 / 1" по отношению к ширине.



Встроенная поддержка Masonry


Разработчики часто спрашивают, может ли CSS Grid использоваться для создания Masonry или Pinterest-подобного макета. Хотя некоторые демо с разметкой, созданной с помощью CSS Grid, и похожи на подобные решения, данная технология разрабатывалась не для создания Masonry сеток.


Чтобы объяснить это, вам нужно иметь представление, что такое Masonry. В типичной Masonry сетке элементы отображаются построчно. Как только первая строка заполнена, элементы начинают заполнять следующую. Однако, если некоторые из элементов, расположенных в первой строке, короче других, элементы из второй строки расположатся выше, чтобы заполнить пробел. При помощи JavaScript такая сетка реализовывается при помощь библиотеки Masonry.


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


Так может ли CSS Grid использоваться для создания Masonry сетки? Один из инженеров Mozilla считает, что может и создал прототип такого функционала. Вы можете попробовать его с помощью Firefox Nightly, в разделе по URL-адресу about:config установив флаг layout.css.grid-template-masonry-value.enabled, на значение true.




Хотя это может быть очень востребованным для всех, кто создавал такой тип разметки с помощью JavaScript, многие из нас задаются вопросом, является спецификация CSS Grid подходящим местом для определения этого очень специфичного функционала. Вы можете ознакомиться с моими размышлениями по этому поводу в статье "Does Masonry Belong In The CSS Grid Specification?".


Subgrid


Мы уже слышали раньше о начале поддержки значения subgrid в свойствах grid-template-columns и grid-template-rows в браузере Firefox. Использование этого значение подразумевает, что дочерние сетки могут наследовать размер и количество треков от сетки родительской. То есть, если элемент имеет свойство display: grid он может наследовать структуру строк и столбцов, которые он занимает на родительской сетке.


С данным функционалом можно ознакомиться в браузере Firefox. Также, у меня есть множество примеров, которые можно попробовать реализовать. Статья "Digging Into The Display Property: Grids All The Way Down" объясняет, чем Subgrid отличается от вложенных Grid-сеток, а "CSS Grid Level 2: Here Comes Subgrid" является введением в спецификацию. Также, у меня есть несколько примеров в Grid by Example.


Тем не менее, первый вопрос, который люди задают, когда говорю о Subgrid "Когда он станет доступен в Chrome?". Я всё ещё не могу сказать, когда именно, но некоторые первые новости уже видны в самом ближайшем будущем. 18 июня в блоке Chromium я анонсировала, что команда Microsoft Edge (в данный момент работающая над Chromium) перерабатывает Grid Layout в движок LayoutNG (движок следующего поколения для браузера Chromium). Часть этой работы также будет включать внедрение поддержки Subgrid.


Добавление функционала в браузер процесс не быстрый, однако именно команда Microsoft первой реализовала поддержку CSS Grid в виде ранней реализации в IE10. Так что это отличные новости и с нетерпением жду возможности протестировать бета-версию.


prefers-reduced-data


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


@media (prefers-reduced-data: reduce) {  .image {    background-image: url("images/placeholder.jpg");  }}

Медиа-запрос "prefers_reduced_data" работает так же, как некоторые уже реализованные в 5 уровне Спецификации медиа-запросов свойства, определяющие предпочтения пользователя. Например, медиа-запросы "prefers_reduced_motion" и "prefers_color_scheme" позволяют вам узнать, предпочитает ли пользователь уменьшить количество анимаций на сайте или может использует тёмную тему во всей операционной системе и соответствующим образом настроить CSS.


::marker


Псевдоэлемент "::marker" позволяет нам работать с маркером конкретного элемента списка. В самом простом случае это значит, что мы можем поменять его цвет или размер. Раньше это было невозможно из-за того, что можно было работать только со всем списком целиком.



В дополнение к стилизации маркеров настоящих списков, "::marker" можно использовать и для других элементов, списками не являющимися. В примере ниже у меня есть заголовок, которому присвоено свойство "display: list-item" и следовательно имеет маркер, который я заменила на emoji.


"::marker" уже браузером Firefox и в Chrome Canary.



Примечание: прочитать больше про псевдоэлемент "::marker" и другие связанные со списками функции можно в моей статье CSS Lists, Markers, And Counters


Пожалуйста, тестируйте новые функции


Я рекомендую пробовать применять новый функционал, если вам встречаются подходящие ситуации. Вы можете найти ошибку или что-то, что работает не так, как вы ожидали. Разработчики браузеров и Рабочая группа CSS с удовольствием хотели бы узнать о них. Если кажется, что вы нашли ошибку в работе браузера, например, заметили, что "::marker" в Chrome и Firefox ведёт себя по-разному, зафиксируйте эту проблему (в статье How to file a good browser bug описывается, как лучше это сделать). Если вы считаете, что в спецификации следовало бы предусмотреть обработку ещё какого-то поведение, также зафиксируйте это как проблему в GitHub-репозитории рабочей группы CSS

Подробнее..

Перевод Мои любимые трюки в JavaScript

06.08.2020 08:23:05 | Автор: admin

Приветствую. Представляю вашему вниманию перевод статьи My Favorite JavaScript Tips and Tricks, опубликованной 28 июля 2020 года автором Tapas Adhikary



Большинство языков программирования являются достаточно открытыми, чтобы предоставить разработчикам возможность делать одно и то же разными способами. JavaScript не является исключением. Зачастую, у нас есть разные пути достижения одинакового результата, что порой даже может сбивать с толку.


При этом, некоторые приёмы обладают преимуществами перед аналогами. В данной статье я привожу список моих любимых. Уверен, какие-то из них вам также будут знакомы.


1. Забудьте о конкатенации, используйте шаблонные строки (литералы)


Конкатенация строк с помощью оператора "+" это старая школа. Более того, конкатенация строк с участием большого количества переменных (или выражений) повышает риск возникновения путаницы и ошибок.


let name = 'Charlse';let place = 'India';let isPrime = bit => {  return (bit === 'P' ? 'Prime' : 'Nom-Prime');}// конкатенация строк с помощью оператора "+"let messageConcat = 'Mr. ' + name + ' is from ' + place + '. He is a' + ' ' + isPrime('P') + ' member.'

Шаблонные строки (или литералы) позволяют встраивать выражения прямо в текст. Они обладают уникальным синтаксисом, при котором строка заключается в обратные кавычки (``). Шаблонная строка может содержать места для подстановки динамических значений. Такие места отмечаются знаком доллара и фигурными скобками. Например, ${выражение}.


Демонстрация их применения:


let name = 'Charlse';let place = 'India';let isPrime = bit => {  return (bit === 'P' ? 'Prime' : 'Nom-Prime');}// использование шаблонной строкиlet messageTemplateStr = `Mr. ${name} is from ${place}. He is a ${isPrime('P')} member.`console.log(messageTemplateStr);

2. isInteger


Это аккуратный способ узнать, является ли значение целым числом. Встроенный в JavaScript API "Number" предоставляет для этого метод "isInteger()". Очень полезная штука, о которой следует знать.


let mynum = 123;let mynumStr = "123";console.log(`${mynum} is a number?`, Number.isInteger(mynum));console.log(`${mynumStr} is a number?`, Number.isInteger(mynumStr));

Результат:



3. Значение как число


Вы когда-нибудь обращали внимание, что "event.target.value" всегда возвращает строковое значение, даже если для поля ввода "input" задан тип "number"?


Посмотрите на пример ниже. У нас есть простое поле ввода, рассчитанное только на числа, а также обработчик, срабатывающий, когда отпускают нажатую ранее кнопку клавиатуры.


<input type='number' onkeyup="trackChange(event)" />

Обработчик извлекает значение поля с помощью "event.target.value". Но возвращаемое значение имеет строковый тип. Мы получим дополнительную головную боль из-за необходимости преобразовывать это значение в целое число. А что, если поле ввода допускало бы и дробные числа (типа 16.56)? Тогда для преобразования пришлось бы использовать "parseFloat()"? Сколько же лишней работы и риска что-то напутать!


function trackChange(event) {   let value = event.target.value;   console.log(`is ${value} a number?`, Number.isInteger(value));}

Чтобы сразу получать числовое значение, используйте "event.target.valueAsNumber".


let valueAsNumber = event.target.valueAsNumber;console.log(`is ${value} a number?`, Number.isInteger(valueAsNumber));


4. Сокращение с помощью AND


Давайте рассмотрим ситуацию, в которой у нас есть логическое значение и функция.


let isPrime = true;const startWatching = () => {    console.log('Started Watching!');}

Немало кода получается ради проверки логического условия и вызова функции.


if (isPrime) {    startWatching();}

А как насчёт использования сокращённой записи вместе с оператором AND (&&)? Да, условный оператор "if" нам больше не нужен. Круто, правда?


isPrime && startWatching();

5. Значение по умолчанию с помощью OR


Если для переменной необходимо предусмотреть запасное значение по умолчанию, это достаточно просто реализуется с помощью оператора OR.


let person = {name: 'Jack'};// если свойство "age" равно "undefined", устанавливает значение 35let age = person.age || 35;console.log(`Age of ${person.name} is ${age}`);

6. Произвольные значения


Генерирование произвольных значений или получение произвольного элемента массива очень полезные методы, которые стоит держать под рукой. Я использую их почти в каждом своём проекте.


Получить произвольный элемент из массива


let planets = ['Mercury ', 'Mars', 'Venus', 'Earth', 'Neptune', 'Uranus', 'Saturn', 'Jupiter'];let randomPlanet = planets[Math.floor(Math.random() * planets.length)];console.log('Random Planet', randomPlanet);

Генерирование произвольного числа из диапазона с указанием минимального и максимального значений


 let getRandom = (min, max) => {     return Math.round(Math.random() * (max - min) + min); } console.log('Get random', getRandom(0, 10));

Примечание от переводчика
Согласно справочнику learn.javascript.ru, такой способ является не очень верным, так как вероятность получения минимального и максимального значений в 2 раза меньше, чем любого другого числа. В справочнике приводится более корректный вариант решения.

7. Значения параметров функции по умолчанию


В JavaScript параметры функции подобны локальным переменным. При вызове этой самой функции вы можете и не передавать значения для её параметров. В этом случае они принимают значение "undefined", что может привести к нежелательным последствиям.


Существует простой способ передачи значения по умолчанию для параметров функции при их определении. В примере ниже для параметра "message" функции "greetings" передаётся значение по умолчанию "Hello".


let greetings = (name, message='Hello,') => {    return `${message} ${name}`;}console.log(greetings('Jack'));console.log(greetings('Jack', 'Hola!'));

8. Обязательные параметры функции


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


Сначала объявляем функцию, которая будет выдавать в консоль сообщение об ошибке


let isRequired = () => {    throw new Error('This is a mandatory parameter.');}

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


let greetings = (name=isRequired(), message='Hello,') => {    return `${message} ${name}`;}console.log(greetings());

В примере выше параметр "name" будет иметь значение "undefined", из-за чего будет произведена попытка установить значение по умолчанию, которым и выступает функция "isRequired()". Будет вызвана ошибка:



9. Оператор "Запятая"


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


В JavaScript оператор запятой используется для оценки каждого из операндов слева направо и возврата значения последнего операнда.


let count = 1;let ret = (count++, count);console.log(ret);

В примере выше значением переменной "ret" будет число 2. По тому же принципу, результатом следующего кода будет вывод в консоль числа 32.


let val = (12, 32);console.log(val);

Где мы его используем? Есть идеи? Чаще всего оператор запятой используется параметров в цикле "for".


В примере ниже оператор запятой задаёт значение переменной "j" после объявления счётчика "i".


for (var i = 0, j = 50; i <= 50; i++, j--)

10. Объединение нескольких объектов


У вас может возникнуть потребность объединить вместе два объекта, чтобы создать третий, более полный. В этом случае можно использовать оператор "..." (да, три точки).


Рассмотрим на примере:


let emp = { 'id': 'E_01', 'name': 'Jack', 'age': 32, 'addr': 'India'};let job = { 'title': 'Software Dev',  'location': 'Paris'};

Их можно объединить с помощью spread-оператора (оператора расширения):


 // spread-оператор let merged = {...emp, ...job}; console.log('Spread merged', merged);

Существует и другой путь такого объединения. С помощью "Object.assign()":


 console.log('Object assign', Object.assign({}, emp, job));

В результате получается:



Обратите внимание, что и spread-оператор и "Object.assign" выполняют поверхностное (shallow) объединение. При поверхностном объединении, если свойства повторяются, то происходит перезапись первого объекта данными из таких же свойств второго.


Для глубокого объединения объектов, следует использовать, например, библиотеку lodash


11. Деструктуризация


Деструктуризация это техника, позволяющая извлекать значения элементов массива или свойств объекта в набор переменных. Давайте рассмотрим её на примерах


Массив


У нас есть массив со смайликами


let emojis = ['', '', '', ''];

Чтобы осуществить его деструктуризацию, следует использовать следующий синтаксис:\


let [fire, clock, , watermelon] = emojis;

Это то же самое, что и "let fire = emojis[0];", но способ гораздо более гибкий. Вы обратили внимание, что я пропустил присваивание смайлика "награда", просто оставив пустое пространство между запятыми? Итак, что у нас получится?


console.log(fire, clock, watermelon);

Результат:



Думаю, здесь также стоит упомянуть оператор "rest". Если вы хотите деструктурировать массив, присвоив один или более элементов переменным, а все оставшиеся элементы поместить в другой массив, это можно реализовать с помощью параметра "...rest", как показано ниже.


let [fruit, ...rest] = emojis;console.log(rest);

Результат:



Объект


Подобно массивам, деструктурировать можно и объекты


let shape = {  name: 'rect',  sides: 4,  height: 300,  width: 500};

Деструктурируя объект подобным образом, мы присваиваем переменным значения двух первых свойств объекта, а остальные помещаем в другой объект.


let {name, sides, ...restObj} = shape;console.log(name, sides);console.log(restObj);

Результат



Дополнительно почитать на тему деструктуризации можно здесь.


12. Обмен значений переменных


С помощью техники деструктуризации, с которой мы ознакомились выше, это сделать очень просто


let fire = '';let fruit = '';[fruit, fire] = [fire, fruit];console.log(fire, fruit);

13. isArray


Ещё один полезный метод, на этот раз позволяющий определить, являются ли входящие данные массивом


let emojis = ['', '', '', ''];console.log(Array.isArray(emojis));let obj = {};console.log(Array.isArray(obj));

14."'undefined" против "null"


"undefined" присутствует у переменной, которая была объявлена, но значение для неё задано не было
"null" обозначает пустое или несуществующее значение, которое явно присваивается переменной


"undefined" и "null" не равны при строгом сравнивании


undefined === null // false

Дополнительно почитать на тему разницы между этими двумя значениями можно здесь


15. Получение фрагментов url-адреса


Объект "window.location" имеет набор полезных методов и свойств. С помощью них мы можем получить данные про протокол, хост, порт, домен и тому подобное из url-адреса браузера.


Одно из свойств, которое мне кажется очень полезным


window.location.search

Свойство "search" возвращает фрагмент строки url-адреса, находящийся после вопросительного знака: "?project=js".


Для получения параметров запроса, помимо "location.search" можно использовать ещё один полезный API, называемый "URLSearchParams".


let project = new URLSearchParams(location.search).get('project');

В результате получаем "js"


Подробнее об этой теме читайте здесь


Это не конец


Существует множество других полезных приёмов. Я решил добавлять их в репозиторий вместе с небольшими примерами по мере того, как буду с ними сталкиваться.


Какие у вас любимые приёмы JavaScript? Как насчёт поделиться ими в комментариях ниже?

Подробнее..

Перевод Используем Chrome DevTools профессионально

25.09.2020 20:20:19 | Автор: admin
И снова здравствуйте. В преддверии старта курса JavaScript Developer. Professional перевели

11 советов для тех, кто использует Chrome в качестве среды разработки.





Итак, по тем или иным причинам вы решили при разработке ориентироваться на Chrome. Вы открываете инструменты разработчика и приступаете к отладке кода.



Иногда вы открываете консоль, чтобы посмотреть вывод своей программы, или вкладку Elements, чтобы проверить CSS-стили элементов DOM.



А действительно ли вы разбираетесь в Chrome DevTools? На самом деле у инструментов разработчика множество мощных функций, которые облегчают жизнь, но о них мало кто знает.
Я расскажу о самых полезных из них.

Для начала рассмотрим командное меню. Командное меню в Chrome это как командная оболочка в Linux. В нем вы можете писать команды для управления Chrome.

Открываем Chrome Developer Tools. Для доступа к командному меню используем горячие клавиши:

  • WindowsCtrl + Shift + P
  • macOSCmd + Shift + P


Открыть его можно и через графический интерфейс, вот так:



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



Расширенные функции скриншотов


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

  • сделать скриншот всей страницы целиком, в том числе контента, который находится за пределами области просмотра?
  • захватить содержимое отдельного элемента DOM?


Такая необходимость возникает нередко, но большинство системных инструментов для создания снимков экрана не справляются с этими задачами. К счастью, для создания таких скриншотов у нас есть специальные команды Chrome.

Вот они:

  • Screenshot Capture full size screenshot (сделать снимок страницы целиком)
  • Screenshot Capture node screenshot (сделать снимок отдельного узла)


Пример


Откройте любую веб-страницу, например самые популярные статьи о JavaScript на Medium: medium.com/tag/javascript.

Откройте командное меню и выполните команду Screenshot Capture full size screenshot.



Мы сделали снимок всей текущей страницы целиком.


У оригинального скриншота качество хорошее, а сюда я загрузил сжатое изображение, чтобы сэкономить ваш трафик.

Если вы хотите сделать скриншот элемента DOM, можно использовать системные инструменты, но они не смогут идеально точно захватить элемент. В Chrome для этого есть специальная команда Capture node screenshot.

Сначала откройте вкладку Elements и выберите нужный элемент. Затем выполните команду.



Вот результат:



Использование результата последней операции в консоли


Мы часто отлаживаем код в консоли. Допустим, вы хотите узнать, как перевернуть строку в JavaScript. Вы ищете нужную информацию в Интернете, и вам попадается вот такой фрагмент кода.

'abcde'.split('').reverse().join('')




Да, этот код переворачивает строку. Но вам пока непонятно, как работают методы split(), reverse(), join() и какой результат выдает каждый из них. Вы можете выполнить этот код пошагово, записав его как-то так:



Теперь мы знаем, какое значение возвращает каждый метод.
Но зачем так много писать? В такой длинной записи легко допустить ошибку, и ее сложно понять. Открою секрет: в консоли есть волшебная переменная $_, которая хранит результат последней операции.



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



Повторная отправка запроса XHR


Во фронтенд-проектах часто приходится использовать XHR для отправки запросов на получение данных с сервера. Что делать, если нужно отправить запрос повторно?

Неопытные разработчики обновляют страницу, но это очень неудобно. В Chrome мы можем отладить код прямо на вкладке Network.



  • Откройте вкладку Network.
  • Нажмите кнопку XHR.
  • Выберите запрос XHR, отправку которого вы хотите повторить.
  • Выберите Replay XHR в контекстном меню, чтобы повторить запрос.


Вот анимированный пример:



Отслеживание статуса загрузки страницы


На полную загрузку страницы может уйти более десяти секунд. В таких случаях нужно контролировать процесс загрузки страницы в каждый отдельный момент времени.

В Chrome DevTools можно делать скриншоты страницы в ходе ее загрузки, поставив галочку напротив Capture Screenshots на вкладке Network.



Выберите скриншот, чтобы посмотреть информацию о соответствующих сетевых запросах. Такая визуализация даст вам лучшее представление о сетевых запросах, которые отправляются на сервер в отдельные моменты времени.



Копирование переменных


Вы знаете, как скопировать значение переменной JavaScript в буфер обмена?
Это кажется невыполнимой задачей, но в Chrome для ее решения предусмотрена специальная функция copy.



ECMAScript не содержит определения функции copy, это функция Chrome. С ее помощью можно скопировать значение переменной JavaScript в буфер обмена.

Копирование изображения как URI с приставкой data:


Есть два способа вставить изображение на страницу: можно дать ссылку на внешний файл или внедрить изображение при помощи data: URL.

Data: URL (URL с приставкой data:) это схема, позволяющая встраивать небольшие файлы в документ в качестве строковых элементов.Раньше она называлась data: URI, но WHATWG отказалась от этого названия.

Встраивание маленьких изображений непосредственно в код по схеме data: URL сокращает количество HTTP-запросов к серверу, благодаря чему страница загружается быстрее.
Как это сделать в Chrome?

Посмотрите анимацию:



Вывод массива объектов в таблицу


Допустим, у нас есть массив объектов:

let users = [{name: 'Jon', age: 22},  {name: 'bitfish', age: 30},  {name: 'Alice', age: 33}]




Воспринимать такую информацию в консоли тяжело. А если массив длиннее и содержит более сложные элементы, то потеряться в нем еще проще.
К счастью, в Chrome есть функция, которая выводит массив объектов в таблицу.



Она вам пригодится, и не раз.

Перетаскивание на вкладке Elements


Иногда нужно переместить некоторые элементы DOM на странице, чтобы протестировать пользовательский интерфейс. На вкладке Elements можно перетащить любой HTML-элемент в любое место в коде:



В этом примере я перетащил элемент на вкладке Elements, и его расположение на веб-странице тоже моментально изменилось.

Обращение к текущему выделенному элементу в консоли


$0 это еще одна волшебная переменная, которая содержит элемент, выделенный на вкладке Elements.



Активация псевдоклассов CSS


Псевдоклассы позволяют задать стиль для элемента не только в зависимости от его расположения в дереве документа, но и в зависимости от внешних факторов, таких как история просмотра (например, :visited), состояние контента (например, :checked в некоторых формах), положение указателя мыши (например, псевдокласс :hover изменяет стиль элемента при наведении на него указателя мыши).

Для одного элемента можно написать несколько псевдоклассов. Чтобы было проще тестировать стили, псевдоклассы можно активировать прямо на вкладке Elements.



Пример


Посмотрите на код страницы:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>body{font-size: 150px;}div:hover{color: red;}div:active{color: blue;}div:focus{color: brown;}</style></head><body><div>hello world</div></body></html>


Открываем страницу в браузере, на вкладке Elements проверяем, как работают псевдоклассы, и при необходимости вносим изменения.



Горячая клавиша для скрытия элементов


Во время отладки CSS-стилей часто возникает необходимость скрыть элемент. В Chrome это делается быстро: достаточно лишь выделить элемент и нажать клавишу H.


Нажмите H на клавиатуре

Эта операция применяет к элементу стиль visibility: hidden !important;.

Сохранение элемента DOM в качестве глобальной временной переменной


Если мы хотим быстро сослаться на элемент DOM в консоли, можно сделать это так:

  • Выбрать элемент.
  • Открыть контекстное меню правой кнопкой мыши.
  • Выбрать Store as a global variable (Сохранить как глобальную переменную).


Подробнее..

Детальный разбор навигации в Flutter

24.07.2020 14:12:14 | Автор: admin

Навигация во Flutter


image


Flutter набирает популярность среди разработчиков. Большенство подходов впостроении приложений уже устоялись иприменяются ежедневно вразработке E-commerce приложений. Тема навигации опускают навторой или третий план. Какой API навигации предоставляет Фреймворк? Какие подходы выработаны? Как использовать эти подходы иначто они годятся?


Введение


Начнём с того, что такое навигация? Навигация это метод который позволяет перемещаться между пользовательским интерфейсом с заданными параметрами.
К примеру в IOS мире организовывает навигацию UIViewController, а в Android Navigation component. А чтопредоставляет Flutter?



Экраны в Flutter называются route. Для перемещениями между route существует класс Navigator который имеющий обширный API для реализации различных видов навигации.



Начнём спростого.Навигация нановый экран(route) вызывается методом push() который принимает всебя один аргумент это Route.


Navigator.push(MaterialPageRoute(builder: (BuildContext context) => MyPage()));

Давайте детальнее разберёмся ввызове метода push:


Navigator Виджет, который управляет навигацией.
Navigator.push() метод который добавляет новый route виерархию виджетов.
MaterialPageRoute() Модальный route, который заменяет весь экран адаптивным кплатформе переходом анимации.
builder обязательный аргумент конструктора MaterialPageRoute, который возвращает пользовательский интерфейс Фреймворк для отрисовки.
[MyPage](https://miro.medium.com/max/664/1Xm96KtLeIAAMtAYWcr1-MA.png)* пользовательский интерфейс реализованный при помощи Stateful/Stateless Widget


Возвращение на предыдущий route


Для возвращения с экрана на предыдущий необходимо использовать метод pop().


Navigator.pop();

Переда данных между экранами


Данные на новый экран передаются через конструктор при создании экрана в builder методе. Этот принцип работает в методах Navigator, где нужно реализовать метод build.


Navigator.push(context, MaterialPageRoute(builder: (context) => MyPage(someData: data)));

В примере продемонстрирована передача данных в класс MyPage (в этом классе хранится пользовательский интерфейс).


Для того чтобы передать данные на предыдущий экран нужно вызвать метод pop() и передать опциональным аргументом туда данные.


Navigator.pop(data);


Состояние виджета Navigator, который вызван внутри одного из видов MaterialApp/CupertinoApp/WidgetsApp. State отвечает за хранение истории навигации и предоставляет API для управления историей.
Базовые методы навигации повторяют структуру данных Stack. В диаграмме можно наблюдать методы и "call flow" NavigatorState.


http://personeltest.ru/aways/habrastorage.org/webt/5w/dg/nb/5wdgnb-tjlngub4c8y4rlpqkeqi.png


Императивный vs Декларативный подход в навигации


Во всех примерах которые были приведены выше использовался императивный подход в навигации. Разница между императивным и декларативным подходом в том как будет выглядеть вызов нового route, и кто будет хранить реализацию перехода.


Давайте на простом примере:


Императивный подход , отвечает на вопрос как?
Пример: Я вижу, что тот угловой столик свободен. Мы пойдём туда и сядем там.


Декларативный подход, отвечает на вопрос что?
Пример: Столик для двоих, пожалуйста.


Для более глубокого понимания разницы советую прочитать эту статью Imperative vs Declarative Programming


Императивная навигация


Вернёмся к реализации навигации. В императивном подходе описывается детали работы в вызывающем коде. В нашем случае это поля Route. В Flutter много типов route, например MaterialPageRoute и CupertinoPageRoute. Например в CupertinoPageRoute задаётся title, или settings.


Пример:


Navigator.push(    context,    CupertinoPageRoute<T>(        title: "Setting",        builder: (BuildContext context) => MyPage(),        settings: RouteSettings(name:"my_page"),    ),);

Этот код и знания о новом route будут хранитьсяв ViewModel/Controller/BLoC/ У этот подхода существует недостаток.


Представим что потребовалось внести изменения в конструкторе в MyPage или в CupertinoPageRoute. Нужно искать каждый вызов метода push в проекте и изменять кодовую базу.


Вывод:


Этот подход не имеет единообразный подход к навигации, и знание о реализации route проникает в бизнес логику.

Декларативная навигация


Принцип декларативной навигации заключается в использовании декларативного подхода в программировании. Давайте разберём на примере.


Пример:


Navigator.pushNamed(context, '/my_page');

Принцип императивной навигации выглядит куда проще. Говорите "Отправь пользователя на экран настроек" передавая путь одним из аргументов навигации.
Для хранении реализации роста в самом Фреймворке предусмотрен механизм у MaterialApp/CupertinoApp/WidgetsApp. Это 2 колбэка onGenerateRoute и onUnknownRoute отвеспющие за хранение деталей реализации.


Пример:


MaterialApp(    onUnknownRoute: (settings) => CupertinoPageRoute(      builder: (context) {                return UndefinedView(name: settings.name);            }    ),  onGenerateRoute: (settings) {    if (settings.name == '\my_page') {      return CupertinoPageRoute(                title: "MyPage",                settings: settings,        builder: (context) => MyPage(),      );    }        // Тут будут описание других роутов  },);

Разберёмся подробнее в реализации:
Метод onGenerateRoute данный метод срабатывает когда был вызван Navigator.pushNamed(). Метод должен вернуть route.
Метод onUnknownRoute срабатывает когда метод onGenerateRoute вернул null. должен вернуть дефолтный route, по аналогии с web сайтами 404 page.


Этот принцип упрощает вызывающий код и хранить логику переходов в одном едином месте с возможностью переиспользования кодовой базы внутри. Но так же существует недостаток, это универсальность для разных типов раутов.

Диалоговые и модальные окна


Для того чтобы вызвать диалоговое окна разного типа в Фреймворке предусмотрены глобальные методы. Давайте разберёмся в типах диалоговых окон.


Методы для вызова диалоговых и модальных окон:


  • showAboutDialog
  • showBottomSheet
  • showDatePicker
  • showGeneralDialog
  • showMenu
  • showModalBottomSheet
  • showSearch
  • showTimePicker
  • showCupertinoDialog
  • showDialog
  • showLicensePage
  • showCupertinoModalPopup

Эти методы покрывают базовые потребности разработчиков которые хотят работать с окнами. Если не хватает этого API, тогда стоит разобраться как эти методы работают.


Как работает это под капотом?


Давайте рассмотрим исходный код одного из методов, например showGeneralDialog.


Исходный код:


Future<T> showGeneralDialog<T>({  @required BuildContext context,  @required RoutePageBuilder pageBuilder,  bool barrierDismissible,  String barrierLabel,  Color barrierColor,  Duration transitionDuration,  RouteTransitionsBuilder transitionBuilder,  bool useRootNavigator = true,  RouteSettings routeSettings,}) {  assert(pageBuilder != null);  assert(useRootNavigator != null);  assert(!barrierDismissible || barrierLabel != null);  return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(_DialogRoute<T>(    pageBuilder: pageBuilder,    barrierDismissible: barrierDismissible,    barrierLabel: barrierLabel,    barrierColor: barrierColor,    transitionDuration: transitionDuration,    transitionBuilder: transitionBuilder,    settings: routeSettings,  ));}

Давайте детальнее разберёмся в устройстве этого метода. showGeneralDialog вызывает метод push у NavigatorState с _DialogRoute(). Нижнее подчёркивание обозначает что этот класс приватный и используется только в пределах области видимости в которой сам описан, то есть в пределах этого файла.


Диалоговые и модальные окна которые отображаются при помощи глобальных методов это кастомные route которые реализованы разработчиками Фреймворка.

Типы route в фреймворке


Теперь понятно что"every thins is a route", то есть что связанное с навигацией. Давайте взглянем на то, какие route уже реализованы в Фреймворке.


Два основных route в Flutter это PageRoute и PopupRoute.


PageRoute Модальный route, который заменяет весь экран.
PopupRoute Модальный route, который накладывает виджет поверх текущего route.


Реализации PageRoute:


  • MaterialPageRoute
  • CupertinoPageRoute
  • _SearchPageRoute
  • PageRouteBuilder

Реализации PopupRoute:


  • _ContexMenuRoute
  • _DialogRoute
  • _ModalBottomSheetRoute
  • _CupertinoModalPopupRoute
  • _PopupMenuRoute

Реализация PopupRoute приватна и скрыты для внешних потребителей, но роуты используют глобальные методы для показа диалоговых и модальных окон.


Вывод:


Универсальный метод для декларативной навигации из капота реализовать невозможно, так как нужно учесть навигацию не только между экранами.

Best practices


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


Начнём с того что мы сделаем некий сервис который будет будет соблюдать следующим аспектам:


  • Декларативный вызов навигации.
  • Отказ от использования BuildContext для навигации (Это критично если сервис навигации будет вызываться в компонентах, в которых нет возможности получить BuildContext).
  • Модульность. Можно вызвать любой route, CupertinoPageRoute, BottomSheetRoute, DialogRoute и т.д.

Для нашего сервиса навигации нам понадобится интерфейс:


abstract class IRouter {  Future<T> routeTo<T extends Object>(RouteBundle bundle);  Future<bool> back<T extends Object>({T data, bool rootNavigator});  GlobalKey<NavigatorState> rootNavigatorKey;}

Разберём методы:
routeTo - выполняет навигацию на новый экран.
back возвращает на предыдущий экран.
rootNavigatorKey GlobalKey умеющий вызывать методы NavigatorState.

После того как мы сделали интерфейс навигации, давайте сделаем реализацию этого интерфейса.


class Router implements IRouter {    @override  GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();    @override  Future<T> routeTo<T>(RouteBundle bundle) async {   // Push logic here  }    @override  Future<bool> back<T>({T data, bool rootNavigator = false}) async {        // Back logic here    }}

Супер, теперь нам нужно реализовать метод routeTo().


@override  Future<T> routeTo<T>(RouteBundle bundle) async {        assert(bundle != null, "The bundle [RouteBundle.bundle] is null");    NavigatorState rootState = rootNavigatorKey.currentState;    assert(rootState != null, 'rootState [NavigatorState] is null');    switch (bundle.route) {      case "/routeExample":        return await rootState.push(          _buildRoute<T>(            bundle: bundle,            child: RouteExample(),          ),        );      case "/my_page":        return await rootState.push(          _buildRoute<T>(            bundle: bundle,            child: MyPage(),          ),        );      default:        throw Exception('Route is not found');    }  }

Данный метод вызывает у root NavigatorState (который описан в WidgetsApp) метод push и конфигурирует его относительно RouteBundle который приходит одним из аргументов в данный метод.


Теперь нужно реализовать класс RouteBundle. Это просто модель, которая хранит в себе набор полей для конфигурации.


enum ContainerType {  /// The parent type is [Scaffold].  ///  /// In IOS route with an iOS transition [CupertinoPageRoute].  /// In Android route with an Android transition [MaterialPageRoute].  ///  scaffold,  /// Used for show child in dialog.  ///  /// Route with [DialogRoute].  dialog,  /// Used for show child in [BottomSheet].  ///  /// Route with [ModalBottomSheetRoute].  bottomSheet,  /// Used for show child only.  /// [AppBar] and other features is not implemented.  window,}class RouteBundle {  /// Creates a bundle that can be used for [Router].  RouteBundle({    this.route,    this.containerType,  });  /// The route for current navigation.  ///  /// See [Routes] for details.  final String route;  /// The current status of this animation.  final ContainerType containerType;}

enum ContainerType тип контейнера, котрый будет задаваться декларативно из вызываемого кода.
RouteBundle класс-холдер данных отвечающих конфигурацию нового route.


Как вы могли заметить у я использовал метод _buildRoute. Именно он отвечает за то, кой тип route будет вызван.


Route<T> _buildRoute<T>({@required RouteBundle bundle, @required Widget child}) {    assert(bundle.containerType != null, "The bundle.containerType [RouteBundle.containerType] is null");    switch (bundle.containerType) {      case ContainerType.scaffold:        return CupertinoPageRoute<T>(          title: bundle.title,          builder: (BuildContext context) => child,          settings: RouteSettings(name: bundle.route),        );      case ContainerType.dialog:        return DialogRoute<T>(          title: '123',          settings: RouteSettings(name: bundle.route),          pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {                        return child;                    },        );      case ContainerType.bottomSheet:        return ModalBottomSheetRoute<T>(          settings: RouteSettings(name: bundle.route),          isScrollControlled: true,          builder: (BuildContext context) => child,        );      case ContainerType.window:        return CupertinoPageRoute<T>(          settings: RouteSettings(name: bundle.route),          builder: (BuildContext context) => child,        );      default:        throw Exception('ContainerType is not found');    }  }

Думаю что в этой функции стоит рассказать о ModalBottomSheetRoute и DialogRoute, которые использую. Исходный код этих route позаимствован из раздела Material исходного кода Flutter.


Осталось сделать метод back.


@overrideFuture<bool> back<T>({T data, bool rootNavigator = false}) async {    NavigatorState rootState = rootNavigatorKey.currentState;  return await (rootState).maybePop<T>(data);}

Ну и конечно перед использованием сервиса необходимо передать rootNavigatorKey в App следующим образом:


MaterialApp(    navigatorKey: widget.router.rootNavigatorKey,    home: Home());

Кодовая база для нашего сервиса готова, давайте вызовем наш route. Для этого создадим инстанс нашего сервиса и каким-либо образом "прокинуть" в объект, который будет вызывать этот инстанс, например при помощи Dependency Injection.



router.routeTo(RouteBundle(route: '/my_page', containerType: ContainerType.window));

Теперь мы имеем единый подход к навигации, позволяющий решить вышеперечисленные проблемы:


  • Декларативный вызов навигации
  • Отказ от BuildContext по средствам GlobalKey
  • Модульность достигнута возможностью конфигурирования route относительно имени пути и контейнера для View

Итог


В Фреймворке Flutter существуют различные методы для навигации, которые дают преимущества и недостатки.


Ну и конечно полезные ссылки:
Мой телеграм канал
Мои друзья Flutter Dev Podcast
Вакансии Flutter разработчиков

Подробнее..

Перевод Пишем чат с использованием Spring Boot и WebSockets

26.08.2020 18:04:13 | Автор: admin


Всем привет. В преддверии старта курса Разработчик на Spring Framework мы подготовили для вас еще один полезный перевод. Но, прежде чем перейти к статье, хотим поделиться с вами бесплатной записью урока от наших преподавателей по теме: Рефакторинг кода приложений на Spring, а также предлагаем посмотреть запись вебинара из которого вы сможете подробно узнать о программе курса и формате обучения.


А теперь перейдем к статье


В статье Building Scalable Facebook-like Notification using Server-Sent Event and Redis для отправки сообщений от сервера клиенту мы использовали Server-sent Events. Также там было упомянуто о WebSocket технологии двунаправленной связи между сервером и клиентом.

В этой статье мы посмотрим на один из распространенных примеров использования WebSocket. Мы напишем приложение для обмена приватными сообщениями.

Ниже на видео продемонстрировано то, что мы собираемся сделать.

https://www.google.com/url?q=https://youtu.be/fgfSyAQD24k&sa=D&ust=1598454677145000&usg=AFQjCNEMeXm-44vGZ6p9UR1KURKO9HEaMA

Введение в WebSockets и STOMP


WebSocket это протокол для двусторонней связи между сервером и клиентом.
WebSocket, в отличие от HTTP, протокола прикладного уровня, является протоколом транспортного уровня (TCP). Хотя для первоначальной установки соединения используется HTTP, но потом соединение обновляется до TCP-соединения, используемого в WebSocket.

WebSocket протокол низкого уровня, который не определяет форматы сообщений. Поэтому WebSocket RFC определяет подпротоколы, описывающие структуру и стандарты сообщений. Мы будем использовать STOMP поверх WebSockets (STOMP over WebSockets).

Протокол STOMP (Simple / Streaming Text Oriented Message Protocol) определяет правила обмена сообщениями между сервером и клиентом.

STOMP похож на HTTP и работает поверх TCP, используя следующие команды:

  • CONNECT
  • SUBSCRIBE
  • UNSUBSCRIBE
  • SEND
  • BEGIN
  • COMMIT
  • ACK


Спецификацию и полный список команд STOMP можно найти здесь.

Архитектура




  • Сервис аутентификации (Auth Service) ответственен за аутентификацию и управление пользователями. Здесь мы не будем изобретать колесо и воспользуемся сервисом аутентификации из статьи JWT and Social Authentication using Spring Boot.
  • Сервис чата (Chat Service) ответственен за настройку WebSocket, обработку STOMP-сообщений, а также за сохранение и обработку сообщений пользователей.
  • Клиент (Chat Client) это приложение на ReactJS, использующее STOMP-клиента для подключения и подписки на чат. Также здесь находится пользовательский интерфейс.


Модель сообщения


Первое, о чем нужно подумать это модель сообщения. ChatMessage выглядит следующим образом:

public class ChatMessage {   @Id   private String id;   private String chatId;   private String senderId;   private String recipientId;   private String senderName;   private String recipientName;   private String content;   private Date timestamp;   private MessageStatus status;}


Класс ChatMessage довольно простой, с полями, необходимыми для идентификации отправителя и получателя.

В нем также есть поле статуса, указывающее доставлено ли сообщение клиенту.

public enum MessageStatus {    RECEIVED, DELIVERED}


Когда сервер получает сообщение из чата, он не отправляет сообщение адресату напрямую, а отправляет уведомление (ChatNotification), чтобы оповестить клиента о получении нового сообщения. После этого клиент сам может получить новое сообщение. Как только клиент получит сообщение, оно помечается как доставленное (DELIVERED).

Уведомление выглядит следующим образом:

public class ChatNotification {    private String id;    private String senderId;    private String senderName;}


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





Настройка WebSocket и STOMP в Spring


Первым делом настраиваем конечную точку STOMP и брокер сообщений.

Для этого создаем класс WebSocketConfig с аннотациями @Configuration и @EnableWebSocketMessageBroker.

@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer {    @Override    public void configureMessageBroker(MessageBrokerRegistry config) {        config.enableSimpleBroker( "/user");        config.setApplicationDestinationPrefixes("/app");        config.setUserDestinationPrefix("/user");    }    @Override    public void registerStompEndpoints(StompEndpointRegistry registry) {        registry                .addEndpoint("/ws")                .setAllowedOrigins("*")                .withSockJS();    }    @Override    public boolean configureMessageConverters(List<MessageConverter> messageConverters) {        DefaultContentTypeResolver resolver = new DefaultContentTypeResolver();        resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();        converter.setObjectMapper(new ObjectMapper());        converter.setContentTypeResolver(resolver);        messageConverters.add(converter);        return false;    }}


Первый метод конфигурирует простой брокер сообщений в памяти с одним адресом с префиксом /user для отправки и получения сообщений. Адреса с префиксом /app предназначены для сообщений, обрабатываемых методами с аннотацией @MessageMapping, которые мы обсудим в следующем разделе.

Второй метод регистрирует конечную точку STOMP /ws. Эта конечная точка будет использоваться клиентами для подключения к STOMP-серверу. Здесь также включается резервный SockJS, который будет использоваться, если WebSocket будет недоступен.

Последний метод настраивает конвертер JSON, который используется Spring'ом для преобразования сообщений из/в JSON.

Контроллер для обработки сообщений


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

@Controllerpublic class ChatController {    @Autowired private SimpMessagingTemplate messagingTemplate;    @Autowired private ChatMessageService chatMessageService;    @Autowired private ChatRoomService chatRoomService;    @MessageMapping("/chat")    public void processMessage(@Payload ChatMessage chatMessage) {        var chatId = chatRoomService                .getChatId(chatMessage.getSenderId(), chatMessage.getRecipientId(), true);        chatMessage.setChatId(chatId.get());        ChatMessage saved = chatMessageService.save(chatMessage);                messagingTemplate.convertAndSendToUser(                chatMessage.getRecipientId(),"/queue/messages",                new ChatNotification(                        saved.getId(),                        saved.getSenderId(),                        saved.getSenderName()));    }}


С помощью аннотации @MessageMapping мы настраиваем, что при отправке сообщения в /app/chat вызывается метод processMessage. Обратите внимание, что к маппингу добавится сконфигурированный ранее application-префикс /app.

Этот метод сохраняет сообщение в MongoDB, а затем вызывает метод convertAndSendToUser для отправки уведомления адресату.

Метод convertAndSendToUser добавляет префикс /user и recipientId к адресу /queue/messages. Конечный адрес будет выглядеть так:

/user/{recipientId}/queue/messages


Все подписчики данного адреса (в нашем случае один) получат сообщение.

Генерация chatId


Для каждой беседы между двумя пользователями мы создаем чат-комнату и для ее идентификации генерируем уникальный chatId.

Класс ChatRoom выглядит следующим образом:

public class ChatRoom {    private String id;    private String chatId;    private String senderId;    private String recipientId;}


Значение chatId равно конкатенации senderId_recipientId. Для каждой беседы мы сохраняем две сущности с одинаковыми chatId: одну между отправителем и получателем, а другую между получателем и отправителем, чтобы оба пользователя получали одинаковый chatId.

JavaScript-клиент


В этом разделе мы создадим JavaScript-клиента, который будет отправлять сообщения на WebSocket/STOMP-сервер и получать их оттуда.

Мы будем использовать SockJS и Stomp.js для общения с сервером с использованием STOMP over WebSocket.

const connect = () => {    const Stomp = require("stompjs");    var SockJS = require("sockjs-client");    SockJS = new SockJS("http://localhost:8080/ws");    stompClient = Stomp.over(SockJS);    stompClient.connect({}, onConnected, onError);  };


Метод connect() устанавливает соединение с /ws, где ожидает подключений наш сервер, и также определяет callback-функцию onConnected, которая будет вызвана при успешном подключении, и onError, вызываемую, если при подключении к серверу произошла ошибка.

const onConnected = () => {    console.log("connected");    stompClient.subscribe(      "/user/" + currentUser.id + "/queue/messages",      onMessageReceived    );  };


Метод onConnected() подписывается на определенный адрес и получает все отправляемые туда сообщения.

const sendMessage = (msg) => {    if (msg.trim() !== "") {      const message = {        senderId: currentUser.id,        recipientId: activeContact.id,        senderName: currentUser.name,        recipientName: activeContact.name,        content: msg,        timestamp: new Date(),      };              stompClient.send("/app/chat", {}, JSON.stringify(message));    }  };


В конце метода sendMessage() сообщение отправляется по адресу /app/chat, который указан в нашем контроллере.

Заключение


В этой статье мы рассмотрели все важные моменты создания чата с использованием Spring Boot и STOMP over WebSocket.
Мы также создали JavaScript-клиент с применением библиотек SockJs и Stomp.js.

Пример исходного кода можно найти здесь.



Узнать о курсе подробнее.




Читать ещё:


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru