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

Html

Создаем приложение для ANDROID быстро и просто

26.05.2021 20:08:54 | Автор: admin

Сегодня я хотел бы поделиться с Вами, как быстро и просто можно создать приложение для Android с базовыми знаниями HTML CSS и JS. По данному примеру код на Java для Android будет минимальным. Благодаря платформе XAMARIN приложения для мобильных телефонов можно делать в Visual Studio.

Шаг 1 - Переходим на сайт и Скачиваем бесплатную версию Community.



Шаг 2 - Запускаем установку и выбираем параметры. Нас интересует XAMARIN. Но Вы также можете выбрать другие параметры.



После успешной установки мы можем создать свой первый проект.

Шаг 3 - Запускаем Visual Studio. Создать проект. В фильтре пишем xamarin, платформа Android, язык c# (Если желаете другой язык можете его выбрать)


Шаг 4 - Далее. Указываете имя для своего приложения, выбираете каталог где его сохранить. Создать.


Шаг 5 - Указываем пустое приложение и выбираем минимальную версию андроида для запуска этого приложения.


Шаг 6 - Жмем ок. Visual Studio автоматически создает код для приложения



Мы можем его запустить в эмуляторе, который идет комплекте с Visual Studio нажав клавишу F5.



Шаг 7 - Теперь немного модифицируем код. В данном случае мы вообще не будем использовать Java. Так как мы будем кодить на C#.



Приводим код к такому виду. Здесь мы создаем WebView контейнер который будет грузить локальный HTML файл, который находится в проекте в папке Assets.

public class MainActivity : AppCompatActivity    {        WebView mWebview; //это контейнер для просмотра HTML        protected override void OnCreate(Bundle savedInstanceState)        {            base.OnCreate(savedInstanceState);            Xamarin.Essentials.Platform.Init(this, savedInstanceState);                       mWebview = new WebView(this);            mWebview.Settings.JavaScriptEnabled = true; //это разрешение работа JS скриптов            mWebview.Settings.DomStorageEnabled = true; //это разрешение на запись в память браузера            mWebview.Settings.BuiltInZoomControls = true; //это разрешение на масштабирование пальцами щипком            mWebview.Settings.DisplayZoomControls = false; //это запрет вывода кнопок масштаба            mWebview.Settings.CacheMode = CacheModes.NoCache; //это отключает либо включает кэширование данных             mWebview.LoadUrl($"file:///android_asset/Content/login.html"); //это загрузка локального файла из папки Asset/Content            SetContentView(mWebview);         }        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)        {            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);        }    }

Шаг 8 - Создадим там папку Content.



Шаг 9 - Добавим в папку Content файл login.html



Шаг 10 - Далее уже пишем на привычном нам HTML CSS JS. Можем нажать на F5 и увидеть результат нашей работы.



По такому принципу можно создать приложение быстро и просто. Файлы html будут выглядеть одинаково на всех устройствах. То есть, Вы можете сделать приложения для Android и iOS с одинаковым интерфейсом. Не надо изучать сложные языки разметки, не надо изучать сложные макеты (сториборды) на iOS. Все можно сделать на HTML.

В идеале, вместо локальных файлов можно сделать загрузку со стороннего сайта. В этом случае Вы можете менять контент приложения без его обновления в AppStore и Google Play.
Q: Но как быть с функциями самой платформы? Пуш сообщения? Как взаимодействовать с самой платформой?
Все очень просто! JavaScript можно использовать для вызова функций Android:

Шаг 1 - Немного модифицируем наш файл MainActivity



//добавляем интерфейс для javascript            mWebview.AddJavascriptInterface(new JavaScriptInterface(), "interface");              //

Шаг 2 - Далее создаем класс JavaScriptInterface на который будет ругаться Visual Studio



  public class JavaScriptInterface : Java.Lang.Object    {        [JavascriptInterface]        [Export("alert")]  //здесь мы указываем название функции вызываемой из html файла interface.alert('сообщение пользователю');        public void alert(string data)        {            Toast.MakeText(Application.Context, data, ToastLength.Short).Show();//здесь Андроид выведет сообщение посредством Toast        }    }

Мы видим, что теперь программа ругается на Export так как не знает что это такое.

Шаг 3 - Добавим нужную библиотеку



Шаг 4 - В фильтре напишем mono



Шаг 5 - Найдем Export и поставим галочку



Шаг 6 - Жмем ок и видим что ошибка пропала.

Так вы можете подключать библиотеки если вдруг Visual Studio ругается на что то.

Toast.MakeText(Application.Context, data, ToastLength.Short).Show();

Данная функция это показ всплывающей информации на экране. Она выполняется именно на платформе Андроида. То есть мы можем написать в HTML файле вызов функции Андроида. Получается полное дружелюбие двух платформ по JavaScript интерфейсу. Данные можно передавать туда сюда. Вызывать переход от одной активити в другую. Все через HTML + JavaScript.

Немного модифицируем файл login.htm:



<html><head>    <style>        h1 {            color: yellowgreen;        }    </style></head><body>    <h1>Привет мир</h1>    <button onclick="sendToAndroid();">Нажми меня</button>    <script>        function sendToAndroid() {            //здесь мы запускаем функцию андроида из HTML файла по javacsript интерфейсу            interface.alert("текст сообщения");        }    </script></body></html>

жмем F5



Теперь при нажатии на кнопку HTML вызывается функция Toast андроида и выводиться сообщение пользователю.

Спасибо за внимание.

P.s. Полный листинг MainActivity

using Android.App;using Android.OS;using Android.Runtime;using Android.Webkit;using Android.Widget;using AndroidX.AppCompat.App;using Java.Interop;namespace MyFirstApp{    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]    public class MainActivity : AppCompatActivity    {        WebView mWebview; //это контейнер для просмотра HTML        protected override void OnCreate(Bundle savedInstanceState)        {            base.OnCreate(savedInstanceState);            Xamarin.Essentials.Platform.Init(this, savedInstanceState);            mWebview = new WebView(this);            mWebview.Settings.JavaScriptEnabled = true; //это разрешение работа JS скриптов            mWebview.Settings.DomStorageEnabled = true; //это разрешение на запись в память браузера            mWebview.Settings.BuiltInZoomControls = true; //это разрешение на масштабирование пальцами щипком            mWebview.Settings.DisplayZoomControls = false; //это запрет вывода кнопок масштаба            mWebview.Settings.CacheMode = CacheModes.NoCache; //это отключает либо включает кэширование данных            //добавляем интерфейс для javascript            mWebview.AddJavascriptInterface(new JavaScriptInterface(), "interface");                         //            mWebview.LoadUrl($"file:///android_asset/Content/login.html"); //это загрузка локального файла из папки Asset/Content            SetContentView(mWebview);        }        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)        {            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);        }    }    public class JavaScriptInterface : Java.Lang.Object    {        [JavascriptInterface]        [Export("alert")]        public void alert(string data)        {            Toast.MakeText(Application.Context, data, ToastLength.Short).Show();        }    }}



Подробнее..

Перевод Самый популярный HTML-редактор в выдаче Google это афера с поисковой оптимизацией

08.06.2021 14:19:38 | Автор: admin
Это история о том, как я случайно обнаружил масштабную аферу с поисковой оптимизацией.

Резюме


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

Инструменты, которые, кажется, делают это, созданы одними и теми же людьми:

  • html-cleaner.com
  • html-online.com/editor/
  • html5-editor.net
  • htmlg.com
  • и другие

Жертвами этого стали сайты BoingBoing, официальная футбольная ассоциация Германии и Kaspersky. Забавно, что хакнутая статья Касперского посвящена защите от хакеров.

Обнаружение подозрительного


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

Все началось с загадки продукта, который я создаю. Продукт представляет собой онлайн-табло, и, несмотря на наличие (что я думаю) надежной стратегии SEO, мне не удалось завоевать желанное первое место на странице результатов поисковой системы. Последние 12 месяцев меня всегда опережал участник под названием Scorecounter.

image

Теперь, в связи с особенностями моего продукта, люди делятся ссылками на него и встраивают его на свои веб-сайты, а это означает, что со временем я накапливаю много входящих ссылок. Со временем мой рейтинг SEO станет непревзойденным. У конкурента нет такой заметной виральности, как у меня, но он у меня всегда лучше, чем у SEO. Какой трюк они используют?

Итак, вчера вечером я выпил 2 стакана красного вина и вместо того, чтобы удалять производственную базу данных (как в прошлый раз), я решил разобраться в ней. Я заплатил за подписку Ahrefs и взглянул на профиль обратных ссылок Scorecounter. Вот что я нашел:

image

Scorecounter имеет 3600 входящих ссылок, которые он накопил за очень короткое время. Впечатляющие!

Затем я начал просматривать страницы, содержащие ссылки, и именно здесь у меня возникли подозрения.

Например, я видел сообщение в блоге Немецкой футбольной ассоциации, содержащее ссылку на Scorecounter. Слово, на которое была сделана ссылка, было оценка (score), но наличие ссылки здесь не имело абсолютно никакого смысла в контексте статьи. Что происходило?

Вот еще несколько примеров ссылок, которые я нашел на случайных доменах (вам нужно искать на странице оценка).


У них есть страницы и страницы (посмотрите сами, если у вас есть учетная запись Ahrefs).

Поэтому я написал несколько электронных писем на 1-2 из этих сайтов и спросил их, почему на их страницах есть эти ссылки. Все ли эти сайты продавали ссылки?

Я получил следующий ответ от новостного онлайн-портала:

Спасибо, что обратились к нам. Нет, мы не продаем ссылки ни за какие деньги.

На самом деле я использовал очиститель HTML (html-online.com/editor/) с прошлого года, который в первые месяцы работал нормально, как и предполагалось, но несколько недель назад я понял, что инструмент внезапно начал тайно вводить ссылки в содержание HTML документа.

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

Бинго


Ага! Вот в чем был секрет: создатели Scorecounter также сделали онлайн-редактор HTML, который вставляет ссылки по определенным ключевым словам. Прелесть этой аферы в том, что, внедрив ссылки в свой собственный редактор HTML, они создали замечательный цикл положительной обратной связи: чем выше он поднимается в рейтинге поиска, тем больше людей используют его и тем больше секретных ссылок они могут ввести.

Теперь, если вы чувствуете себя очень великодушным, вы можете возразить, что редактор это инструмент freemium, а добавленные ссылки это то, как вы платите за бесплатную версию. Что ж, я не чувствую себя великодушным, подозреваю, что и Google тоже.

Помимо улучшения самого HTML-редактора и Scorecounter, я нашел третий продукт, который пользовался всеобщим вниманием:

Кубик Рубика


Ruwix.com создан теми же людьми и посвящен знаменитой головоломке. Опять же, очень легко найти большое количество обратных ссылок на Ruwix.com на случайных сайтах с помощью Ahrefs.com. Каждая из этих статей представляет собой несоответствие в тексте, в который они втиснуты, что показывает мне, что авторы этих статей понятия не имели, что происходит. Взгляните (на странице нужно искать Rubiks):


UPD: Касперский удалил ссылку, но у меня есть скриншот:
image

Чтобы узнать, насколько распространена эта инъекция, попробуйте поискать в Google: Learn how to solve a Rubix Cube with the beginner method. Более 600 посещений на самых разных сайтах. Удивительно, но ссылка даже пробралась в исследовательскую статью (она находится на странице 24, внизу раздела Ссылки)!

Целая сеть инструментов


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

  • htmltidy.net
  • html-css-js.com
  • divtable.com
  • html-cleaner.com
  • html5-editor.net
  • htmlg.com

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

Внедрение ссылок упоминается в Условиях Использования этих инструментов


Это правда, что условия использования по крайней мере одного инструмента содержат следующее:

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

Я искренне сомневаюсь, что этого отказа от ответственности достаточно, чтобы предотвратить кару от Google. Скоро мы это выясним.
Подробнее..

Перевод Взгляд на Tailwind CSS

23.05.2021 18:15:00 | Автор: admin

В этом году я видел много шумихи вокруг популярного фреймворка CSS, Tailwind CSS. И подумал, что поделюсь некоторыми мыслями и опасениями по поводу этого фреймворка UI. Я приобрёл небольшой опыт написания CSS с подходом utility-first (полезность прежде всего), когда начал свою карьеру в разработке интерфейсов, несколько лет назад.

К старту курса о Frontend-разработке делимся статьёй, автор которой решил, возможно, несколько изменить то, как воспринимается Tailwind, рассказать как о положительных, так и об отрицательных сторонах фреймворка, особенно в контексте работы с мультиязычными сайтами.


Вещи, которые я считаю интересными

Вам не нужно закрывать файл с HTML

Основной заголовок на официальном сайте Tailwind гласит:

Быстрое создание современных веб-сайтов, даже не покидая HTML.

Я согласен, что писать код в одном месте может быть быстрее, чем переключаться между разными файлами. Однако оставить в стороне свой HTML для меня не проблема. Это может помочь переключить контекст между разметкой и стилями. Разделение файлов HTML и CSS может помочь мне лучше сосредоточиться на выполнении поставленной задачи. Однако, когда разметка и стили смешиваются, когда вы работаете над сложным, многоязычным, отзывчивым сайтом и пользовательским интерфейсом с темами, всё может пойти наперекосяк.

Когда я работаю с Tailwind, это похоже на то, как если бы я держал две ручки: одну для набросков, а другую для раскрашивания. Одновременное написание разметки и CSS напоминает мне эти две ручки.

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

Проектные ограничения

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

Именование классов CSS

О названиях классов CSS думать не нужно и это может ускорить реализацию макетов в вебе. Я нахожу это полезным в случае, когда ваш босс пишет вам, что есть срочный лендинг, который должен быть сделан сегодня, и у вас только 3 часа, чтобы написать его. Tailwind может помочь быстро достичь результата.

Однако, если вы придерживаетесь разделения разметки и стилей, разработка займёт гораздо больше времени. Ещё одно полезное применение Tailwind это соревнование или хакатон. Самое главное на таких событиях сделать работу и что-то получить за неё. Никому не будет дела до того, как вы написали CSS, верно?

То, с чем я не согласен

Tailwind не фреймворк utility-first

Подзаголовок на их веб-сайте сообщает, что CSS Tailwind:

Прежде всего утилитарный CSS-фреймворк, содержит такие классы, как...

Из увиденного я сделал вывод, что Tailwind это только утилитарный (utility-only) фреймворк. Может быть, название "только утилитарный" повлияет на то, как его воспримут новички? Я редко вижу какой-то сайт, использующий Tailwind и применяющий концепцию utility-first.

Длинный список классов может запутать

Обратите внимание на то, что я знаю о методе @apply. Рассмотрим пример из документации Tailwind:

<input  class="block appearance-none bg-white placeholder-gray-600 border border-indigo-200 rounded w-full py-3 px-4 text-gray-700 leading-5 focus:outline-none focus:border-indigo-400 focus:placeholder-gray-400 focus:ring-2 focus:ring-indigo-200"  placeholder="jane@example.com"/>

Это поле ввода с 17 классами CSS. Что проще: читать классы один за одним горизонтально или сканировать их сверху вниз? Вот так поле будет выглядеть стиль этого поля в файле CSS:

.c-input {  display: block;  appearance: none;  background-color: #fff;  @include placeholder(grey);  border: 1px solid rgba(199, 210, 254, var(--tw-border-opacity));  border-radius: 0.25rem;  padding: 0.75rem 1rem;  line-height: 1.25rem;  color: rgba(55, 65, 81, var(--tw-text-opacity));}.c-input:focus {  /* Focus styles.. */}

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

Я знаю о методе @apply, но каждый раз, когда я думаю о нём, я прихожу к выводу, что он противоречит основной концепции Tailwind. Вот тот же пример (поле ввода):

.c-input {  @apply block appearance-none bg-white placeholder-gray-600 border border-indigo-200 rounded w-full py-3 px-4 text-gray-700 leading-5 focus:outline-none focus:border-indigo-400 focus:placeholder-gray-400 focus:ring-2 focus:ring-indigo-200;}

Посмотрите на длину списка классов. Если в Tailwind в приоритете полезность, то почему в официальной документации Tailwind или в Tailwind UI мы редко видим @apply? Опять же, я вижу Tailwind как только утилитарный фреймворк.

Всегда нужно давать имена элементам

В дизайн-системе трудно обсуждать конкретный компонент, не договорившись о его названии. Причина вот в чём: вы не отправите своему коллеге сообщение вроде такого:

Привет, есть новости о bg-white w-full py-3 px-4?

На самом деле фраза будет такой:

Есть новости о дизайне поляризованной карты?

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

<person class="hair-brown length-[175] face-rounded"></person>

Код выше бессмыслица. Гораздо лучше такой код:

<person class="ahmad"></person>

Некоторые классы запутывают

Когда я только начинал использовать Tailwind, мне нужно было добавить класс, который отвечает за свойство align-items: center. Моей первой мыслью было воспользоваться align-center, но это не сработало.

Я посмотрел в документацию и впал в замешательство. Класс items-center добавит CSS-свойство align-items: center, где класс align-middle будет содержать vertical-align: middle. Чтобы запомнить их, требуется немного мышечной памяти.

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

Tailwind затрудняет настройку дизайна в браузере

Я занимаюсь как дизайном, так и frontend-разработкой, поэтому редактирование в браузере с помощью DevTools для меня крайне важно. С Tailwind работа в DevTools может стать сложнее. Скажем, например, я хочу изменить отступы для компонента. Когда я изменяю значение класса py-3, это влияет на каждый использующий его элемент страницы.

Единственный способ убрать его открыть меню .cls в DevTools, после чего класс можно будет переключить. Однако это не решает проблему. Что я могу сделать в этом случае? Добавить встроенный стиль через DevTools, а это мне не нравится. Проблема решится, если просто давать элементам названия. Добавлю к этому факт: общий размер файла полной сборки Tailwind составляет 12 МБ. Редактирование CSS в DevTools будет очень медленным.

Это означает, что разработка в браузере неэффективна. Недавно команда Tailwind выпустила компилятор JIT (just in time), удаляющий неиспользуемый CSS. Это мешает всей идее дизайна в браузере.

Я набрал back и получил длинный список всех доступных классов CSS. При JIT-компиляции таких подсказок не будет.

Tailwind неудобен для многоязычных сайтов

Чтобы вы больше понимали, добавлю: я работаю над веб-сайтами, которые должны работать как на английском (с направлением слева направо, LTR), так и на арабском (с направлением справа налево, RTL). Рассмотрим такой код:

/* LTR: left to right */.c-input {  padding-left: 2rem;}

В отдельном файле CSS для RTL стиль будет выглядеть так:

/* RTL: Right to left */.c-input {  padding-left: 0;  padding-right: 2rem;}

В приведённом выше примере padding следует развернуть в зависимости от направления страницы (слева направо или справа налево). Для этого уже есть плагины, но они решают проблему только с внешней стороны. Первый найденный мной делает следующее:

html[dir="rtl"] .ml-2 {   margin-right: 1rem; }

Для меня это не очень хорошее решение. Второй найденный плагин немного отличался от первого:

[dir="rtl"] .rtl\:text-2xl {  font-size: 1.5rem;  line-height: 2rem;}

Такой код может очень быстро выйти из-под контроля. Исследуя один веб-сайт, я заметил более 30 классов CSS.

Это пример того, как Taillwind может выйти из-под контроля, особенно у новичков. Тридцать классов для компонента кнопки это слишком много. В таком случае я лучше поработаю со встроенными (inline) стилями.

Чтобы помочь в создании многоязычного веб-сайта, сейчас я использую Bi-App Sass. Вот пример:

.elem {  display: flex;  @include margin-left(10px);  @include border-right(2px solid #000);}

Код скомпилируется в два разных файла CSS. Файл ltr:

/* LTR Styles */.elem {  display: flex;  margin-left: 10px;  border-right: 2px solid #000;}

Подробнее о стилизации RTL читайте в этом руководстве от вашего покорного слуги.

Я не всегда работаю с шаблонами

Одна из проблем Tailwind заключается в том, что, если у вас есть список карточек и вы хотите изменить определённый набор классов, вам придётся вручную просматривать их в редакторе кода. Это не будет проблемой, если вы используете в своём продукте частичные файлы CSS (partial) или компоненты. Вы можете один раз написать HTML, и любое изменение будет отражено везде, где используется этот компонент.

Это не всегда так. Я работаю над простыми страницами index.html, где усилия в разделении на части или компоненты себя не оправдывают. В этом случае работа с Tailwind и редактирование CSS могут стать процессом, чреватым ошибками, поскольку вы даже не можете использовать функцию "Найти и заменить": она может пропустить некоторые другие элементы на странице.

Tailwind делает веб-сайты похожими

Команда Tailwind создала набор продуманных компонентов под названием Tailwind UI, которые легко настраиваются и готовы к использованию в вашем проекте.

Внешний вид и функциональность компонентов приятные, и мне это нравится. Хотя есть проблемы, если сайт использует Tailwind UI, есть большая вероятность, что я предскажу использовать его.

Обычно, когда вы работа Tailwind UI, это означает, что у вас нет времени на создание индивидуального дизайна, поэтому вам нужно что-то, что можно быстро запустить, верно? И это хороший вариант применения, за исключением одной детали: применение Tailwind приведёт к тому, что многие сайты будут выглядеть похожими друг на друга, подобно тому, что было много лет назад с Bootstrap.

Некоторые свойства или особенности CSS использовать невозможно

К примеру, не включено свойство clip-path, и я полностью понимаю причину. Предположим, мы хотим включить его как компонент. Где мы должны написать код? Здесь:

<article class="block appearance-none bg-white placeholder-gray-600 border border-indigo-200 rounded w-full py-3></article>

Либо примерно так включить его в конфигурацию Tailwind:

// tailwind.config.jsmodule.exports = {  theme: {    clipPath: {      // Configure your clip-path values here    }  }};

Или же сделать следующее:

.card {  @apply block appearance-none bg-white placeholder-gray-600 border border-indigo-200 rounded w-full py-3;  clip-path: inset(20px 20px 50px 20px);}

Заключительные мысли

Может ли Tailwind оборачивать CSS в свои классы во время компиляции?

Представьте себе, что Tailwind появляется только во время компиляции. То есть вы пишете обычный CSS с именованием и всё такое, а умный компилятор преобразует ваш CSS в утилитарные классы. Это было бы воплощением мечты.

Утилитарные классы мощный инструмент, если не перестараться

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

Фронтенд довольно часто выбирают как точку входа в IT. Но фронтенд это не только внешнее оформление сайтов, но и работа с базами данных, а также внешними API. Фронтенд требует системной, комплексной подготовки. Если вам или вашим знакомым интересно это направление разработки можете присмотреться к нашему курсу о Frontend-разработке, где мы касаемся не только вышеупомянутых тем, но и разработки отзывчивых сайтов при помощи flexbox, работаем с методологией БЭМ и затрагиваем другие аспекты фронтенда.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 468 (17 23 мая 2021)

24.05.2021 00:09:13 | Автор: admin
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.


Медиа|Веб-разработка|CSS|JavaScript|Браузеры


Медиа


podcast Подкаст Веб-стандарты #282: Rome, CloudFront Functions, кроссбраузерность, has() и другой современный CSS, мониторинг, GDE
podcast Подкаст Фронтенд Юность #187: Bootstrap круче чем все сраные фреймворки
podcast Подкаст Callback Hell: Производительность CSS-in-JS, языки логического программирования, ООП в современном фронтенде
podcast Новости 512 от CSSSR: Angular 12, Deno 1.10, мониторинг, тестирование UI, :has(), курс по git, Rome + $, TypeScript 4.3 RC
podcast Подкаст Callback Hell Поддержка нескольких мажорных версий, венчурный капитал в Open Source и возвращение тонкого клиента
podcast Подкаст proConf #96: DeveloperWeek 2020
podcast video Подкаст Цинковый Прод #113: Сайт сына маминой подруги

Веб-разработка


W3C представил черновой вариант стандарта WebGPU
en Google AMP мертв! AMP-страницы больше не пользуются приоритетом в поиске Google
en Incremental Static Regeneration: создавайте статические сайты понемногу
en Тестирование фронтенд-приложений что, где, как?





CSS


habr Трюки CSS, которые сделают из вас ниндзя верстки
habr Взгляд на Tailwind CSS
en Новая отзывчивость: веб-дизайн в мире компонентов
en Нет, утилитарные классы это не то же самое, что инлайн стили
en Как создать неоновый текст с помощью CSS
en Как стилизовать любое поле ввода советы и методы
en 82% разработчиков неправильно проходят этот трехстрочный тест по CSS
en Learn CSS Постоянно обновляемый курс CSS и справочник для повышения вашего уровня знаний в области стилизации веба
en aspect-ratio

JavaScript


habr Швейцарский нож отладки JavaScript
habr Трасси что? Доклад Яндекса
en DOM Events изучение системы событий DOM с помощью визуального исследования
en ES12 сделает вашу жизнь проще
en Справочник по массивам JavaScript методы работы с JS-массивами с примерами
en Двухмерные оптические демки в Javascript
en JavaScript API для распознавания людей и ботов в Chrome







Браузеры


habr Microsoft прекратит поддержку приложения Internet Explorer 11 в Windows 10 с июня 2022 года
habr Кросс-браузерный трекинг на основе перебора обработчика внешних протоколов
В Chrome экспериментируют с поддержкой RSS, чисткой User-Agent и автосменой паролей
Компания Mozilla представила режим строгой изоляции сайтов для Firefox
Выпуск перенастраиваемого web-браузера Nyxt 2.0.0

Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Подробнее..

HTML и CSS ошибки, ухудшающие UX

24.05.2021 10:16:39 | Автор: admin
В прошлом году я собрал несколько случаев, когда HTML и CSS ошибки негативно влияют на доступность интерфейсов. В этой статье я хочу продолжить и описать еще несколько случаев.


Не мучайте пользователей свойствами justify-content и align-items


Когда мы решаем задачи по позиционированию элементов, нам нравится использовать свойства justify-content и align-items. Но мало кто знает, что эти свойства могут провести к мукам пользователя. Особенно часто проблемы связаны с вертикальным позиционированием.

Это связано с особенностями работы свойств, а именно свойства justify-content и align-items не учитывают размеры flex-элементов. Соответственно в случае когда размеры flex-элементов больше размеров flex-контейнера, то flex-элементы будут выходить за его пределы, и могут отображаться некорректно.

Например, я часто не могу закрыть модальное окно, потому что кнопка-крестик вышла за пределы окна и не доступна. Вы можете увидеть этот эффект на изображении ниже.

Эта проблема легко решается с помощью свойства margin со значением auto. Внутри flex-контейнера значение auto будет рассчитано с учетом размера самого контейнера и размеров flex-элементов. Если размеры flex-элементов меньше, чем размеры flex-контейнера, то браузер рассчитает отступ автоматически. А если больше, то элементы прижмутся к границам flex-контейнера.

Не делайте так

<div class="modal">  <div class="modal__main"></div></div>


.modal {  display: flex;  justify-content: center;  align-items: center;}


Можно делать так

<div class="modal">  <div class="modal__main"></div></div>


.modal {  display: flex;}.modal__main {  margin: auto;}




Не заставляйте пользователей ждать загрузки кастомных шрифтов


Сегодня мы часто используем кастомные шрифты, чтобы наши интерфейсы выглядели более уникальными. Из-за того, что в большинстве случаев такие шрифты не установлены в операционных системах, нам нужно загружать их. Но это занимается какое-то время и появляется проблема. А что нам показывать в этот момент?

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

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

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

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

Не делайте так

@font-face {  font-family: "Baloo Tamma";  src: url("balotamma.woff2") format("woff2"),       url("balotamma.woff") format("woff");}


Можно делать так

@font-face {  font-family: "Baloo Tamma";  src: url("balotamma.woff2") format("woff2"),       url("balotamma.woff") format("woff");  font-display: swap;}




Не ломайте SVG иконками интерфейсы


Когда вы используете SVG иконки внутри HTML, обратите внимание, что вам нужно уставить атрибуты width и height. Если вы это не сделаете и установите ширину и высоту через CSS, то ваш интерфейс будет сломан.

В эпоху фреймворков CSS может сработать не сразу. Я не понимаю как это получилось, но я видел приложения на React, в которых cначала отображались огромные иконки, а только потом они принимали нужный размер. Поэтому просто установите размеры через атрибуты width и height, и ваши интерфейсы будут пуленепробиваемыми.

Не делайте так

<svg   xmlns="http://personeltest.ru/away/www.w3.org/2000/svg"  viewBox="0 0 448 512">    <path fill="currentColor" d="..."></path></svg>


svg {  width: 0.875rem;  height: 1rem;}


Можно делать так

<svg   xmlns="http://personeltest.ru/away/www.w3.org/2000/svg"  viewBox="0 0 448 512"  width="0.875rem"  height="1rem">    <path fill="currentColor" d="..."></path></svg>


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


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

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

Например, мы может создать 2 source элемента и определить через медиа-запросы, какое изображение загружать на планшетах, а какое на десктопах. Кроме того, для отображения изображения на мобильных устройствах мы будем использовать элемент img.

Не делайте так

<img   src="ferrari-1920x1080.jpg"  alt="yellow ferrari F8 spider on the background of the ocean">


Можно делать так

<picture>  <source     srcset="ferrari-1200x960.jpg"    media="(min-width: 641px) and (max-width: 1200px)">  <source     srcset="ferrari-1920x1080.jpg"    media="(min-width: 1201px)">   <img     src="ferrari-640x480.jpg"    alt="yellow ferrari F8 spider on the background of the ocean"></picture>

Также мы можем оптимизировать загрузку изображений для retina-экранов. Для этого мы будем использовать дескриптор плотности.

Например, если у смартфона плотность пикселя 2x, то браузер загрузит ferrari-640x480-2x.jpg, а если 1x, то ferrari-640x480-1x.jpg. Такой же механизм сработает для планшетов и десктопных экранов.

Не делайте так

<img   src="ferrari-1920x1080.jpg"  alt="yellow ferrari F8 spider on the background of the ocean">


Можно делать так

<img   src="ferrari-1x.jpg"  srcset="ferrari-2x.jpg 2x"  alt="yellow ferrari F8 spider on the background of the ocean"><!-- или  --><picture>  <source     srcset="ferrari-1200x960-1x.jpg, ferrari-1200x960-2x.jpg 2x"    media="(min-width: 641px) and (max-width: 1200px)">  <source     srcset="ferrari-1920x1080-1x.jpg, ferrari-1920x1080-2x.jpg 2x"    media="(min-width: 1201px)">   <img     src="ferrari-640x480-1x.jpg, ferrari-640x480-2x.jpg 2x"    alt="yellow ferrari F8 spider on the background of the ocean"></picture>


P.S: Если у вас есть вопросы по CSS/HTML, то, не стесняйтесь, пишите мне на мою почту. Она указана в моем профиле на Хабре.
Подробнее..
Категории: Html , Css , Usability , Accessibility , Ux , Front-end

Перевод Самая серьёзная проблема HTML? Разработчики, разработчики, разработчики

27.05.2021 12:14:32 | Автор: admin
image

Мы можем насмехаться над Баллмером за этот полубезумный нервный срыв, но его месседж попал в цель. Если не дать разработчикам инструменты и знания, которые необходимы для работы с вашей системой, они не только будут испытывать трудности, но и не смогут развивать то, над чем вы работаете!

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

Ни в одной области это так не очевидно, как в высокомерном, пофигистском, а то и откровенно презрительном отношении к HTML. Нет пределов для бесконечных благоглупостей и ошибочных заявлений от тех, кто не способен написать ни одной строки на этом языке.

Если в двух словах, разработчики воспринимают HTML недостаточно серьёзно, но что произойдёт, если вы укажете им на их слабость? В ответ мы дождёмся только бесконечный поток бессмысленных оправданий того, почему их нельзя отвлекать на его правильную реализацию!

Список слабых оправданий


HTML это ненастоящий язык программирования

Это последовательность команд, которым следуют компьютеры для выполнения задачи. Если и есть другое определение языка программирования, то за четыре десятка лет написания ПО я его не слышал. Является ли от Тьюринг-полным? Нет но тем не менее он сообщает компьютеру, как передать грамматическое и структурное значение контента в машинонезависимом виде. Есть правила по использованию его тэгов, порядка и синтаксиса.

Никого не волнует код, если для пользователя он выглядит правильно

Ровно до того момента, как вы не столкнётесь с незрячими пользователями. HTML это не только то, как выглядит страница Нет! Исправлюсь HTML вообще не о том, как что-то выглядит. HTML нужен для того, что сообщить, какими должны быть элементы с точки зрения грамматики и структуры, чтобы user-agent мог передать это значение пользователю. Поэтому для описания того, как должны выглядеть элементы, у нас есть CSS. Если любой из ваших тэгов, id или классов сообщает о том, как элементы должны выглядеть, то вы выбрали неподходящий код, исходя из неверных предпосылок.

Скринридеры (ПО для чтения страницы вслух), электронные книги со шрифтом Брайля, TTY всё это невизуальные целевые объекты; и не забывайте, что у поисковых движков тоже нет глаз. Им нет ни малейшей разницы, как выглядит ваша страница.

Кроме того, людям важно, что ваша страница медленнее или её хостинг обходится дороже, или она нарушает правила доступности для людей с ограничениями, или она забивает весь доступный канал. Несемантическая разметка, бесконечные и бессмысленные DIV, не делающие ничего, presentational classes все они складываются и влияют на результат!

Вы будете слышать те же оправдания для множества других аспектов веб-разработки, и почти всегда это ложь. Будь то плохая/поломанная семантика, проблемы с доступностью для людей с ограничениями, раздутый необязательный JS, использование технологий веб-приложений для того, что приложением быть не должно, и т.д., и т.п.

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

А как насчёт следующего несчастного неудачника, которому придётся поддерживать результат вашего труда, в котором собраны все ошибки из списка, как НЕ нужно использовать HTML? Люди всегда болтают о том, как их ужасный поломанный фреймворк должен помогать в совместной работе. Как в два или десять раз больший объём кода, не соответствующий базовым правилам HTML и нарушающий само предназначение этого языка, может улучшить совместную работу?

Но примеры во фреймворках работают именно так, а их писали специалисты

Они не специалисты в веб-разработке. Скорее, специалисты в маркетинге, пропаганде и обмане. Разметка в примерах таких систем, как Bootstrap и Tailwind это кошмарные практики HTML. Они воняют ужасной смесью заявлений я не хочу изучать HTML и CSS и я скучаю по разметке 1990-х, отказываясь от двадцати с лишним лет прогресса. Только потому, что их используют миллионы сайтов (большинство не может ошибаться), а самозванные эксперты поют им хвалебные оды (апелляция к авторитету), не делает их или подобные практики хорошими.

С ванильным кодом работать сложнее

Сложным делаете его вы. В том-то и проблема: игнорируя семантические правила структурирования и весь смысл предназначения HTML, вы усложняете работу с ним. Способствует этому и то, что приманки для нубов наподобие W3Schools (aka W3fools) переполнены неточной информацией, вульгарными упрощениями и полным отсутствием большинства из базовых концепций HTML.

Контент должен определять разметку, контент + разметка + целевая среда/возможности user-agent должны определять структуру. Следуя базовой семантике и благодаря постепенному совершенствованию и использованию правильного разделения функциональности, вы в конечном итоге получите набор инструкций, позволяющий с лёгкостью создавать простые в поддержке страницы. Если у вас возникают с этим проблемы и вы считаете, что эти фреймворки HTML/CSS упрощают вашу жизнь, значит, вы недостаточно хорошо знаете HTML или CSS для выполнения хоть каких-то задач.

Вообще, Tailwind проще, чем ванильные HTML/CSS, достаточно просто выучить более 500 классов, 90% из которых уже существуют в виде свойств CSS, а затем игнорировать почти все правила того, как должен использоваться HTML!

Если вы не поняли, это был сарказм.

Вы придаёте слишком большую важность HTML



Я постоянно слышу эту чушь, и меня раздражает её недальновидность!

Это как говорить, что я уделяю слишком много внимания почве под стройплощадкой или материалам, используемым для создания фундамента. Если вы пренебрегаете такими деталями, то не удивляйтесь, что потом всё непонятным образом обвалится или будет затянуто в карстовую воронку!

HTML это фундамент и краеугольный камень. Если им пренебречь, то не удивляйтесь, что результаты будут полной катастрофой.

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

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

Но HTML не даёт нам инструментов, которые нужны для обеспечения UX

Это оправдание имеет множество разных версий, но в целом они подразумевают написанное выше. Говорящие это почти всегда относятся к поклонникам веб-приложений или одностраничных приложений, они пытаются использовать JavaScript везде, они не заботятся о доступности для людей с ограничениями и настаивают, что пользователям почему-то нужны все их модные причиндалы для улучшения user experience.

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

И результаты их работы вы УЖЕ СЕЙЧАС видите во всей нашей отрасли: хрупкие, распухшие, медленные решения, которые способен улучшить скриптинг, настолько затормаживают системы корзин интернет-магазинов, что многие из них даже неспособны поддерживать аптайм (привет, Zotac); при этом пользователи ожесточённо жмут на F5, надеясь, что им всё-таки удастся купить видеокарту. Из-за перезагрузки всей страницы целиком и повторного запуска скрипта все функции приложения приводят только к УМЕНЬШЕНИЮ скорости загрузки страницы. И ещё сильнее это проявляется, если вы плюёте на разметку, пользуясь presentational classes.

А поскольку скрипты можно отключать, и генерируемый скриптами контент сложнее для скринридеров, электронных книг со шрифтом Брайля, и так далее, одностраничные приложения (single-page application, SPA) нарушают правила доступности для людей с ограничениями.

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

И если вы думаете, что использование скриптинга для этого действительно улучшает user experience, то вы, очевидно, не тестировали систему на реальных пользователях и реальном трафике! По крайней мере, не выполняли реального сравнения разделения задач с использованием кэша в загрузках нормальных страниц и на страницах с новомодными скриптами.

То есть во всём виноват веб-разработчик?


Ни в коем случае. Вернёмся к началу статьи и к крикам Баллмера разработчики, разработчики, разработчики.

Когда он разыгрывал свою небольшую сценку, она была призвана решить проблему того, что в конце 90-х Windows ни в коем случае не была на первом месте, потому что разработчики часто не использовали предоставляемые компанией Microsoft инструменты. Лучшую документацию по Windows API напечатала Borland. Люди использовали инструменты не от Microsoft, потому что визуальные языки считались игрушками. Они так сильно отставали от технологий веб-разработки, то можно сказать, что они и сейчас пытаются их догнать!

У W3C и WhatWG есть похожие проблемы с тем что так называемые спецификации попросту написаны не для людей, которые пишут веб-сайты. Позвольте мне повторить: спецификация языка, используемого для написания веб-сайтов, предназначена не для людей, которые на самом деле пишут веб-сайты. Она написана для людей, которые пишут user-agents! Браузер это user-agent, но UA не всегда браузер.

На самом деле, это такой абсурд, что идиотическая версия динамичного документа WhatWG ссылается на MDN, чтобы её могли понять простые смертные.

Примечание: я даже не буду начинать рассуждения о том, насколько тупой является сама идея динамичного документа (living document), особенно потому что в реальных HTML-документах отсутствует отслеживание версий. HTML 5, который был валиден в прошлом году, становится сегодня невалидным HTML 5, а сегодняшний валидный HTML 5 может стать невалидным завтра? Отличный способ сделать валидацию совершенно бесполезной!

Простой факт: для получения описаний значений тэгов на простом английском приходится обращаться к сторонним источникам, многие из которых даже не согласуются друг с другом. Более того, W3C стала совершенно беззубой, она слепо соглашается со всем, что говорит WhatWG, даже несмотря на то, что WhatWG многократно доказала, что она не имеет достаточной квалификации для создания потомка HTML 4 Strict. Принятие EMBED в качестве валидного тэга, создание и/или поддержка тэгов, избыточных относительно OBJECT, больше не поддерживаемый (к счастью) тэг HGROUP, показавший, что они даже не понимают, для чего нужны нумерованные заголовки и как их использовать По признанию многих, кто над ним работал, задача HTML 5 на самом деле никогда не заключалась в создании спецификации или стандарта, говорящего нам, как создавать полезные веб-сайты! Она заключалась в документировании того, делают ли сегодня люди правильно или неправильно, и того, что браузеры могут поддерживать, но не того, что они должны поддерживать! Учитывая то, что во время разработки HTML 5 большинство разработчиков всё ещё вбивало HTML 3.2 и набрасывало поверх него извращённый doctype HTML 4, к чему удивляться, что всё оказалось таким скоплением плохих, устаревших и старомодных практик?

Если уж разработчики не воспринимают HTML достаточно серьёзно, то обвинять в этом стоит тех, кто разрабатывал его как спецификацию; даже назвать подобное именем HTML 5 было таким серьёзным преступлением против веб-разработки Как преступлением против музыки стало вручение Грэмми Билли Эйлиш за её бесталанные творения.

W3C и WhatWG даже не воспринимаются серьёзно другими организациями по стандартизации, и на то есть причина.

Каким же должно быть решение?


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

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

Но нам нужна не просто улучшенная официальная документация, необходимо урезать язык, сделать его более ориентированным на задачи. Возродить многие идеи, которые содержались в HTML 5 до того, как W3C выкинула их на помойку и приняла версию WhatWG. Тот факт, что Microsoft потратила десятки лет на то, чтобы IE препятствовал нам использовать OBJECT ещё не причина не только сохранять тэг IMG, но и добавлять множество новых тэгов без нужды (VIDEO, AUDIO). Просто потому, что художники и жулики от маркетинга любят открывать для пользователя новые окна, нравится ему это или нет, ещё не причина того, чтобы в спецификации было TARGET="_BLANK".

Более того, ЯВНОЕ использование и значения тэгов необходимо поместить в центр спецификации, а может быть, даже в отдельное руководство по использованию для тех, кто по-прежнему живёт в 1997 году.

Создание более простой, чистой и полезной версии HTML, которая станет руководством для нас всех не такая уж сложная задача.

Кроме того, нам полезно будет, если при её создании меньший вес будут иметь разработчики браузеров. Microsoft, Mozilla, Apple и Google имеют огромное влияние на W3C и WhatWG, и это совершенно неэтично. Их вес в процессе принятия решений противоречит самой концепции свободного и открытого веба.



На правах рекламы


Эпичные серверы это VDS для размещения сайтов от маленького интернет-магазина на Opencart до серьёзных проектов с огромной аудиторией. Создавайте собственные конфигурации серверов в пару кликов!

Присоединяйтесь к нашему чату в Telegram.

Подробнее..

Google документы станут полновесными с 1 июня. Пишем скрипт для обхода этого ограничения

27.05.2021 18:04:31 | Автор: admin

Предыстория

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

Я часто работаю с Google документами, и при активном использовании дисковая квота закончится довольно быстро. Но есть и хорошая новость: документы, созданные до 1 июня 2021 года так и останутся невесомыми, поэтому вы не получите превышение квоты в одночасье.

У меня сразу возникла мысль сделать документов "в запас". Ниже я расскажу, как это можно осуществить, не тратя много времени и сил.

Пишем скрипт, создающий документы

Скрипт буду писать с помощью Google Apps Script.

Создаём новую таблицу Google, заходим в редактор скриптов (Инструменты - Редактор скриптов).

Создаём три файла

  • main.gs - основной код для создания файлов

  • menu.gs - код для создания пользовательского меню при открытии таблицы

  • index.html - шаблон страницы для отображения информации

Сначала в файле menu.gs создаём функцию onOpen(). Это простой триггер, который выполняется при открытии таблицы. Его задача - создать пользовательское меню, из которого можно запустить функцию.

function onOpen(e) { // Выполняется при открытии таблицы  SpreadsheetApp.getUi() // Получаем интерфейс пользователя      .createMenu('Меню')// Создаём меню      .addItem('Создать документы', 'main') // Создаём команду меню      .addToUi();// Добавляем меню в интерфейс}

В файле main.gs создадим функцию main(), которая и будет запускаться с кнопки в меню.

function main() { // Меню - Создать документы  // Создаём HTML документ из шаблона  let template = HtmlService.createTemplateFromFile(`index`);  // Показываем модальное окно с HTML   SpreadsheetApp.getUi()    .showModelessDialog(template.evaluate(),`Создаю документы...`);}

Пора разобраться с index.html. это обычный HTML файл, который отобразится в модальном окне. там можно использовать стили, скрипты и т.п.

index.html
<!doctype html><html lang="en">  <head>    <!-- Required meta tags -->    <meta charset="utf-8">    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">    <!-- Bootstrap CSS -->    <link rel="stylesheet" href="http://personeltest.ru/aways/maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">    <script>      let timer = function(){ // Обслуживает таймер        // Получаем текущее время        let now = (new Date()).getTime();                // Задаём режим обновления таймера - 1 раз в секунду        setInterval(function(){          // Получаем прошедшее время. now будет доступно из-за замыкания.          let time = (new Date()).getTime() - now;          // Вычисляем минуты          let minutes = Math.floor(time/60000);          // И секунды          let seconds = Math.floor(time%60000/1000);          // Обновляем данные в поле lifeTime          updateData("lifeTime", `${minutes<10?"0"+minutes:minutes}:${seconds<10?"0"+seconds:seconds}`);        },1000);      };            updateData = function(id, value){ // Обновляет информацию в одном элементе по id        let element = document.getElementById(id);        if (element) {          element.innerHTML = value;        };      };            refreshData = function(){ // Обновляем все данные        google.script.run.withSuccessHandler(function(data){        updateData("createTables", data.createTables); // Таблиц создано:        updateData("createDocs", data.createDocs);// Документов создано:        updateData("createForms", data.createForms);// Форм создано        updateData("createSlides", data.createSlides);// Презентаций создано        updateData("log", data.log);// Лог      }).getData(); // Получаем данные     };      setTimeout(setInterval(refreshData, 2000),1000); // Данные обновляем раз в 2 секунды      timer();// Запуск таймера      google.script.run.doMagic();// Запуск функции для создания документов    </script>  </head>  <body>    <div class=".container bg-dark text-white text-center">      <div class="row">        <div class="col">          Таблиц        </div>                <div class="col">          Документов        </div>                <div class="col">          Форм        </div>                <div class="col">          Презентаций        </div>      </div>      <div class="row">        <div class="col" id="createTables">          0        </div>        <div class="col" id="createDocs">          0        </div>                <div class="col" id="createForms">          0        </div>                <div class="col" id="createSlides">          0        </div>      </div>    </div>         <div class=".container bg-dark text-white">      <div class="row">        <div class="col text-right" id="label_lifeTime">          Время работы:          </div>                <div class="col  text-left" id="lifeTime">          00:00        </div>              </div>    </div>    <div bg-dark text-white id="label_log">Лог: </div>    <ul class="list-group" id="log">    </ul>  </body></html>

В ней подключаем стили, создаём скрипт, в котором три функции:

  • updateData(id, value) - ищет на странице элемент по id и обновляет содержимое

  • refreshData() - обновляет все данные на странице, кроме таймера

  • timer() - обслуживает таймер

Для создания документов в файле main.gs создаём универсальную функцию.

function create()
const FILES_TO_CREATE = 50;function create(filesToCreate = FILES_TO_CREATE, folderId, prefix="file_", app, key) {  // Получаем словарь ключ-значение для текущего скрипта.  let props = PropertiesService.getScriptProperties();  // Получаем значение для ключа data. Преобразуем в объект  let data = JSON.parse(props.getProperty(`data`));  try{    // Получаем директорию по id    let folder = DriveApp.getFolderById(folderId);    for(var i=0; i<filesToCreate; i++){ // Создаём filesToCreate документов      // Создаём документ, получаем его id      let ssId = app.create(`${prefix}${(new Date()).getTime()}`).getId();      // Получаем файл по его id      let ss = DriveApp.getFileById(ssId);      // Копируем файл в папку      folder.addFile(ss);      // Удаляем файл из корневой папки      DriveApp.getRootFolder().removeFile(ss);      // Увеличиваем счётчик созданных файлов      data[key]=1+data[key];      // Сохраняем новые данные      props.setProperty(`data`, JSON.stringify(data));    };  }catch(err){    // В случае ошибки - пишем её в лог    logToHtml(`Error: ${err}`, LOG_TYPES.danger);  };  // Возвращаем из функции количество созданных файлов  return +i;};

И конкретные реализации - для таблиц, документов, форм и презентаций.

Обёртки для функции create()
function createSheets(key) {  // Получаем id папки для таблиц  let folderId =  PropertiesService.getScriptProperties().getProperty(`sheetsFolder`);  // Создаём нужное количество таблиц  let count = create(FILES_TO_CREATE, folderId, `sheet_`, SpreadsheetApp, key);  // Сохраняем в лог  logToHtml(`${count} sheets were created`, LOG_TYPES.success);}function createDocs(key) {  let folderId =  PropertiesService.getScriptProperties().getProperty(`docsFolder`);  let count = create(FILES_TO_CREATE, folderId, `doc_`, DocumentApp, key);  logToHtml(`${count} docs were created`, LOG_TYPES.success);}function createForms(key) {  let folderId =  PropertiesService.getScriptProperties().getProperty(`formsFolder`);  let count = create(FILES_TO_CREATE, folderId, `form_`, FormApp, key);  logToHtml(`${count} forms were created`, LOG_TYPES.success);}function createSlides(key) {  let folderId =  PropertiesService.getScriptProperties().getProperty(`slidesFolder`);  let count = create(FILES_TO_CREATE, folderId, `slide_`, SlidesApp, key);  logToHtml(`${count} slides were created`, LOG_TYPES.success);}

Далее делаем функцию для создания папок.

function createFolders(){  // Получаем словарь ключ-значение для текущего скрипта  let props = PropertiesService.getScriptProperties();  // Задаём структуру папок  let folders = [    {key:`rootFolder`,   name:`Прозапас`                         },    {key:`sheetsFolder`, name:`Sheets`, parentFolder:`rootFolder`},    {key:`docsFolder`,   name:`Docs`,   parentFolder:`rootFolder`},    {key:`formsFolder`,  name:`Forms`,  parentFolder:`rootFolder`},    {key:`slidesFolder`, name:`Slides`, parentFolder:`rootFolder`},    ];  // Проходим по структуре и создаём папки    folders.forEach(folder=>{      if (!props.getProperty(folder.key)){ // Если папка ещё не создана        // Если есть параметр rootFolder, то используем его, иначе выбираем корневую папку        let parentFolder = folder.parentFolder?DriveApp.getFolderById(props.getProperty(folder.parentFolder)):DriveApp.getRootFolder();        // Создаём папку        let folderId = parentFolder.createFolder(folder.name).getId();        // Сохраняем информацию о ней        props.setProperty(folder.key, folderId);      };    });}

И функцию для создания всех типов документов:

Основная функция, которая запускает создание папок и документов
function doMagic(){  let props = PropertiesService.getScriptProperties();  // Структура данных  let data = {    createTables:0,     createDocs:0,     createForms:0,     createSlides:0,     startTime:new Date(),    log:``,  };  // Сохраняем данные в словарь скрипта  props.setProperty(`data`, JSON.stringify(data));    try{    createFolders(); // Создаём папки    createSheets(`createTables`);// Создаём таблицы    createDocs(`createDocs`);// Создаём документы    createForms(`createForms`);// Создаём формы    createSlides(`createSlides`);// Создаём презентации        // Сообщаем, что всё готово    SpreadsheetApp.getUi().alert(`Готово!`);  }catch(err){    // При ошибке сообщаем об этом    SpreadsheetApp.getUi().alert(`Ошибка! ${err}`);  };};

Остаётся создать функцию для получения данных - так модальное окно может получить актуальные данные для отображения.

function getData(){ // Получает данные из словаря  // Получаем словарь ключ-значение  let props = PropertiesService.getScriptProperties();  // Получаем нужные данные и преобразуем в объект  let data = JSON.parse(props.getProperty(`data`));  return data; //Возвращаем данные};

И функция для записи строки в лог:

const LOG_TYPES = { // Типы сообщений. Взято из bootstrap  primary:   "primary",  secondary: "secondary",  success:   "success",  danger:    "danger",  warning:   "warning",  info:      "info",  };function logToHtml(log, type = LOG_TYPES.primary){  // Получаем данные  let data = getData();  // Добавляем li тег (лог отображается в виде списка) с данными  data.log+=`<li class="list-group-item text-${type}">${log}</li>\n`;  // Сохраняем данные  let props = PropertiesService.getScriptProperties();  props.setProperty(`data`, JSON.stringify(data));};

Что получилось

Остаётся только запустить скрипт из меню. При первом запуске Google запросит права - это нормально. После этого откроется окно, в котором можно наблюдать за прогрессом.

Модальное окно для визуализации прогрессаМодальное окно для визуализации прогресса

Ограничения скрипта

  • Время жизни скрипта ограничено 6 минутами. За это время он успеет создать несколько сотен документов. Можно обойти это ограничение, закинув все функции непосредственно в HTML код модального окна и обращаться к диску по API, но об этом в следующий раз

  • Есть ограничение на количество ежедневно созданных документов. Рано или поздно будет появляться ошибка Exception: Служба была вызвана слишком много раз за день: docs create. Тогда скрипт можно запустить на следующий день


TL;DR

Всё вышеописанное я собрал в таблицу, которую можно скопировать себе(Файл - Создать копию), запустить(Меню - Создать файлы) и получить к себе на диск несколько сотен файлов. При необходимости процедуру повторить.

Спасибо за внимание. Буду рад получить фидбэк по коду. Удачи!

Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 469 (24 30 мая 2021)

31.05.2021 00:23:47 | Автор: admin
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.


Медиа|Веб-разработка|CSS|JavaScript|Браузеры


Медиа


podcast Подкаст Веб-стандарты 83. Sublime Text 4, Sass, Svelte после React, Container Queries, Learn CSS, Google I/O, новые GDE
podcast Подкаст proConf #97: JavaScript for WordPress 2020
podcast Подкаст Callback Hell: Sublime Text 4 и другие редакторы, проблемы написания читаемого кода, завершение эпохи IE
podcast Новости 512 от CSSSR: Chrome 91, TypeScript 4.3, Server-Sent Events API, logux и logux/state, postTask, Parcel 2 beta 3
podcast Новости 512 от CSSSR: Sublime Text 4, PostCSS 8.3, ненадежность TypeScript, Angular DevTools, WebContainers, Google I/O 21
podcast Пилотный выпуск подкаста Goose & Duck: Babel, деньги, два гуся

Веб-разработка


habr Самая серьёзная проблема HTML? Разработчики, разработчики, разработчики
habr Использование веб-компонентов при работе над GitHub
habr Наиболее полное руководство по практическому использованию Web Speech API
en Эволюция и новое определение Jamstack
en 10 вариантов клиентских хранилищ и когда их использовать
en Нарушаете ли вы патент, публикуя PWA?
en Создайте эффект плавного наведения с помощью GSAP и SVG



CSS


habr HTML и CSS ошибки, ухудшающие UX
en Тщательный анализ CSS-in-JS
en CSS Container Queries для дизайнеров
en 25 лет CSS
en CSS Container Queries: примеры использования и стратегии миграции
en Новый способ уменьшить влияние загрузки шрифтов: дескрипторы шрифтов в CSS

JavaScript


habr Карманная книга по TypeScript. Часть 1. Основы, Часть 2. Типы на каждый день
habr 3 способа визуального извлечения данных с помощью JavaScript
en Sparkplug неоптимизирующий компилятор JavaScript
en Новые стандарты доступа к оборудованию устройств с использованием JavaScript
en 7 инструментов, трансформирующих JavaScript-разработку
en Введение в Clio lang: несложная реализация производительного critical js






Браузеры


habr Mozilla примет Manifest v3 для дополнений Firefox, но без мер против блокировщиков рекламы
Релиз Chrome 91
en Призрак Google Reader находит свой путь в новой сборке Chrome Canary

Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Подробнее..

EasyUI действительно easy?

31.05.2021 12:16:05 | Автор: admin

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

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

Раньше создание такого интерфейса вызывало серьёзную головную боль у программистов, но теперь для избавления от неё выпущено большое количество различных фреймворков и библиотек. Казалось бы ура, проблема решена! Однако, теперь перед нами встаёт другой вопрос: какой препарат выбрать пенталгин или панадол?

Вопрос нелёгкий, и решать, в итоге, вам. Я же расскажу о своём лекарстве: библиотеке EasyIU, предназначенной для создания полноценных одностраничных веб-приложений (SPA) и основанной на jQuery, Angular, Vue и React.

Моё знакомство с EasyUI началось около двух лет назад, когда наша команда приступила к разработке софта для системы управления питанием и его интеллектуального распределения между потребителями. Управляющее устройство имело на борту Linux и кроме распределения питания должно было обмениваться данными с различными периферийными устройствами, уметь контролировать их, а также принимать показания от большого количества (до нескольких сотен) датчиков. Правила управления могли изменяться, позволяя пользователю настроить работу всего комплекса для выполнения необходимых задач.

Настройка и мониторинг устройства могли вестись через различные протоколы: ssh, snmp, redfish, BACnet, но основным способом общения с ним был http, то есть всем комплексом можно было управлять через обыкновенный веб-браузер. Это широко используемое решение, и оно не сулило никаких проблем. Однако, дьявол всё же основательно порылся в деталях.

Веб-страница должна была отражать состав комплекса, его внутренние и внешние компоненты и их содержимое в виде дерева разнотипных объектов. Показания датчиков должны были выводиться в виде таблицы (как в Excel'е, безмятежно улыбался заказчик), причём часть данных (показания, состояния датчиков и т.п.) требовалось непрерывно обновлять, а часть могла изменяться пользователем непосредственно в таблице. Для настройки комплекса было необходимо использовать многоуровневое меню и большое количество модальных диалоговых окон.

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

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

Итак, что же такое такое EasyUI?

Как я уже упоминал выше, EasyIU представляет собой набор компонентов пользовательского интерфейса, основанных на jQuery, Angular, Vue и React. Мы использовали библиотеку, базирующуюся на jQuery.

С самого начала мне понравилась возможность создания макета приложения без программирования на javascript. EasyUI для jQuery имеет встроенный парсер, который расширяет HTML-разметку и ассоциирует её с библиотечным кодом. Для этого в классе HTML-элемента достаточно указать наименование компонента, который необходимо применить.

Например, эта разметка создаст на странице приложения пять зон: шапку и подвал высотой 100 пикселей, и разделённую на три колонки среднюю часть. Левая и правая колонки имеют ширину 100 пикселей, а центральная часть имеет серый фон и занимает всё оставшееся место. Кроме этого, высоту шапки и подвала, ширину левой и правой колонок можно изменять мышью во время работы приложения. Для этого EasyUI создаст на странице приложения специальные разделители.

<body class="easyui-layout">  <div data-options="region:'north',title:'North Title',split:true"       style="height:100px;"></div>  <div data-options="region:'south',title:'South Title',split:true"       style="height:100px;"></div>  <div data-options="region:'east',title:'East',split:true"       style="width:100px;"></div>  <div data-options="region:'west',title:'West',split:true"       style="width:100px;"></div>  <div data-options="region:'center',title:'center title'"       style="padding:5px;background:#eee;"></div></body>

Конечно, EasyUI позволяет сделать то же самое при помощи javascript:

$('body').layout({fit: true}).layout('add', {  region: 'north', title: 'North Title', split: true, height: 100}).layout('add', {  region: 'south', title: 'South Title', split: true, height: 100}).layout('add', {  region: 'east', title: 'East Title', split: true, width: 100}).layout('add', {  region: 'west', title: 'West Title', split: true, width: 100}).layout('add', {  region: 'center', title: 'сenter Title', split: true, widht:100,  style: {padding: 5, background: '#eee'}});

В результате EasyUI создаст вот такую страницу:

Пока всё easy, не так ли? Мы можем создать предварительный макет без написания javascript кода, что очень удобно на начальных этапах проектирования приложения.

А что ещё она умеет?

Для беглой оценки возможностей EasyUI, достаточно посмотреть на её конструктор тем:

Здесь показаны не все возможности EasyUI, но для первого впечатления вполне достаточно и этого: разметка (layout), панели (panel), многоуровневое меню (menu, menubutton), вкладки (tab), аккордеоны (accordion), календарь (calendar), таблица (datagrid), набор конфигурационных параметров (propertygrid), список (datalist), дерево (tree), диалоги (dialog), формы (form) и их элементы (validatebox, textbox, passwordbox, maskedbox, combobox, tagbox, numberbox, datetimebox, spinner, slider, filebox, checkbox, radiobutton) и этот перечень далеко не полон. При более глубоком погружении в возможности библиотеки выясняется, что эти компоненты можно расширять и создавать на их основе новые. На сайте проекта есть раздел Extention, на котором представлены некоторые расширения, например, всем известная лента (Ribbon):

Для демонстрации всех компонентов, реализованных EasyUI, на странице проекта есть специальный раздел с демонстрацией их работы.

И снова о дизайне

EasyUI поддерживает изменение стиля оформления пользователем из набора готовых тем прямо во время выполнения приложения. На сайте проекта предлагается собственная коллекция стилей. К сожалению, решение из такого набора не всегда может удовлетворить заказчика. Это, скорее, набор стилей для начала работы, который впоследствии потребует корректировки. Для этого на сайте проекта имеется конструктор тем. Разумеется, всегда можно настроить тему приложения в соответствующей таблице стилей, которые находятся в каталоге themes проекта. Для нашего проекта поддержка возможности изменения темы приложения не требовалась, однако, таблицы стилей были существенно переработаны, чтобы UI соответствовал корпоративному стилю заказчика.

Создание диалога при помощи EasyUI

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

Код для создания диалога настроек HTTP
(function($) {   $.fn.httpConfDlg = function(icon) {     var title = _("HTTP Configuration"), me;     var succ = _(       "HTTP properties have been changed. " +       "You need to re-connect your browser " +       "according to the new properties."     );     var errcode = "System returned error code %1."     var errset = _(       "Can't set HTTP configuration. " + errcode     );     var errget = _(       "Can't get HTTP configuration. " + errcode     );     var allowed = $.SMR_PRIVILEGE.CHECK(       $.SMR_PRIVILEGE.CHANGE_NETWORK_CONFIGURATION     );     var buttons = [];     if (allowed) {       buttons.push({         okButton: true,         handler: function() {           var ho = $(this.parentElement).api({             fn: $.WAPI.FN_SET_HTTP_PROPERTIES,             param: {               httpPort: parseInt($('#httpPort').textbox('getValue')),               httpsPort: parseInt($('#httpsPort').textbox('getValue')),               forceHttps: $.HpiBool($('#forceHttp')[0].checked)             },             before: function() {               $('body').css('cursor', 'wait');             },             done: function() {               $('body').css('cursor', 'default');               me.dialog('close');             },             error: function(err) {               if (err.RC == $.WAPI.RC_BAD_RESPONSE) {                 $.messager.alert(                   title,                   $.fstr(errset, err.IC),                   'error'                 );                 return false;               } else if (err.RC == 1003) {                 ho.api('drop');                 $.messager.alert(title, succ, 'info', function() {                   $('#sinfo').session('logout');                 });                 return false;               }               return true;             }           });         }       });     }     buttons.push({cancelButton: true});     return this.each(function() {       document.body.appendChild(this);       me = $(this).append(         '<div id="httpSetting" style="padding: 10px 30px">' +         $.fitem('httpPort', _("HTTP port")) +         $.fitem('httpsPort', _("HTTPS port")) +         $.fcheck('forceHttp', _("Force HTTPS for Web Access")) +         '</div>'       );       $('#httpPort').textbox({         type: 'text', width: 60, disabled: !allowed       });       $('#httpsPort').textbox({         type: 'text', width: 60, disabled: !allowed       });       if (!allowed) $('#forceHttp').attr('disabled', 'disabled');         me.mdialog({           title: title,           iconCls: icon,           width: 320,           height: 180,           modal: true,           buttons: buttons,           onOpen: function() {             var ho = $(this).api({               fn: $.WAPI.FN_GET_HTTP_PROPERTIES,               receive: function(res) {                 $('#httpPort').textbox('setValue', res.httpPort);                 $('#httpsPort').textbox('setValue', res.httpsPort);                 if (res.forceHttps == 1) {                   $('#forceHttp').attr('checked', 'checked')                 } else {                   $('#forceHttp').removeAttr('checked')}               },               error: function(err) {                 if (err.RC == $.WAPI.RC_BAD_RESPONSE) {                   $.messager.alert(                     _("HTTP"),                     $.fstr(                       errget,                       err.IC                     ),                   'error'                 );                 me.dialog('close');                 return false;               }               me.dialog('close');               return true;             }           });         }       });     });   }; })(jQuery); 

Поскольку компоненты EasyUI реализованы в виде коллекций jQuery (в нашем случае это $('div').httpConfDlg(http_icon)), инициализация диалога производится через метод this.each().

В начале активируются кнопки диалога: OK и Cancel. Это можно сделать непосредственно при инициализации диалога, но кнопка OK создается только для обеспечения привилегированного доступа. Таким образом, для пользователя, не имеющего достаточных прав для изменения параметров HTTP протокола, диалог будет отображать только кнопку Cancel (Конечно, EasyUI допускает установку и снятие запрета нажатия на кнопки во время инициализации диалога, а также во время его работы кнопка при запрете использования изменяет стиль и не реагирует на нажатия. Однако, для сохранения общего стиля, неиспользуемые кнопки в диалогах нами не отображаются). Обработчик кнопки Cancel по умолчанию закрывает окно диалога без дополнительных действий. У кнопки OK есть обработчик, который выполняет AJAX-запрос. В качестве параметра запросу передаётся JSON структура, содержащая номер функции для бэкенда, набор параметров для самой функции и обработчики результатов выполнения (callback).

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

И наконец, производится активация самого диалога: устанавливаются его размеры, заголовок, иконка, модальный режим, активируются кнопки диалога и их обработчики (указанные явно или по умолчанию). После открытия диалога запускается функция, выполняющая AJAX запрос для получения текущих параметров HTTP. Если при выполнении запроса происходит ошибка, генерируется сообщение об ошибке и диалог закрывается. Если запрос выполнен успешно, текстовые поля и флажок устанавливаются в соответствии с полученными результатами запроса.

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

Несколько, пояснений к коду.

  • Вызов $.fitem('httpPort', _("HTTP port")) создаёт набор связанных HTML элементов, реализующих типовое для нашего приложения поле ввода с идентификатором httpPort и меткой (label) HTTP port. Функция _() обеспечивает использование языка, указанного пользователем в настройках. Последующий вызов компонента EasyUI $('#httpPort').textbox({type: 'text', width: 60, disabled: !allowed}); регистрирует поле ввода как EasyUI textbox. Вызов $('#httpPort').textbox('setValue', res.httpPort); устанавливает значение для текстового поля в соответствие результату AJAX запроса. И наконец, parseInt($('#httpPort').textbox('getValue')) в обработчике OK-кнопки возвращает текущее значение текстового поля.

  • Компонент mdialog() является нашим собственным расширением от базового компонента EasyUI dialog() для автоматического закрытия диалога при наступлении определённых событий, а также для создания типовых кнопок с обработкой нажатия по умолчанию. В данном случае это кнопка Cancel, которая создаётся короткой инструкцией buttons.push({cancelButton: true});

  • Функция $.messager вызывает окно предупреждения, которое также является компонентом EasyUI, производным от компонента Dialog.

В итоге диалог выглядит так:

EasyUI диалог для настройки HTTPEasyUI диалог для настройки HTTP

Ложка дёгтя

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

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

И всё-таки, почему EasyUI?

Работа с большими массивами данных, представленными в виде таблиц и деревьев это та фишка, которая определила выбор EasyUI.

Для нас критически важно было отображать большой и постоянно обновляемый набор данных как в виде иерархической структуры, так и в виде таблицы. И EasyUI предоставила нам такую возможность. Для этого в составе библиотеки есть специальные компоненты: tree и datagrid.

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

Компонент datagrid отображает данные в табличном формате и предлагает богатую поддержку для выбора, сортировки и группировки данных. Слияние ячеек, многоколоночные заголовки, замороженные столбцы и нижние колонтитулы вот лишь некоторые из его особенностей. Кроме этого, для datagrid есть специальные расширения: datagrid-scrollview, которое манипулирует полным набором данных, сохраняя в DOM-е лишь отображаемую их часть (а это существенно повышает скорость работы приложения), и datagrid-filter позволяющее накладывать фильтры на используемый набор данных.

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

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

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

Подробнее..

Перевод 25 лет CSS

03.06.2021 02:15:41 | Автор: admin
image

Первые заметки по CSS.

Это было утро вторника, 7 мая, когда я сидел в конференц-зале Амбруази CNIT в Париже, Франция, и мои мысли поразила перспективная веб-технология под названием Cascading Style Sheets, 25 лет назад.

На тот момент я работал чуть более двух лет Вебмастером в Case Western Reserve University, и хотя я знал о верстке с помощью таблиц, я не хотел использовать этот подход для основного сайта кампуса. Все эти таблички казались неправильными. Но все же, у меня не было вариантов отказаться от табличного способа верстки. Я ждал чего-то лучшего, но все больше сомневался, сколько еще мне можно ждать.

image


Эрик Майер

Успешно убедив университет оплатить мою поездку в Париж для участия в WWW5, отчасти за счет того, что публикация была принята для презентации, я теперь сидел в на конференции W3C, видел примеры работы CSS в браузере, и мне просто показалось правильность. Когда я увидел, что одно слово приобрело насыщенно-синий цвет и размер 100 пунктов с одним элементом и несколькими простыми правилами, я был очарован. Я до сих пор помню жужжащее возбуждение, охватившее мою голову, когда я почувствовал, что вижу реальный сдвиг в силе сети, большой скачок вперед и именно то, чего я ожидал.

Просматривая свои рукописные заметки (ноутбуки были тяжелыми, громоздкими, с батареями малой емкости и дорогими в те дни, поэтому я не стал брать его с собой) с конференции, которые у меня все еще сохранились, я нахожу много такого, что меня интересует. HTTP 1.1 и HTML 3.2 были анонсированы или, по крайней мере, подробно объяснены на этой конференции. Я сделал несколько заметок о новом элементе <OBJECT> и написал CENTER is in!, Что, на мой взгляд, было выражением энтузиазма. Ах, снова бы стать таким молодым и глупым.

Есть и другие лакомые кусочки: утверждение, что стандарты будут следовать за инновациями что, как мне кажется, действительно произошло только в последнее десятилетие или около того и что Математика перешла на ActiveMath, последнее из которых я признаю, я не только забыл, но и никак не могу вспомнить.

Мои первые впечатления от CSS, разбиты по непонятной причине на две страницы.
Но я записал, что CSS имеет около 35 свойств и что вы можете связать его с разметкой, используя <LINK REL = STYLESHEET>, <STYLE> </STYLE> или <H1 STYLE = "">. Тут записан вопрос Градиентный фон? что я уже не могу вспомнить, было ли это примечание для меня, которое нужно проверить позже, или что-то, что было рассмотрено как возможность во время разговора. Я делал заметки о фонах изображений, расстоянии между текстом, отступах (которые мне удалось написать с ошибками) и многом другом.

В то время я не знал, что CSS по-прежнему оставался бесполезным. Реализации, конечно, появлялись, но демонстрации, которые я видел, были выбраны очень узко, а поддержка браузеров была в лучшем случае минимальной, не говоря уже о дикой непоследовательности. Я не обнаружил ничего из этого, пока не вернулся домой и не начал экспериментировать с языком. Имея рядом с собой распечатанную копию спецификации CSS1, я продолжал пробовать вещи, которые, казалось, должны были работать, но они не работали. Не имело значения, использовал ли я доминирующего на рынке гиганта, которым был Netscape Navigator, или лоскутный, второстепенный новый Internet Explorer: казалось, очень мало что соответствовало спецификации, и почти ничего не работало стабильно во всех браузерах.

Поэтому я начал создавать небольшие тестовые страницы, занимаясь одним свойством на каждой странице с одним тестом для каждого значения (или типа значения), каждая из которых является простым утверждением того, что должно отображаться вместе с копией CSS, используемого на странице. Со временем моя склонность к завершению заставила меня расширить этот небольшой набор тестов, чтобы охватить все, что есть в CSS1, и перфекционист во мне приложил усилия, чтобы упростить навигацию. Таким образом, когда вышла новая версия браузера, я мог прогнать ее через весь набор тестов, посмотреть, что изменилось, и отметить это.

В конце концов, эти тесты превратились в CSS1 Test Suite, и то, как он выглядит сегодня, в значительной степени совпадает с тем, как я его сделал. Некоторые тесты были расширены, пересмотрены и добавлены, плюс, в конце концов, все это было объединено в базовую тестовую систему, которую, я думаю, написал кто-то другой, но большинство тестов и общий визуальный дизайн были моей работой, нечувствительностью к дальтонизу и всем остальным. Эти тесты в основном и привели меня в рабочую группу в качестве приглашенного эксперта еще в свое время.

Однако до того, как это произошло, со всеми этими тестами я смог скомпилировать информацию о поддержке браузера CSS в большую таблицу с цветовой кодировкой, которую я опубликовал на веб-сайте CWRU (помните, я был вебмастером) и сделал ее доступной для всех. Данные поддержки хранились в большой базе данных FileMaker Pro с настраиваемыми выпадающими полями для ввода значений Y/N/P/B и множеством полей для ввода фрагментов шаблона, чтобы я мог экспортировать в HTML. Эта диаграмма поддержки в конечном итоге перекочевала в позднюю версию Web Review, где она стала известна как Mastergrid термин, который я считаю забавным в ретроспективе, потому что макет сетки был еще через два десятилетия в будущем, и в любом случае это была просто большая и сильно стилизованная таблица данных. Потому что я был не против таблиц для табличных данных. Мне просто не понравилась идея использовать их исключительно для верстки.

image


Вы можете увидеть одну из более поздних версий Mastergrid в Wayback Machine с ее сильно классифицированной, но все еще очаровательно неуклюжей разметкой. Моя работа по поддержке Mastergrid и статьи, которые я написал для Web Review, привели меня к моей первой книге для O'Reilly (в настоящее время в четвертом издании), что привело к тому, что меня попросили написать другие книги и выступить на конференциях, что привело к моему решение стать соучредителем конференции и еще много чего.

И все это началось 25 лет назад в мае месяце, в конференц-зале в Париже 7 мая 1996 года. Вот это было путешествие. Сейчас, во второй половине моей жизни, мне интересно, как CSS и как сам Интернет будет выглядеть через 25 лет.
Подробнее..

Проблемы рендера 7-и тысяч элементов на Vuetify

03.06.2021 08:11:45 | Автор: admin

Предисловие

На момент написания статьи я готовился к диплому и писал дипломный проект для нужд Московского Политеха. Моя задача - перенести существующий функционал из PHP-таблицы во что-то современное с кучей проверок, после чего дополнить данный функционал. Движок - Nuxt, материал-фреймворк: Vuetify.

После написания первичного кода, я, довольный, окинул взглядом свою таблицу и пошел спать. На следующий день мне предстояло импортировать 150+ проектов заказчика в свою таблицу. После импорта, я удивился тому, что браузер повис. Ну, бывает, просто переоткрою вкладку. Не помогло. Я впервые столкнулся с проблемой, что я рендерю слишком много, как для движка, так и для самого браузера. Пришлось начать думать.

Первые попытки

Что делает разработчик, когда сталкивается с проблемой? Идет гуглить. Это было первое, что я сделал. Как оказалось, проблема медленного рендера таблицы Vuetify встречается с куда меньшим числом элементов, чем у меня. Что советуют:

  • Рендерить элементы по частям через setInterval

  • Ставить условие, чтобы не рендерить элементы, пока не сработает хук жизненного цикла mounted()

  • Использовать v-lazy для последовательной отрисовки

При этом было предложение использовать компонент Virtual Scroller, позволяющий отрисовывать элементы по мере скролла, а предыдущие разрендеривать. Но этот компонент Vuetify не работает с таблицами Vuetify -_-

С "радостью" прочитав, что в Vuetify 3 (релиз через ~полгода) производительность улучшится на 50%, я стал пробовать решения. Рендер элементов по частям ничего не дал, так как на условном тысячном элементе отрисовка начинала лагать, а к семи тысячам снова всё висло. Рендер элементов на mounted не дал вообще ничего, всё зависало, но зато после того, как страница загрузится (эээ, ура?). v-lazy хоть и рендерился быстрее, но рендерить 14 тысяч компонентов (Vuetify и Transition от Vue) тоже грустное занятие.

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

ТаблицаТаблица

Решение 1. Intersection Observer

Итак, что мы имеем. v-lazy отрисовывать невозможно, это 14 тысяч компонентов. Vuetify Virtual Scroller не поддерживается в Vuetify Data Table из-за его структуры. Выходит, нужно писать свою реализацию. Кто умеет определять, докрутил ли пользователь до элемента? Intersection Observer. Internet Explorer нам не нужен, так что можем приступать.

Первая логичная попытка: использовать директиву v-intersect от самого Vuetify. И 7 тысяч директив также привели к длительному рендеру страницы =(. Значит, выбора нет и придется работать руками.

mounted() {  //Цепляем его на таблицу с overflow: auto  //Требуемая видимость элемента для триггера: 10%this.observer = new IntersectionObserver(this.handleObserve, { root: this.$refs.table as any, threshold: 0.1 });//Почему нельзя повесить observe на все нужные элементы? Ну ладноfor (const element of Array.from(document.querySelectorAll('#intersectionElement'))) {this.observer.observe(element);}},

Теперь взглянем на сам handleObserve:

async handleObserve(entries: IntersectionObserverEntry[]) {const parsedEntries = entries.map(entry => {const target = entry.target as HTMLElement;  //Предварительно задали data-атрибутыconst project = +(target.dataset.projectId || '0');const speciality = +(target.dataset.specialityId || '0');return {          isIntersecting: entry.isIntersecting,          project,          speciality,};    });//Чтобы точно было реактивно    this.$set(this, 'observing', [      //Не добавляем дубликаты      ...parsedEntries.filter(x => x.isIntersecting && !this.observing.some(y => y.project === x.project && y.speciality === x.speciality)),      //Убираем пропавшие      ...this.observing.filter(entry => !parsedEntries.some(x => !x.isIntersecting && x.project === entry.project && x.speciality === entry.speciality)),     ]);//Иначе функция стриггерится несколько раз     Array.from(document.querySelectorAll('#intersectionElement')).forEach((target) => this.observer?.unobserve(target));     //Даем Vuetify перерендериться await this.$nextTick(); //Ждем 300мс, чтобы не триггернуть лишний раз, отрисовка тоже грузит браузер     await new Promise((resolve) => setTimeout(resolve, 500));     //Вновь обсервим элементы     Array.from(document.querySelectorAll('#intersectionElement'))          .forEach((target) => this.observer?.observe(target));},

Итак, мы имеем 7 тысяч элементов, на которых смотрит наш Intersection Observer. Есть переменная observing, в которой содержатся все элементы с projectId и specialityId, по которым мы можем определять, нужно ли показывать нужный элемент в таблице. Осталось всего-лишь повесить v-if на нужный нам элемент и отрисовывать вместо него какую-нибудь заглушку. Ура!

 <template #[`item.speciality-${speciality.id}`]="{item, headers}" v-for="speciality in getSpecialities()"><div id="intersectionElement" :data-project-id="item.id" :data-speciality-id="speciality.id"><ranking-projects-table-itemv-if="observing.some(x => x.project === item.id && x.speciality === speciality.id)":speciality="speciality":project="item"/><template v-else>Загрузка...</template></div></template>

А на саму таблицу вешаем v-once. Таблице будет запрещено менять свой рендер без $forceUpdate. Не очень красивое решение, но Vuetify непонятно чем занимается при скролле, запретим ему это делать.

<v-data-table v-bind="getTableSettings()"   v-once   :items="projects"   @update:expanded="$forceUpdate()">

Итоги:

  • Рендер занимает около секунды

  • Элементы разрендериваются вне их зоны видимости

  • Идет ререндер на каждое действие внутри таблицы

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

Загрузка...Загрузка...

Однако было ясно (и заказчик это подчеркнул), что работать с таблицей тяжело. Нужно было что-то решать, но на момент тогда я зашел в тупик: Vuetify Data Table имеет ужасные проблемы с производительностью, которые признают даже разработчики фреймворка.

Не переписывать же мне их таблицу и создавать свой аналог?

Решение 2. Переписать таблицу и создать свой аналог

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

Что мы поняли из первого решения:

  1. Таблицы Vuetify лагучий отстой

  2. Если показывать элементы только когда пользователь их увидит, рендер будет быстрее

  3. Нужно как-то сократить время на первичную отрисовку, чтобы пользователь не видел бесконечную "Загрузку"

  4. Можно заставить Intersection Observer реагировать чаще, чтобы рендерить элементы по мере скролла, а не когда скролл остановится или среагирует событие (300мс задержка)

Возвращаемся к Virtual Scroller. Он не работает в таблице Vuetify, так? А что если мы напишем свою таблицу с блекджеком и display: grid? Зачем-то же его придумали.

Что нужно для Virtual Scroller? Фиксированная высота каждого элемента. Что нужно для Grid'ов? Фиксированная ширина каждого элемента и информация о количестве элементов. Бахнем CSS-переменные для последующего использования в CSS:

<div class="ranking-table" :style="{    '--projects-count': getSettings().projectsCount,    '--specialities-count': getSettings().specialitiesCount,    '--first-column-width': `${getSettings().firstColumnWidth}px`,    '--others-columns-width': `${getSettings().othersColumnsWidth}px`,    '--cell-width': `${getSettings().firstColumnWidth + getSettings().othersColumnsWidth * getSettings().specialitiesCount}px`,    '--item-height': `${getSettings().itemHeight}px`  }">

Потом пишем гриды по типу аля

display: grid;grid-template-columns:var(--first-column-width)repeat(var(--specialities-count), var(--others-columns-width));

И так далее для элементов внутри. Класс! А еще мы, зная ширину и количество элементов, можем задать ширину нашему Virtual Scroller (он сам по умолчанию туповат), а заодно и всем нашим элементам, чтобы не дай боже кто-то вышел за доступные ему границы

.ranking-table_v2__scroll::v-deep {.v-virtual-scroll {&__container, &__item, .ranking-table_v2__project {    width: var(--cell-width);}}}

Примечание: если вы сделаете просто <style>без scoped и решите, что будет хорошей идеей редактировать глобальные стили вне окружения компонента, то у меня для вас плохие новости: лучше так не делать вне каких-то App.vue, и стоит ознакомиться с тем, что это за v-deep.

Поехали: добавляем Virtual Scroller, пихаем в него проекты, после чего выводим последние. Сразу скажу: поддержки Expandable Items у нас тут нет, я вынес информацию о проекте во всплывающее окно. Жаль, конечно, что нельзя это отображать прямо в таблице, как делал Vuetify, но тогда придется помучаться с их скроллером, а он и так не особо хорошо работает. В общем, к делу:

<v-virtual-scroll   class="ranking-table_v2__scroll"   :height="getSettings().commonHeight":item-height="getSettings().itemHeight":items="projects"><template #default="{item}"><div class="ranking-table_v2__project" :key="item.id"><!-- ... -->

Итого: на страницу, допустим, помещается 6 проектов (высота же у всех максимальная по факту), итого рендерится 6 строк + шапка. Колонок 50. Итого рендерится около 300 сложных компонентов. А вот это уже задача не уровня мстителей, 300 мы рендерить умеем.

Вспоминаем про лучший инструмент всех времен и народов v-lazy: он позволяет отрендерить элемент один раз и потом не перерендеривать его. Раньше мы пытались отрендерить 14 тысяч компонентов, сейчас 600 простых. Ну и оборачиваем все наши колонки (кроме шапки) в v-lazy. При горизонтальном скролле элементы подгружаются, и остаются отрендеренными до тех пор, пока сама строка таблицы не пропадет из области видимости и не разрендерится.

 <v-lazy class="ranking-table_v2__item ranking-table_v2__item--speciality"v-for="(speciality, index) in specialities":key="speciality.id">   <!-- Колонка в строке таблицы --></v-lazy>

Видео с разницей, можно сравнить:

Плюсы такого решения:

  • Элементы перестали скакать как ненормальные

  • Нет нужды писать свою реализацию логики рендера/отрендера

  • Можем отказаться от v-once и всяких принудительных ререндеров аля $forceUpdate

  • Куда больше гибкости при верстке таблицы

Минусы:

  • Если используются выпадающие элементы (expand), нужно писать свою реализацию

  • Нужно верстать таблицу самому, без инструментов движка

  • Нет средств сортировки/группировки и прочего (мне было не нужно)

  • Для того, чтобы шапка всегда была отрендеренной, но скроллилась вместе со всеми, мне пришлось отслеживать событие Scroll и подгонять скролл шапки к скроллу основных элементов

  • Чтобы отображать таблицу в нужном мне проценте от высоты страницы, мне пришлось смотреть на window.innerHeight и применять его в CSS переменных и в значении высоты у VirtualScroll

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

Заключение

Я привел 2 варианта решения проблем с производительностью отрисовки таблиц в Vuetify. Оба решения достаточно сложные, просто сложные в разных аспектах. Оба так или иначе помогли мне решить мою проблему, и у обоих есть свои ограничения. Например, второй вариант мне нравится больше, но отсутствие средств Vuetify (сортировка/группировка и пр.) может оттолкнуть тех, кому это позарез нужно.

Зачем я это пишу? Я перегуглил половину интернета и не нашел простых решений своей проблемы. Наверное, как раз таки потому, что этих решений нет. Эти таблицы не предназначались для сложной логики, даже в условных гугловских таблицах элементов отрисовывается меньше, потому что там буквально один инпут, изредка с каким-то контентом, здесь же - гигантская куча логики.

И да: я пользовался дебагером производительности от Vue и смотрел, кто её потребляет. Зачастую там был буквально один-два компонента, и, заменив их на какой-то другой с похожей логикой, проблема не решалась - дело в их количестве, а не сложности (не считая таблицу Vuetify - там передается множество props'ов из компонента в компонент).

Надеюсь, что приведенные мной варианты натолкнут кого-то на решение его проблемы, а кто-то просто узнает что-то новое =). Будем вместе ждать стабильный Vue 3 со всей его экосистемой, как минимум Nuxt 3. Что-то обещают множество улучшений, может, часть костылей из этой статьи даже пропадет.

Подробнее..

Как я сделал свою сборку Gulp для быстрой, лёгкой и приятной вёрстки

03.06.2021 18:21:18 | Автор: admin

Серьёзно и профессионально я начал заниматься вёрсткой в 2019 году, хотя до этого ещё со школы интересовался данной темой как любитель. Поэтому новичком мне себя назвать сложно, но и профессионалом с опытом 5+ лет я тоже не являюсь. Тем не менее, я успел познакомиться со сборщиком Gulp, его плагинами и сделал для себя хорошую, как по мне, сборку для работы. О её возможностях сегодня и расскажу.

ВАЖНО! В этой статье речь пойдёт о самой последней версии сборки. Если вы пользуетесь версиями сборки, вышедшими до публикации этой статьи, информация будет для вас не релевантна, но полезна.

Какие задачи решает эта сборка?

  • вёрстка компонентами (вам не нужно в каждую страницу копировать head, header, footer и другие повторяющиеся элементы, вплоть до кнопок или кастомных чекбоксов);

  • вёрстка с препроцессорами (SASS/SCSS);

  • конвертация шрифтов из ttf в eot, woff, woff2;

  • лёгкое (почти автоматическое) подключение шрифтов;

  • лёгкое (почти автоматическое) создание псевдоэлементов-иконок;

  • обработка изображений "на лету";

  • минификация html/css/js файлов;

  • возможность вёрстки с использованием php;

  • выгрузка файлов на хостинг по FTP;

  • несколько мелких задач с помощью миксинов.

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

Собственно создание сборки

Начнём собирать нашу сборку (простите за тавтологию). Предварительно нам потребуется уже установленная на компьютере LTS-версия Node.js и NPM (входит в пакет Node.js) либо Yarn. Для нашей задачи не имеет значения, какой из этих пакетных менеджеров использовать, однако я буду объяснять на примере NPM, соответственно, для Yarn вам потребуется нагуглить аналоги NPM-команд.

Первое, что нам нужно сделать - это инициализировать проект. Открываем директорию проекта в командной строке (очень надеюсь, вы знаете, как это делается) и вводим команду npm init.

После этого npm задаст нам неесколько стандартных вопросов по типу названия проекта, автора, версии и т.д... Отвечаем на них как душе угодно. Для нашей задачи это не имеет никакого значения.

Далее будет намного удобнее работать через Visual Studio Code (поскольку у него есть встроенный терминал) или любой другой удобный вам редактор + терминал.

Прежде всего, нам нужно установить сам Gulp. Делается это двумя командами npm i gulp -global - устанавливаем Gulp глобально на систему и npm i gulp --save-dev - устанавливаем Gulp локально в проект. Ключ --save здесь отвечает за сохранение версии плагина при дальнейшей установке (без него вам может установить более новую, несовместимую с другими плагинами версию), а ключ -dev указывает на то, что этот пакет необходим только во время разработки проекта, а не во время его выполнения. Например, если мы устанавливаем в проект пакет Swiper, который содержит скрипты слайдера и будет отображаться на странице, мы будем устанавливать его без ключа -dev, поскольку он нужен для выполнения, а не для разработки.

После того, как Gulp установился, имеет смысл создать в корне проекта управляющий файл gulpfile.js, в котором мы и будем писать задачи для сборщика.

После этого нам нужно подключить Gulp в нашем файле, для того чтобы он исполнялся. Это делается с помощью require:

const gulp = require('gulp');

Далее, для каждой задачи будем использовать модули в отдельных файлах. Для того, чтобы не подключать каждый модуль отдельно, нужно установить и подключить плагин require-dir. Устанавливается он всё той же командой (как и все последующие плагины, поэтому далее повторяться не буду, просто знайте, что установить - это npm i $PLUGIN-NAME$ --save-dev). После установки подключаем его и прописываем путь к директории, в которую будем складывать модули (у меня это директория tasks):

const gulp = require('gulp');const requireDir = require('require-dir');const tasks = requireDir('./tasks');

Первая задача

Давайте проверим, всё ли мы правильно сделали. Создадим в директории tasks файл модуля с именем hello.js. В созданном файле напишем простейшую функцию, которая будет выводить в консоль строку "Hello Gulp!" (можете придумать что-то менее банальное, если хотите).

module.exports = function hello () {console.log("Hello Gulp!");}

Теперь вернёмся в gulpfile.js и зададим там задачу hello:

const gulp = require('gulp');const requireDir = require('require-dir');const tasks = requireDir('./tasks');exports.hello = tasks.hello;

Теперь командой gulp hello в терминале запустим нашу задачу. Если всё сделано правильно - в терминал должно вывестись приблизительно такое сообщение:

[13:17:15] Using gulpfile D:\Web projects\Easy-webdev-startpack-new\gulpfile.js[13:17:15] Starting 'hello'...Hello Gulp![13:17:15] The following tasks did not complete: hello[13:17:15] Did you forget to signal async completion?

Так же, можно получить список всех заданных задач командой gulp --tasks.

Файловая структура

Теперь, когда мы создали первую функцию, можно подумать о том, какая структура будет у наших проектов. Я предпочитаю использовать директорию (папку) /src для хранения исходных файлов и директорию /build для готовых файлов проекта.

В директории src/ нам понадобятся следующие поддиректории:
  • components/ - директория для компонентов

  • components/bem-blocks/ - директория для БЭМ-блоков

  • components/page-blocks/ - директория для типовых блоков страницы, таких как хедер, футер и т.п.

  • fonts/ - директория для шрифтов

  • img/ - директория для изображений

  • js/ - директория для файлов JavaScript

  • scss/ - директория для файлов стилей

  • scss/base/ - директория для базовых стилей, которые мы изменять не будем

  • svg/ - директория для файлов SVG

  • svg/css/ - директория для SVG-файлов, которые будут интегрироваться в CSS

Получиться в итоге должно приблизительно следующее:

 project/  build/ 
Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 470 (1 6 июня 2021)

07.06.2021 00:10:32 | Автор: admin
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.


Медиа|Веб-разработка|CSS|JavaScript|Браузеры


Медиа


podcast video Подкаст Goose&Duck #1 Ржавеющий JavaScript
podcast CSSSR Callback Hell: Rescript, мысли пьяного Senior-разработчика, слежка за сотрудниками
podcast Новости 512 от CSSSR: Server-Sent Events: ограничения, поддержка Node.js-проектов, плагины для VSCode, 12 лет Node.js
podcast Подкаст Фронтенд Юность #189 Рон-дом-дом

Веб-разработка


habr С помощью перехода на микросервис мы ускорили бизнес-процесс в 60 раз
en Создание нескольких прогрессивных веб-приложений в одном домене
en Тестирование фронтенда для всех
en Разрушение мифов: Jamstack не может обрабатывать динамический контент
en История веба: часть 1
en Некоторые из лучших пасхальных яиц, спрятанных на сайтах в Интернете





CSS


habr 25 лет CSS
Нативная валидация ввода в CSS
en CSS in SVG in CSS: добавление конфетти в дизайн-систему Stack Overflow
en Новые функциональные селекторы псевдоклассов CSS: is() и: where()
en Тригонометрия в CSS и JavaScript: Введение в тригонометрию
en Тригонометрия в CSS и JavaScript: творческий подход с помощью тригонометрических функций
en The CSS Layout Generator визуальный инструмент для создания компонентов лейаута на CSS Grid
en Inherit, initial, unset, revert
en Шестиугольники и не только: гибкие, отзывчивые сеточные шаблоны, без медиа-запросов

JavaScript


habr Управление зависимостями в Node.js
habr Как мы потерпели неудачу, а затем преуспели в переходе на TypeScript
habr Создание нейронной сети Хопфилда на JavaScript
ES12 сделает вашу жизнь проще!
en Обеспечение быстрой работы JavaScript в WebAssembly
en Еще одна альтернатива Javascript: ReScript
en Взгляд на компиляцию в JavaScript-фреймворках






Браузеры


habr Firefox 89 обновил интерфейс браузера
Релиз Firefox 89 с переработанным интерфейсом
Mozilla, Google, Apple и Microsoft объединили усилия в стандартизации платформы для браузерных дополнений
en Что нового в DevTools (Chrome 92)

Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Подробнее..

Используйте ссылки и кнопки правильно. Пожалуйста

09.06.2021 14:20:05 | Автор: admin

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

Почему возникает проблема

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

Ссылки создаются с помощью тега <a>.

<a href="http://personeltest.ru/aways/htmlacademy.ru">HTML Academy</a>

Кнопка это функциональный элемент. Она отвечает за выполнение определённой функции: добавить в корзину, купить, отправить, проголосовать и т. д.

Для кнопок используют тег <button>.

<button class="button">9 900 руб.</button>

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

Пример двух кнопок, где нижняя похожа на ссылкуПример двух кнопок, где нижняя похожа на ссылкуПример ссылки, похожей на кнопкуПример ссылки, похожей на кнопку

Что случится, если вместо ссылки сделать кнопку

Если URL-адрес обернуть не <а>, а тегом <button>, то при нажатии пользователя на правую кнопку мыши браузер предложит выполнить действия, которые положены при клике на кнопку. Адрес нельзя будет открыть в новой вкладке или отправить через контекстное меню.

Контекстное меню при клике правой кнопкой мыши на кнопкуКонтекстное меню при клике правой кнопкой мыши на кнопку

Такая кнопка не позволит открыть страницу в новой вкладке и через комбинацию клавиш Ctrl и левая кнопка мыши.

При наведении курсора на правильную ссылку её адрес появляется внизу окна браузера. URL-адрес, обёрнутый в <button>, не отобразится.

Адрес правильной ссылки в окне браузера при наведении на неё курсораАдрес правильной ссылки в окне браузера при наведении на неё курсора

Что произойдёт, если вместо кнопки сделать ссылку

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

Контекстное меню при клике правой кнопкой мыши на ссылкуКонтекстное меню при клике правой кнопкой мыши на ссылку

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

<a href="javascript:;">На самом деле это кнопка</a>

При наведении курсора на псевдоссылку внизу окна браузера появится не адрес, а javascript:; или АдресТекущейСтраницы/#.

Как определить элемент по макету

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

У ссылки должны быть стили для состояния покоя, при наведения курсора :hover, в момент нажатия :focus, активного :active и иногда состояния, когда пользователь уже посещал эту страницу :visited.

У кнопки нет состояния :visited, зато есть состояние блока :disabled.

Состояния ссылок в стайлгайде макетаСостояния ссылок в стайлгайде макета

Однако рассчитывать только на макет не нужно. У ссылки может не быть :visited, а у блока :disabled это может запутать.

Забудьте про div семантика спасёт интернет

Всё-таки кнопка или ссылка?

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

Ссылка

Кнопка

За что отвечает

Навигация, т. е. пользователь перемещается на другую часть страницы или на новую страницу.

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

Что видит пользователь

При наведении курсора на ссылку внизу окна браузера возникает её адрес.

Через меню по клику правой кнопкой мыши можно скопировать, отправить, открыть ссылку в новой вкладке.

При наведении курсора на ссылку внизу окна браузера не отображается адрес.

Через меню по клику правой кнопкой мыши нельзя скопировать адрес или поделиться им.

Пример, чтобы расставить всё по своим местам

На макете отрисована фраза Войти. Если клик по ней должен открывать окно для ввода регистрационных данных на этой же странице, это кнопка. Если при нажатии должна открываться отдельная страница для входа, это ссылка.

Другие великие противостояния


Ссылки ссылками, а промокод VESELO даст приятную скидку на любой профессиональный курс в HTML Academy. Например, на курс Анимации для фронтендеров можно записаться в любой момент, а пользы для карьеры будет целый вагон.

Подробнее..

Как мы интрегрировали Agora SDK в проект

11.06.2021 18:10:18 | Автор: admin

Всем привет. Меня зовут Дмитрий, и я типичный представитель касты гребцов на галере X. Основной ЯП, который я использую - PHP, но иногда приходится писать на других.

Предыстория

Как-то пришел очередной, немного не доделанный, проект "убийца" продукта Y. Все бы ничего, если бы его не делали изначально индусы. Первоначальный анализ кода и все оценкой фич занимался другой разработчик. В итоге было решено взять наследие индусов, немного подправить и в прод.

Немного подправить и в продакшн, растянулось где-то на пол года. Все уже к этому времени поняли что лучше бы выбрали вариант - переписать заново, но было уже поздно, а отступать назад не хотелось. В итоге проект выехал live, но был чертовски страшный и неудобный со стороны UI/UX. Поэтому начался плавный редизайн всего и вся. Все что описано выше, было до меня, я пришел на проект только через пол года.

Постановка задачи и начальные условия

Новый спринт, новые тикеты. Одна из задач звучит как: "Редизайн текущего механизма видео/аудио звонков". Для данного функционала мы использовали Agora Web SDK 3.4.0v. Почему именно Agora - потому что ее выбрали индусы (скорее всего из-за 10000 бесплатных минут). Возможно еще подкупило то что есть SDK под различные платформы:

Поехали

Первый делом я глянул последнюю версию Web SDK. Как оказалось - уже вышла абсолютно новая мажорная версия Agora Web SDK 4.x. Ну, если мы все равно полностью редизайним - то почему бы и не взять новую версию и использовать ее. Все равно будет полный прогон со стороны QA, в добавок - поменялся сам флоу созвона. Сказано - сделано, только насторожила запись:

Вроде разговор идет только про несовместимость Web SDK (у нас еще используется React Native SDK для мобильных устройств), но осадок остался.

На новый дизайн и сервер ушло где-то 3 - 4 дня (не люблю верстать, но что поделать). Настало время самого интересного - запуск процесса интервью. В итоге была взята Agora Web SDK 4.4.0. В течение следующего дня получилось сделать всю JS часть для созвона по видео и ауди (со всеми плюшками). За основу был взят пример из их же гитхаба: https://github.com/AgoraIO/API-Examples-Web/blob/main/Demo/basicVideoCall/basicVideoCall.js (если что, то в архиве с самой либой лежат похожие примеры интеграции)

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

Первый звоночек

При звонке с мобилы на web, и наоборот - звонок не устанавливается. Проверили на проде тот же кейс - все огонь. Значит что-то пошло не так. Так как код на мобильных девайсах не менялся вообще (на их стороне изначально подумали над дизайном и все продумали) - значит проблема на моей стороне. Первое действие - нужно подписаться на все события от SDK что доступны - https://docs.agora.io/en/Voice/API%20Reference/web_ng/interfaces/iagorartcclient.html и смотреть что, где всплывает. Каково же было мое удивление, когда я увидел пустоту в консоле хромиума. Да это же не может быть, что бы Agora Web SDK 4.4.0 была не совместима с Agora React Native API 3.х!

После многих попыток, хоть как-то это дело завести - пришло "Принятие". Что поделать, придется брать все же Agora Web SDK 3.x.

Новый день начинается со скачивания последней версии (в рамках 3.x) и переделкой существующего функционала под другой SDK. К счастью, дело пошло быстро и за первую половину дня - все было готово. На локальной машине все работало отлично. Решил протестировать с коллегой по офису (он должен открыть ветку с прода и попробовать созвонится). И хоп, мы получаем ту же проблему - звонок не устанавливается, но что обнадеживает - в консоле проскакивают логи, что оппонент по звонку выходит из румы (в терминах агоры - это channel). Ну хоть какой прогресс, по сравнению с 4.x.

После первых двух часов дебага - было решено взять код с прода и попробовать его запустить. К черту текущее решение, просто берем код с прода, вcтавляем в HTML, пробрасываем пару конфигов и запускаем. О чудо, все работает. Все друг друга видят и слышат. Первая хорошая новость за день. Значит минорные версии 3.x совместимы между собой (это вселяет надежду с мобилами). Быстро подчищаем код с HTML и переносим его в JS модули. Запускаем и получаем дырку от бублика. Да что ж за день то сегодня такой. Откатываемся назад на вариант с кодом в HTML - работает. Ладно, теперь это уже личное...

Переносим "строчку за строчкой" из HTML в JS модули и почти каждый раз проверяем локально и с коллегой. Хм... Почему же оно все еще работает? Когда была перенесена последняя строчка, я очень удивился. Код был почти один-в-один как после миграции на 3.x, который я получил пол дня назад и он РАБОТАЛ. А давай-ка я попробую запустить старый свой вариант на 3.x. Оп-па не работает. Истина где-то рядом. Как хорошо что есть гит и можно сравнить. Отбросив различия в кодстайле я был очень удивлен увиденным:

Да не может же быть, что бы я так лоханулся. Быстро открываем документацию по либе (uid используется для подключения к руме). Нас интересует метод join:

Все! Я больше не могу. Поеду я домой, рабочий день уже давно закончен.

Второй звоночек

Новый день - новые силы. Как оказалось - на проде используется number, потому что со стороны мобил (с их слов) было жесткое требование на int и ничего более. Успокаиваемся и работаем... Проверяем локально, потом с коллегой, все хорошо - едем на testing. Проверяем web - хорошо, мобилы - черный экран у того кто остался на вебе, но звук работает отлично. Хоть какой прогресс...

После пары часы дебага и проверки различный гипотез, я был готов сдаться, но здесь произошло чудо. Разраб со стороны мобил говорит что у него все работает. После того, как мы созвонились, через один из мессенджеров, и расшарили экран - я действительно убедился, все условия соблюдены и оно действительно работает. Начали искать отличия. Мы с коллегой в офисе. QA на уделенке дома. Разработчик мобильной версии в Латвии. Просим QA пере-проверить кейс со звонком на разных платформа, только пусть звонит на мне в офис, а кому-то вне офиса. О-да, работает... Мы где-то близко...

Услышав наши "горячие" споры в комнате офиса, глава департамента встает и говорит: "Парни, а вы используете STUN / TURN сервера?". Такие слова я слышал впервые, поэтому пришлось гуглить: https://medium.com/nuances-of-programming/webrtc-%D1%84%D1%80%D0%B5%D0%B9%D0%BC%D0%B2%D0%BE%D1%80%D0%BA-ice-stun-%D0%B8-%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80%D0%B0-turn-f835b11d9dde

В общем, все сошлись на том что для текущей версии проекта - никто не будет пока подключать STUN / TURN сервера (ибо если бесплатные STUN еще можно найти, то бесплатных TURN нет).

Почему не догадались, что из-за NAT в офисе - ловим проблемы? Да потому что звук работал. И видео на одной стороне работало отлично. А как раз черный экран в видео мы уже получали, когда ловили кейсы, где один клиент инициировал созвон по связке rtc/vp8, а второй live/h264.

Вот так и закончилось мое увлекательное приключение в мир WebRTC.

PS. Если кто-то меня попросит еще раз сделать звонки на проекта - я первым делом пойду искать другую библиотеку.

Подробнее..

История одной интеграции Agora SDK

11.06.2021 20:19:02 | Автор: admin

Всем привет. Меня зовут Дмитрий, и я типичный представитель касты гребцов на галере X. Основной ЯП, который я использую - PHP, но иногда приходится писать на других.

Предыстория

Как-то пришел очередной, немного не доделанный, проект "убийца" продукта Y. Все бы ничего, если бы его не делали изначально индусы. Первоначальный анализ кода и все оценкой фич занимался другой разработчик. В итоге было решено взять наследие индусов, немного подправить и в прод.

Немного подправить и в продакшн, растянулось где-то на пол года. Все уже к этому времени поняли что лучше бы выбрали вариант - переписать заново, но было уже поздно, а отступать назад не хотелось. В итоге проект выехал live, но был чертовски страшный и неудобный со стороны UI/UX. Поэтому начался плавный редизайн всего и вся. Все что описано выше, было до меня, я пришел на проект только через пол года.

Постановка задачи и начальные условия

Новый спринт, новые тикеты. Одна из задач звучит как: "Редизайн текущего механизма видео/аудио звонков". Для данного функционала мы использовали Agora Web SDK 3.4.0v. Почему именно Agora - потому что ее выбрали индусы (скорее всего из-за 10000 бесплатных минут). Возможно еще подкупило то что есть SDK под различные платформы:

Поехали

Первый делом я глянул последнюю версию Web SDK. Как оказалось - уже вышла абсолютно новая мажорная версия Agora Web SDK 4.x. Ну, если мы все равно полностью редизайним - то почему бы и не взять новую версию и использовать ее. Все равно будет полный прогон со стороны QA, в добавок - поменялся сам флоу созвона. Сказано - сделано, только насторожила запись:

Вроде разговор идет только про несовместимость Web SDK (у нас еще используется React Native SDK для мобильных устройств), но осадок остался.

На новый дизайн и сервер ушло где-то 3 - 4 дня (не люблю верстать, но что поделать). Настало время самого интересного - запуск процесса интервью. В итоге была взята Agora Web SDK 4.4.0. В течение следующего дня получилось сделать всю JS часть для созвона по видео и ауди (со всеми плюшками). За основу был взят пример из их же гитхаба: https://github.com/AgoraIO/API-Examples-Web/blob/main/Demo/basicVideoCall/basicVideoCall.js (если что, то в архиве с самой либой лежат похожие примеры интеграции)

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

Первый звоночек

При звонке с мобилы на web, и наоборот - звонок не устанавливается. Проверили на проде тот же кейс - все огонь. Значит что-то пошло не так. Так как код на мобильных девайсах не менялся вообще (на их стороне изначально подумали над дизайном и все продумали) - значит проблема на моей стороне. Первое действие - нужно подписаться на все события от SDK что доступны - https://docs.agora.io/en/Voice/API%20Reference/web_ng/interfaces/iagorartcclient.html и смотреть что, где всплывает. Каково же было мое удивление, когда я увидел пустоту в консоле хромиума. Да это же не может быть, что бы Agora Web SDK 4.4.0 была не совместима с Agora React Native API 3.х!

После многих попыток, хоть как-то это дело завести - пришло "Принятие". Что поделать, придется брать все же Agora Web SDK 3.x.

Новый день начинается со скачивания последней версии (в рамках 3.x) и переделкой существующего функционала под другой SDK. К счастью, дело пошло быстро и за первую половину дня - все было готово. На локальной машине все работало отлично. Решил протестировать с коллегой по офису (он должен открыть ветку с прода и попробовать созвонится). И хоп, мы получаем ту же проблему - звонок не устанавливается, но что обнадеживает - в консоле проскакивают логи, что оппонент по звонку выходит из румы (в терминах агоры - это channel). Ну хоть какой прогресс, по сравнению с 4.x.

После первых двух часов дебага - было решено взять код с прода и попробовать его запустить. К черту текущее решение, просто берем код с прода, вcтавляем в HTML, пробрасываем пару конфигов и запускаем. О чудо, все работает. Все друг друга видят и слышат. Первая хорошая новость за день. Значит минорные версии 3.x совместимы между собой (это вселяет надежду с мобилами). Быстро подчищаем код с HTML и переносим его в JS модули. Запускаем и получаем дырку от бублика. Да что ж за день то сегодня такой. Откатываемся назад на вариант с кодом в HTML - работает. Ладно, теперь это уже личное...

Переносим "строчку за строчкой" из HTML в JS модули и почти каждый раз проверяем локально и с коллегой. Хм... Почему же оно все еще работает? Когда была перенесена последняя строчка, я очень удивился. Код был почти один-в-один как после миграции на 3.x, который я получил пол дня назад и он РАБОТАЛ. А давай-ка я попробую запустить старый свой вариант на 3.x. Оп-па не работает. Истина где-то рядом. Как хорошо что есть гит и можно сравнить. Отбросив различия в кодстайле я был очень удивлен увиденным:

Да не может же быть, что бы я так лоханулся. Быстро открываем документацию по либе (uid используется для подключения к руме). Нас интересует метод join:

Все! Я больше не могу. Поеду я домой, рабочий день уже давно закончен.

Второй звоночек

Новый день - новые силы. Как оказалось - на проде используется number, потому что со стороны мобил (с их слов) было жесткое требование на int и ничего более. Успокаиваемся и работаем... Проверяем локально, потом с коллегой, все хорошо - едем на testing. Проверяем web - хорошо, мобилы - черный экран у того кто остался на вебе, но звук работает отлично. Хоть какой прогресс...

После пары часы дебага и проверки различный гипотез, я был готов сдаться, но здесь произошло чудо. Разраб со стороны мобил говорит что у него все работает. После того, как мы созвонились, через один из мессенджеров, и расшарили экран - я действительно убедился, все условия соблюдены и оно действительно работает. Начали искать отличия. Мы с коллегой в офисе. QA на уделенке дома. Разработчик мобильной версии в Латвии. Просим QA пере-проверить кейс со звонком на разных платформа, только пусть звонит на мне в офис, а кому-то вне офиса. О-да, работает... Мы где-то близко...

Услышав наши "горячие" споры в комнате офиса, глава департамента встает и говорит: "Парни, а вы используете STUN / TURN сервера?". Такие слова я слышал впервые, поэтому пришлось гуглить: https://medium.com/nuances-of-programming/webrtc-%D1%84%D1%80%D0%B5%D0%B9%D0%BC%D0%B2%D0%BE%D1%80%D0%BA-ice-stun-%D0%B8-%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80%D0%B0-turn-f835b11d9dde

В общем, все сошлись на том что для текущей версии проекта - никто не будет пока подключать STUN / TURN сервера (ибо если бесплатные STUN еще можно найти, то бесплатных TURN нет).

Почему не догадались, что из-за NAT в офисе - ловим проблемы? Да потому что звук работал. И видео на одной стороне работало отлично. А как раз черный экран в видео мы уже получали, когда ловили кейсы, где один клиент инициировал созвон по связке rtc/vp8, а второй live/h264.

Вот так и закончилось мое увлекательное приключение в мир WebRTC.

PS. Если кто-то меня попросит еще раз сделать звонки на проекта - я первым делом пойду искать другую библиотеку.

Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 472 (7 13 июня 2021)

14.06.2021 00:15:08 | Автор: admin
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.


Медиа|Веб-разработка|CSS|JavaScript|Браузеры


Медиа


podcast Новости 512 от CSSSR: Firefox 89, Safari 15 Beta, Jest 27, цикл статей о работе браузера, разработка базовых компонентов, обзорная статья о тестировании фронтенда и анонс WebExtensions Community Group.
podcast Подкаст Веб-стандарты #285: Бета Chrome92, Firefox89, якоря ирасширения, TeamCity, JSвнутри WASM, TypeScript4.3
podcast Подкаст Фронтенд Юность #190: Как подступиться к старому проекту и не сесть на кулак
podcast Новости 512 от CSSSR: React 18, Vue 3.1, анонс ESLint 8, курсы от CSSSR, :is(), where() и :has(), как прилёг Интернет
podcast Подкаст Callback Hell: Сервисы Google с плохими Web Vitals, шеринг логики между фронтом и бэком, документация на проектах


Веб-разработка


habr Будущее веба: станет ли рендеринг в <canvas> заменой DOM?
en Правильный тег для работы: почему следует использовать семантический HTML
en 5 проблем фронтенда, которые нельзя игнорировать





CSS


habr Выкладка нетрадиционной ориентации
en Полное руководство по CSS Grid с шпаргалкой
en Системные цвета CSS
en CSS определяет значения цвета, соответствующие системным настройкам.
en Media Queries во времена @container
en Давайте узнаем об Aspect Ratio в CSS
en CSS size-adjust для @font-face
en Равные столбцы с Flexbox: это сложнее, чем вы думаете
en Эксперимент с сортируемыми мультиколоночными таблицами
en Знакомьтесь с :has: нативный CSS селектор
en Рог изобилия ContainerQueries
en Создание правил для font-size CSS и создание Fluid Type Scale

JavaScript


habr Как я ускорил движок на 13%
habr Прогнозирование временных рядов на JS: анализ данных для самых маленьких фронтендеров
habr Sparkplug неоптимизирующий компилятор JavaScript в подробностях
en Как создать фулстек-приложение с помощью Supabase и Next.js
en Реализация приватных полей в JavaScript
en Forever Functional: Мемоизация промисов
en Как реализовать принципы SOLID в JavaScript
en Автоматизируйте форматирование и исправление JavaScript кода с помощью Prettier и ESLint
en Современный JavaScript
en Выходя за рамки ESLint: обзор статического анализа в JavaScript
en Доберенные типы API для безопасности JavaScript DOM
en Как создать NFT с помощью JavaScript
en Rust с точки зрения JavaScript





Браузеры


habr Vivaldi 4.0 Первое приближение
Google признал неудачным эксперимент с показом только домена в адресной строке Chrome
en Возможности WebKit в Safari, продемонстрированные на WWDC21


Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Подробнее..

Webix Datatable. От простой таблицы к сложному приложению

14.06.2021 10:08:37 | Автор: admin

Эта статья будет интересна для тех, кто привык решать сложные задачи простыми методами. Работа с большими данными, на первый взгляд, может показаться сложной задачей. Но если вы владеете специальными инструментами, то организация и отображение больших наборов данных покажется вам не более чем забавным развлечением. Сегодня мы поговорим об одном из самых неординарных инструментов для работы с данными, который предоставляет нам команда Webix. Речь пойдет о таком простом и одновременно сложном виджете библиотеки Webix UI как DataTable. Давайте разбираться в чем его сила.

Библиотека Webix и виджет DataTable

Webix UI это современная и производительная JavaScript библиотека, которая позволяет создавать дружественный интерфейс на основе собственных ui компонентов. Диапазон ее возможностей представлен виджетами различной сложности, начиная обычной кнопкой и заканчивая комплексными виджетами. Помимо самих компонентов, библиотека предоставляет множество дополнительных инструментов для работы с ними. Здесь стоит упомянуть механизм обработки событий, методы для работы с данными, взаимодействие с сервером, темы для стилизации и многое другое. Обо всем этом и не только, вы можете узнать в документации.

Виджет DataTable - это один из самых функциональных компонентов библиотеки Webix. С его помощью вы можете отображать данные в виде таблиц и очень гибко их настраивать. Этот мощный и одновременно простой в использовании инструмент со стильным дизайном поддерживает различные форматы данных (XML, JSON, CSV, JSArray, HTML tables) и довольно быстро работает с большими объемами информации. Секрет его скорости заключается в так называемом "ленивом подходе отрисовке данных". Это не значит, что ему лень отрисовывать данные. Хотя, без сомнений, крупица правды в этом есть. Суть же подхода заключается в том, что даже если вы загрузите в таблицу 1 000 000 рядов, выджет отрисует только выдимые в окне браузера элементы. Стоит также сказать, что среди своих конкурентов виджет удерживает лидирующее место по скорости отрисовки.

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

Можно много рассуждать об удобстве работы с таблицами Webix и их обширных возможностях, но я предлагаю оставить патетику ораторам и попробовать разобраться во всем на практике. Давайте создадим небольшое приложение, которое будет отображать таблицу данных об аренде автомобилей. На наглядном примере гораздо проще увидеть все преимущества работы с этим мощным инструментом. Успешное применение этого виджета также описано в статье "Создаем Booking приложение с Webix UI".

С готовым приложением можно ознакомиться тут.

Базовые приготовления

Для того чтобы использовать возможности библиотеки Webix в нашем приложении, необходимо подключить ее в главном файле index.html. Здесь стоит упомянуть о том, что существует 2 версии библиотеки: базовая и Pro-версия. Базовая версия бесплатная и предоставляет ограниченный набор возможностей, по сравнению с Pro-версией. Мы же воспользуемся тестовой лицензией расширенной Pro-версии, чтобы по максимуму реализовать возможности виджета DataTable. Необходимые файлы доступны через CDN по следующим ссылкам:

<script type="text/javascript" src="http://personeltest.ru/away/cdn.webix.com/site/webix.js"></script><link rel="stylesheet" type="text/css" href="http://personeltest.ru/away/cdn.webix.com/site/webix.css">

Нам остается только включить их в файл index.html нашего приложения. Теперь он будет так:

<!DOCTYPE html><html>  <head>    <title>Webix Booking</title>    <meta charset="utf-8">    <!--Webix sources -->    <script type="text/javascript" src="http://personeltest.ru/away/cdn.webix.com/site/webix.js"></script>    <link rel="stylesheet" type="text/css" href="http://personeltest.ru/away/cdn.webix.com/site/webix.css">  </head>  <body>    <script type="text/javascript">      //...    </script>  </body></html>

Внутри кода мы добавим теги <script>...</script>, где и будем собирать наше приложение.

Инициализация

Все основные действия будут разворачиваться внутри конструктора webix.ui(). Нам же нужно удостовериться в том, что код начнет выполняться после полной загрузки HTML страницы. Для этого необходимо обернуть конструктор в webix.ready(function(){}). Выглядит это следующим образом:

webix.ready(function(){  webix.ui({    /*код приложения*/  });});

Мы создали базовый index.html файл и подключили необходимые инструменты. Теперь самое время перейти непосредственно к настройке самого компонента DataTable.

Сила в простоте

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

const datatable = {  view:"datatable",  autoConfig:true,  url:"./data/data.json"}

Сам компонент DataTable объявляется с помощью выражения view:"datatable". Через свойство url мы задаем путь, по которому виджет загружает данные. Стоит уточнить, что по умолчанию виджет ожидает получить данные в формате JSON. Если данные приходят в другом формате (xml, jsarray или csv), нужно указать его через свойство datatype. В случае, когда данные находятся на клиенте в виде массива, их можно передать компоненту через свойство data или метод parse().

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

Теперь давайте отобразим компонент в браузере и посмотрим, что же у нас получилось. Для удобства, мы сохраним конструктор виджета в переменную datatable, которую будем использовать для сборки в файле index.html.

С помощью следующего кода мы подключаем файл с компонентом DataTable в файле index.html:

<!--App sources --><script src="js/datatable.js" type="text/javascript" charset="utf-8"></script>

В конструкторе приложения мы указываем переменную, которая хранит настройки виджета:

<script type="text/javascript"> webix.ready(function(){  webix.ui( datatable ); });</script>

В браузере мы увидим следующий результат:

AвтонастройкаAвтонастройка

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

Тонкости настроек таблицы

Если автонастройка кажется вам слишком уж тривиальным вариантом, тогда давайте усложним задачу и зададим индивидуальные настройки для каждого столбца в отдельности. Сделать это можно в массиве свойства columns:[ ]. Для каждого столбца нужно задать объект с соответствующими настройками. Стоить учитывать то, что порядок отображения столбцов в таблице напрямую зависит от порядка объектов с настройками в массиве.

В первую очередь, для каждого столбца нужно задать уникальный id. Он должен соответствовать ключу, под которым данные хранятся в объекте (хранилище). Затем можно указать название для шапки столбца с помощью свойства header.

После этого можно задать ширину каждого столбца. Для этого предусмотрены такие свойства как width, minWidth, maxWidth и fillspace. Если мы хотим, чтобы ширина таблицы подстраивалась под ширину контейнера или под доступное ей место, нужно использовать свойство fillspace по крайней мере для одного столбца. Теперь настройки столбцов будут выглядеть так:

{  view:"datatable",  columns:[    { id:"rank", header:"Rank", width:45 },    //...    { id:"vin_code", header:"VIN", minWidth:50, width:180, maxWidth:300 },    //...    { id:"address", header:"Address", minWidth:200, fillspace:true },    //...  ],  url:"./data/data.json"}

В браузере мы получим следующий результат:

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

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

Помимо индивидуальных настроек ширины для каждого столбца, вы можете задать глобальные настройки размеров для всех столбцов в конструкторе компонента. Для этого предусмотрено свойство autowidth, которое подстраивает ширину виджета к суммарной ширине всех столбцов таблицы, а также свойство columnWidth, которое задает фиксированную ширину для всех столбцов. По умолчанию стандартная ширина столбца равна 100px.

Для полноты картины давайте предоставим пользователям возможность изменять размеры столбцов. Для этого необходимо задать свойству resizeColumn значение true, а также установить границы для рядов, столбцов и их хедеров с помощью свойства css:"webix_data_border webix_header_border".

Так как у таблицы есть много столбцов, необходимо предусмотреть горизонтальную и вертикальную прокрутку. Сделать это можно с помощью свойства scroll, которое изначально установлено в значении false. Нам же нужно задать ему значение xy.

Теперь мы можем изменять размер столбцов, перетаскивая их правую границу в нужном направлении, а также скролить таблицу по горизонтали и вертикали.

Настройка содержимого ячеек

Работа с шаблонами

По умолчанию, ячейки таблицы заполняются данными, ключ которых задан в качестве id в настройках столбца. Но виджет позволяет нам управлять их отображением. С помощью свойства template мы можем задать необходимый шаблон, по которому данные будут отображаться в ячейке. Значение можно указать как в виде строки, так и в виде функции. Чтобы использовать в строковом шаблоне входящие данные, их ключ нужно указать как #data_key#. У нас есть несколько столбцов, для которых необходимо задать шаблоны.

Начнем мы пожалуй с первого столбца, в ячейках которого будут размещаться иконки в виде звездочек. У вас может возникнуть резонный вопрос, зачем нам такой столбец со звездочками? А мы сделаем так, чтобы наш потенциальный пользователь смог отмечать понравившиеся ему варианты при клике по звездочке. Давайте это реализуем с помощью темплейт-функции. Код будет выглядеть следующим так:

{  view:"datatable",  id:"car_rental_table",  //...  columns:[    { id:"stared", header:"",     template:function(obj){       return `<span class='webix_icon star mdi mdi-"+(obj.star ? "star" : "star-outline") + "'></span>`;     }, ...,    },     //...  ]}

Свойству template мы присваиваем функцию, которая возвращает элемент span с определенными классами. Классы star и star-outline мы будем менять динамически при клике по иконке. Давайте создадим функцию, которая будет менять классы для иконок этого столбца:

function selectStar(id){  const table = $$("car_rental_table");  const item = table.getItem(id);  const star = item.star?0:1;  item.star = star;}

В качестве аргумента функция принимает id выбранного ряда. Через метод $$("car_rental_table") мы получаем доступ к виджету по его id. С помощью метода таблицы getItem(), который принимает id элемента в качестве параметра, мы получаем объект данных ряда. Затем проверяем наличие ключа star и присваиваем ему значение 0 (если он существует) либо 1 (если его нет).

Чтобы переключение начало работать, необходимо установит эту функцию в качестве обработчика на событие клика по иконке со звездочкой. Для того чтобы установить обработчик на любой элемент таблицы с определенным css классом, у таблицы предусмотрено специальное свойство onClick:

//...url:"./data/data.json",onClick:{  "star":(e,id) => selectStar(id)},//...

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

Шаблон для столбца со звездочкамиШаблон для столбца со звездочками

На очереди у нас столбец с названием Available. В его ячейках хранятся значения true и false, которые обозначают доступность автомобиля в текущий момент времени. Давайте зададим шаблон, который будет менять входящее значения ячейки на соответствующий текст Yes или No.

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

function customCheckbox(obj, common, value){  if(value){    return "<span class='webix_table_checkbox checked'> YES </span>";  }else{    return "<span class='webix_table_checkbox notchecked'> NO </span>";  }}

Теперь нужно установить эту функцию в качестве шаблона для столбца Available:

columns:[  //...  { id:"active", header:"Available", template:customCheckbox, ...,},]

В браузере результат будет следующим:

Шаблон для столбца "Available"Шаблон для столбца "Available"

Выглядит уже неплохо. Так как в первых трех столбцах у нас находятся данные, которые постоянно должны быть на виду, давайте их зафиксируем в левой части таблицы. Для этого необходимо добавить свойство leftSplit в конструктор виджета и задать ему количество желаемых столбцов (в нашем случае 3). Теперь эти столбцы будут отображаться поверх других элементов при вертикальном скролле.

А сейчас давайте займемся столбцом под названием Color. Его значения отображаются в виде HEX кодов, которые обозначают цвет автомобиля. Для обычного пользователя такие значения ничего не говорят. Давайте добавим визуальное представление цвета для каждой ячейки. И сделаем мы это также с помощью шаблона. Код будет выглядеть так:

columns:[  //...  { id:"color", header:"Color", template:`<span style="background-color:#color#; border-radius:4px; padding-right:10px;">&nbsp</span> #color#`},  //...]

Здесь мы используем строковый шаблон, в котором задаем фон неразрывного пробела (&nbsp) с помощью входящего HEX кода.

В браузере результат будет следующим:

Шаблон для столбца "Color"Шаблон для столбца "Color"

Работа с коллекциями

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

Для примера, давайте рассмотрим столбец с названием "Сar make", в котором должны отображаться марки автомобилей. Данные для его ячеек хранятся в виде чисел от 1 до 24 под ключем "car_make":

//data.json[  { "id":1, "rank":1, "car_make":22, ..., "country":1, "company":1, ..., },  { "id":2, "rank":2, "car_make":10, ..., "country":2, "company":3, ..., },  { "id":3, "rank":3, "car_make":16, ..., "country":1, "company":2, ..., },  //...]

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

//car_make.json[  { "id":22, "value":"Toyota" }, ...,  { "id":10, "value":"GMC" }, ...,  { "id":16, "value":"Mazda" }, ...,  //...]

В настройки столбца необходимо добавить свойство collection и присвоить ему путь к нужному объекту (коллекции):

columns:[  //...  { id:"car_make", header:"Car make", collection:"./data/car_make.json", ...,},  //...]

Вот таким образом, вместо числовых значений, в ячейках столбца Car make будут отображаться названия автопроизводителей. По такому же принципу мы заменяем значения для столбцов Company, Country и Card.

В браузере результат будет следующим:

Коллекции для столбцовКоллекции для столбцов

Работа с форматами данных

При работе с данными, не редко возникает потребность изменить формат их отображения. На этот случай, у таблицы имеется специальное свойство format. С его помощью мы можем задать желаемый формат отображения чисел и дат. Для этого предусмотрены специальные методы. Давайте зададим формат отображения для столбцов с датами и ценой заказа:

columns:[  //...  { id:"date", header:"Date", format:webix.i18n.longDateFormatStr, ..., },  { id:"price", header:"Price", format:webix.i18n.priceFormat, ..., },  //...]

Данные о датах приходят в виде строк 05/26/2021. Нам же нужно получить дату в формате 26 May 2021. Метод webix.i18n.longDateFormatStr, который мы применили в настройках столбца Date, должен получать объект Date и возвращать строку в нужном формате. Но сейчас он получает только строку типа 05/26/2021, поэтому результат может быть неожиданным. Давайте изменим входящие данные и преобразуем строки в соответствующие Date объекты.

Для этого у таблицы предусмотрено свойство scheme. В объекте этого свойства мы меняем строковое значение даты на соответствующий объект с помощью метода webix.i18n.dateFormatDate. Код будет выглядеть следующим образом:

{  view:"datatable",  //...  scheme:{    $init:function(obj){      obj.date = webix.i18n.dateFormatDate(obj.date)    }  },  columns:[...]}

С форматированием даты мы разобрались. Теперь давайте посмотрим как изменить цену в столбце "Price". А здесь все еще проще. Метод webix.i18n.priceFormat получает число (например 199) и возвращает строку со знаком доллара в начале: $199. Вот и вся хитрость.

В браузере результат будет следующим:

Форматирование даты и ценыФорматирование даты и цены

Узнать больше о возможностях форматирования данных библиотеки Webix можно в этой статье.

Сортировка данных

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

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

  • "int" - сравнивает числовые значения

  • "date" - сравнивает даты

  • "string" - сравнивает строковые значения в том виде, в котором они загружаются

  • "text"- сравнивает текст элемента, который отображается в ячейке (включая темплейт)

    columns:[  { id:"car_model", header:"Model", width:120, ..., sort:"string", }, ...,  { id:"car_year", header:"Year", width:85, ..., sort:"int" }, ...,{ id:"country", header:"Country", width:140, ..., sort:"text" }, ...,{ id:"date", header:"Date", width:150, ..., sort:"date" }, ...,]
    

Теперь данные будут сортироваться при клике по хедеру определенного столбца. Более того, мы можем установить режим, который позволяет сортировать данные по нескольким критериям одновременно. Для этого нужно задать свойству sort значение "multi" в конструкторе виджета. Чтобы отсортировать данные по нескольким условиям, нужно нажать клавишу Ctrl/Command и кликнуть по хедерам нескольких столбцов.

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

Если же нужно отсортировать данные таблицы при клике по какой либо внешней кнопке, вы можете воспользоваться методом таблицы sort(). Подробнее о сортировке данных вы можете узнать в этой статье.

Фильтрация данных

Сложно представить себе полноценную таблицу без возможности фильтровать в ней данные. И такая возможность предусмотрена в таблице Webix. С помощью свойства content, мы можем добавить один из нескольких встроенных фильтров или же задать собственные условия фильтрации. Это позволит нам фильтровать данные на клиенте или сервере по одному или нескольким критериям.

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

columns:[  //...  {    id:"company",     header:["Company",{content:"selectFilter"}],     collection:"./data/company.json", ...,  }, ...,]

В браузере результат будет следующим:

Фильтр selectFilterФильтр selectFilter

Для столбца с названием Car make мы добавим фильтр textFilter. Он представляет собой обычное поле для ввода данных. Фильтр будет сравнивать введенные значения с данными столбца. Хочу напомнить, что данные приходят сюда в виде чисел, которые преобразуются в соответствующие названия моделей авто. Дело в том, что фильтр будет сравнивать введенные значения именно с числами, а это нас не совсем устраивает. Давайте изменим поведение фильтра по умолчанию и сделаем так, чтобы введенные значения сравнивались с данными из коллекции. Для этого мы добавим к фильтру специальную функцию для сравнения:

columns:[  //...  { id:"car_make", header:["Car make", {    content:"textFilter", placeholder:"Type car make",    compare:function(item, value, data){       const colValue = cars_make_data.getItem(item).value;      const toFilter = colValue.toLowerCase();      value = value.toString().toLowerCase();      return toFilter.indexOf(value) !== -1;    } }], collection:cars_make_data, ...,  },  //...]

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

columns:[  //...  { id:"car_model", header:["Model", {content:"textFilter", placeholder:"Type model"}, ...,],  //...]

В браузере результат будет следующим:

Фильтр textFilterФильтр textFilter

Теперь давайте перейдем к столбцу с названием Year, в ячейках которого отображается год производства автомобилей. Здесь мы добавим такой фильтр как excelFilter. Особенность его заключается в том, что мы можем вводить данные в поле ввода, задавать дополнительные условия и отмечать нежелательные элементы в специальных чекбоксах. Настройки столбца будут выглядеть так:

columns:[  //...  { id:"car_year", header:[{text:"Year", content:"excelFilter", mode:"number"}], ...,},  //...]

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

Фильтр excelFilterФильтр excelFilter

Среди наших столбцов есть и такой, в котором отображаются доступные даты аренды автомобилей. Для работы с датами у виджета есть специальный фильтр datepickerFilter. Он позволяет выбрать необходимую дату в компактном всплывающем календаре. Настройки столбца будут выглядеть так:

columns:[  //...  { id:"date", header:["Date", {content:"datepickerFilter"}], ..., },  //...]

В браузере результат будет следующим:

Фильтр datepickerFilterФильтр datepickerFilter

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

Редактирование данных

Функционал виджета позволяет редактировать данные непосредственно в ячейках таблицы. Чтобы активировать эту опцию, необходимо задать свойству editable значение true в конструкторе таблицы. Также можно определить действие, по которому будет открываться редактор ячейки. По умолчанию, редактор открывается при клике по ячейке. Можно также определить открытие по двойному клику (dblclick) или указать собственное действие (custom).

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

А начнем мы из самого часто используемого редактора под названием text. Он превращает ячейку в обычное поле для ввода, куда можно ввести нужные нам данные. Давайте добавим его для нескольких столбцов нашей таблицы. Делается это очень просто: в настройках столбца нужно всего лишь указать свойство editor и присвоить ему соответствующий тип редактора. Настройки столбцов будут выглядеть так:

{  view:"datatable",  //...  editable:true,  editaction:"dblclick",  columns:[    { id:"rank", header:"Rank", editor:"text", ..., },    { id:"car_model", header:"Model", editor:"text", ..., },    { id:"manager", header:"Manager", editor:"text", ..., },    //...  ],  //...}

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

Редактор "text"Редактор "text"

Если в ячейке будет находиться большой текст, то редактировать его в маленьком поле будет не очень удобно. Для таких случаев предусмотрен редактор popup. Он позволяет редактировать данные в специальном всплывающем окне. По умолчанию ширина и высота окна равны 250px и 50px соответственно. Давайте добавим этот редактор в настройки столбца Address:

columns:[  { id:"address", header:"Address", editor:"popup", ...,},  //...],//...

При двойном клике по любой ячейке столбца Address, в браузере мы увидим следующий результат:

Редактор "popup"Редактор "popup"

Теперь перейдем к столбцу c названием Available. Как вы помните, для него мы задали шаблон, который превращает значения true и false в соответствующие строки YES и NO. Давайте сделаем так, чтобы пользователь смог переключаться между этими значениями. Для этого мы используем специальный редактор inline-checkbox. Он позволяет менять значения в ячейке при клике по ней. Но для работы этого редактора также необходимо задать свойству checkboxRefresh значение true. Это свойство обновляет данные, полученные из checkbox-редакторов в таблице. Настройки столбца будут выглядеть так:

{  //...  checkboxRefresh:true  columns:[    //...    { id:"active", header:"Available", editor:"inline-checkbox", template:customCheckbox, ..., },  //...  ],  //...}

При клике по любой ячейке этого столбца, его значение изменится на противоположное.

А на очереди у нас столбцы, данные для которых подтягиваются из коллекций. Для них лучше всего подойдет редактор combo. Он позволяет выбирать нужную опцию из выпадающего списка. Список формируется автоматически исходя из данных коллекции столбца. Настройки столбцов будут выглядеть так:

columns:[  { id:"company", header:"Company", editor:"combo",    collection:"./data/company.json", ..., },  { id:"car_make", header:"Car make", editor:"combo",    collection:cars_make_data, ..., },  { id:"country", header:"Country", editor:"combo",   collection:"./data/country.json", ..., },  //...],//...

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

Редактор "combo"Редактор "combo"

Особого внимания заслуживает столбец под названием Color. Хочу напомнить, что входящие данные представляют собой HEX коды различных цветов. У таблицы Webix есть специальный редактор, который позволяет выбрать необходимый цвет в специальном всплывающем окне, а его код отобразится в ячейке столбца. Речь идет о таком редакторе как color. Настройки столбца будут выглядеть так:

columns:[  { id:"color", header:"Color", editor:"color", template:..., },  //...], //...

При двойном клике по любой ячейке столбца Color, в браузере мы увидим следующий результат:

Редактор "color"Редактор "color"

Теперь нам осталось разобраться с редактированием данных в столбце Date. Для работы с датами у таблицы предусмотрен специальный редактор date. Как и в случае с фильтром, возле ячейки появится компактный календарь, где вы сможете выбрать нужную дату, которая сразу отобразится в редактируемой ячейке. Настройки столбца будут выглядеть так:

columns:[  {     id:"date", header:"Date", editor:"date",     format:webix.i18n.longDateFormatStr, ...,   },  //...], //...

При двойном клике по любой ячейке столбца Date, в браузере мы увидим следующий результат:

Редактор "date"Редактор "date"

Более подробно о редактировании таблицы можно узнать в соответствующей статье документации. А мы двигаемся дальше.

Валидация

После редактирования данных, они должны сохраняться на сервере. Здесь хорошим тоном будет проверить измененную информацию перед тем как ее сохранить. Как это сделать спросите вы? Да все донельзя просто. У библиотеки Webix есть специальные правила, которые можно применить для каждого редактируемого поля. В зависимости от типа данных, мы будем применять разные правила.

У нас есть несколько столбцов с числовыми значениями, для которых мы установим правило webix.rules.isNumber. Таблица будет проверять, является ли значение в текстовом поле числом.

Также у нас есть столбец, который содержит адреса электронной почты. Его значения мы будем проверять правилом webix.rules.isEmai.

Для столбца с ценами необходимо задать собственные условия проверки. Давайте сделаем так, чтобы пользователь мог ввести только данные в диапазоне от 20 до 500 долларов. Для этого мы создадим специальную функцию:

function(obj){ return (obj>20 && obj<500) }

Все остальные столбцы мы будем проверять правилом webix.rules.isNotEmpty. Это значит, что в любом случае они должны быть заполнены.

Чтобы применить все эти правила, у таблицы есть специальное свойство rules. Внутри объекта этого свойства необходимо указать id нужных столбцов и присвоить им соответствующие правила. Выглядит это так:

column:[...],rules:{  rank:webix.rules.isNumber,  company:webix.rules.isNotEmpty,  email:webix.rules.isEmail,  price:function(obj){ return(obj>20 && obj<500) },  // правила для других столбцов}

По умолчанию, валидация начнется после закрытия редактора. Если введенные значения не соответствуют правилам, то целый ряд подсветится красным, а в правом верхнем углу невалидной ячейки появится красная метка:

ВалидацияВалидация

Хедеры и футеры

Если вы работаете с большими данными, которые разделяются на множество столбцов, часто возникает необходимость объединить их названия в определенные категории. Такой подход помогает структурировать таблицу и упрощает поиск нужной информации. Виджет DataTable позволяет объединять хедеры с помощью свойств colspan и rowspan, которые немного похожи на настройки обычной HTML таблицы. Для примера, давайте посмотрим как объединить столбцы Price, Card и IBAN в категорию Payment information. Для этого нужно немного изменить свойство header вышеуказанных столбцов:

column:[  //...  { id:"price", header:[{text:"Payment information", colspan:3}, "Price"], ..., },  { id:"credit_card", header:["","Card"], ..., },  { id:"iban", header:["","IBAN"], ..., },  //...]

В браузере мы получим следующий результат:

Объединяем хедерыОбъединяем хедеры

Если хедеры подключены по умолчанию, то футеры нужно активировать отдельно. Для этого необходимо задать свойству footer значение true в конструкторе виджета. Давайте определим название футера для первого столбца и объединим его с футером второго столбца при помощи свойства colspan. А в футере столбца Available, где хранятся данные о доступных автомобилях, мы будем подсчитывать и отображать активные варианты. Настройки столбцов будут выглядеть так:

column:[  //...  { id:"stared", header:[...], ..., footer:{ text:"Available:", colspan:2 } },  //...  { id:"active", header:[...], ..., footer:{content:"summColumn"}, ..., },//...]

Элемент, заданный как {content:"summColumn"} , будет подсчитывать все значения равные true и отобразит их количество в футере. Все изменения в ячейках столбца Available незамедлительно отобразятся в его футере. В браузере мы получим следующий результат:

ФутерыФутеры

Управление видимостью столбцов

Если ваша таблица состоит из множества столбцов, то было бы очень удобно спрятать ненужные в данный момент варианты. Разработчики Webix позаботились о решении этого вопроса и реализовали такую опцию как headerMenu. С ее помощью мы можем управлять видимостью столбцов через специальный интерфейс. По желанию, его можно настроить по своему усмотрению.

Давайте добавим иконку headerMenu в качестве хедера для первых двух столбцов и настроим всплывающее окно меню. Код будет выглядеть так:

//...headermenu:{  width:210,  data:[     { id:"car_year", value:"Year" },    { id:"color", value:"Color" },    { id:"vin_code", value:"VIN" },    { id:"phone_number", value:"Phone" },    //...  ]},column:[  { id:"stared", header:[{ content:"headerMenu", colspan:2, ...,}], ..., },  { id:"rank", header:["",""], ..., },  //...]

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

В браузере мы увидим такой результат:

Опция headermenuОпция headermenu

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

Пагинация для таблицы

Если количество рядов таблицы достаточно большое, то использовать очень длинную вертикальную прокрутку не совсем удобно. Для решения этого вопроса можно добавить пагинацию. И как вы наверное уже догадались, виджет DataTable предоставляет нам такую возможность.

Для начала, давайте создадим модуль пагинации в файле pager.js. Его код будет выглядеть так:

//pager.jsconst pager = {  view:"pager",  id:"pager",  size:20,  group:5,  template:`{common.first()} {common.prev()} {common.pages()} {common.next()} {common.last()}`};

С помощью свойств size и group мы устанавливаем количество элементов на странице (20) и число видимых кнопок для пагинатора (5). Вот, в принципе, и все настройки. Также можно задать свойство template, которое определяет кнопки для переключения страниц (помимо кнопок с цифрами).

Теперь давайте подключим модуль с компонентом в файл index.html и добавим переменную с пагинатором в конструктор приложения:

//index.html<!--App sources --><script src="js/datatable.js" type="text/javascript" charset="utf-8"></script><script src="js/pager.js" type="text/javascript" charset="utf-8"></script>//...<script type="text/javascript">  webix.ready(function(){    webix.ui({      rows:[        datatable,        {cols:[          {},pager,{}        ]}      ]    });  });</script>

Ну и в завершение, давайте свяжем пагинатор с таблицей, чтобы при переключении кнопок, данные таблицы менялись в соответствии с настройками (по 20 рядов на странице). Для этого нужно задать свойству pager значение id созданного нами пагинатора в конструкторе таблицы. Вот такими нехитрыми манипуляциями реализуется пагинация для таблицы в библиотеке Webix. В браузере мы увидим следующий результат:

ПагинацияПагинация

Операции с рядами таблицы

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

Стоит отметить, что в библиотеке Webix есть несколько способов добавлять иконки. Можно использовать иконки из встроенного шрифта (<span class='webix_icon wxi-drag'></span>), или специальные встроенные элементы (common.trashIcon()).

Чтобы это реализовать, нужно перейти к массиву свойства columns и добавить следующие настройки:

column:[  //...  {     header:[{text:"<span class='webix_icon wxi-plus-circle'></span>", colspan:2}],     width:50, template:"<span class='webix_icon wxi-drag'></span>"   },  { header:["",""], width:50, template:"{common.trashIcon()}" }]

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

Иконки для операций с рядамиИконки для операций с рядами

Иконки у нас готовы. Теперь давайте установим обработчики на событие клика по этим иконкам. Чтобы поймать событие клика по любому элементу таблицы с определенным css классом, необходимо воспользоваться свойством onClick. В объекте этого свойства нужно указать класс иконки и присвоить ему соответствующий обработчик. В нашем случае, мы ловим клик по иконкам с классами wxi-plus-circle и wxi-trash:

onClick:{  "wxi-plus-circle":() => addNewElement(), //добавляет элемент  "wxi-trash":(e,id) => removeElement(id), //удаляет элемент  //...,}

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

function addNewElement(){  const table = $$("car_rental_table"); //получаем доступ к таблице  //добавляем данные  const id_new_elem = table.add({"active":0,"color":"#1c1919","date":new Date()});   table.showItem(id_new_elem); //показываем новый элемент в таблице}

С помощью метода таблицы add() мы можем добавить в нее новые данные. Этот метод возвращает id новой записи, который мы передаем другому методу таблицы showItem(), чтобы показать (проскролить) этот элемент в таблице.

Функция для удаления записи будет выглядеть так:

function removeElement(id){  $$("car_rental_table").remove(id);}

Метод таблицы remove() получает id выбранного элемента в качестве параметра и удаляет его из таблицы.

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

Сейчас перетаскивать элемент можно за любую его часть. Давайте ограничим зону перетаскивания на специально созданной иконке с классом wxi-drag .

Для этого мы воспользуемся свойством on, в объекте которого и установим обработчик на событие onBeforeDrag:

on:{  onBeforeDrag:function(data, e){     return (e.target||e.srcElement).className == "webix_icon wxi-drag";  }}

В браузере мы увидим следующий результат:

Перетаскивание рядовПеретаскивание рядов

Теперь мы можем добавлять, удалять и перетаскивать ряды таблицы при помощи специальных иконок.

Тулбар с дополнительными опциями

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

В файле toolbar.js мы создаем компонент toolbar, внутри которого определяем кнопки Reset filters, Add column и Export to Excel. Выглядит это так:

const toolbar = {  view:"toolbar",  css:"webix_dark",  height:50,  //...  cols:[    //...    { view:"button", label:"Reset filters", click:resetFilters },    { view:"button", label:"Add column", click:addColumn },    { view:"button", label:"Export to Excel", click:exportToExcel }  ]};

С помощью свойства click мы устанавливаем обработчики на событие клика по кнопкам. Давайте создадим эти обработчик. А начнем мы из функции, которая будет сбрасывать фильтрацию данных таблицы и сами фильтры. Код будет выглядеть так:

function resetFilters(){  const table = $$("car_rental_table");  table.filter();   table.showItem(table.getFirstId());   table.setState({filter:{}}); }

Метод filter(), вызванный для таблицы без параметров, отображает данные в первоначальном порядке. С помощью метода таблицы setState() мы очищаем значения полей фильтров.

Следующей на очереди у нас функция, которая будет добавлять новые столбцы. Код будет выглядеть так:

function addColumn(){  const table = $$("car_rental_table");  table.config.columns.splice(3,0,{    id:"c"+webix.uid(),    header:`<span class="webix_icon wxi-close-circle" webix_tooltip="Delete column"></span>Extra column`,    editor:"text",    width:120  });  table.refreshColumns();}

С помощью свойства таблицы config.columns мы получаем массив с настройками столбцов и добавляем туда объект с настройками нового столбца в 4 позицию. Для этого используем js метод splice(). Когда данные изменены, нужно обновить представление столбцов с помощью метода таблицы refreshColumns().

И у нас осталась только функция, которая будет экспортировать данные таблицы в формате Excel. Код будет выглядеть так:

function exportToExcel(){  webix.toExcel("car_rental_table", {    filename:"Car Rental Table",    filterHTML:true,    styles:true  });}

Внутри функции мы используем метод webix.toExcel(), которому передаем id таблицы и объект с необходимыми настройками. Вот и вся хитрость.

Когда все уже готово, нужно включить файл toolbar.js в файл index.html и добавить переменную toolbar в конструктор приложения:

webix.ui({  rows:[    toolbar,    datatable,    {cols:[    {},pager,{}    ]}  ]});

В браузере наше приложение будет выглядеть следующим образом:

Тулбар с кнопками Тулбар с кнопками

Теперь мы можем сбрасывать фильтрацию данных, добавлять новые столбцы, а также экспортировать данные в формате Excel.

Остался еще один нюанс. При создании нового столбца, в его хедер мы добавляем иконку с классом wxi-close-circle. Нам нужно установить обработчик на событие клика по этой иконке, который будет удалять столбец. Сделать это можно в объекте свойства onClick:

onClick:{  //...  "wxi-close-circle":(e,id) => deleteColumn(id)}

Теперь давайте создадим этот обработчик:

function deleteColumn(id){  const table = $$("car_rental_table");  table.clearSelection();  table.editStop();  table.refreshColumns(table.config.columns.filter(i=>i.id !== id.column));}

Через свойство config.columns мы получаем массив настроек столбцов, отфильтровываем из него ненужный элемент и передаем обновленный массив методу таблицы refreshColumns().

В браузере мы увидим следующий результат:

Добавляем и удаляем новый столбецДобавляем и удаляем новый столбец

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

Заключение

С кодом готового приложения можно ознакомиться тут.

В этой статье мы детально разобрали, как создать приложение на основе виджета DataTable библиотеки Webix UI, а также научились настраивать таблицу с помощью большого разнообразия свойств и методов. Как вы могли убедиться, все настройки интуитивно понятны и просты в применении. На самом деле, это только малая часть всего, что может предложить нам таблица Webix. Для более детального ознакомления можно перейти к документации, где вы сможете найти подробное описание всех возможностей компонента с наглядными примерами.

Подробнее..

Перевод Пробелы бывают разные ampnbsp C2A0

18.06.2021 16:08:17 | Автор: admin
image

Я только что потратил более двух часов на устранение, казалось бы, простой проблемы с HTML. Когда я скопировал и вставил небольшой раздел HTML, веб-браузер отображал только что вставленный раздел не так, как оригинал. Горизонтальный интервал между некоторыми элементами был немного другим, из-за чего вся страница выглядела неправильно. Но как такое могло быть? Два раздела HTML были идентичны новый был буквально копией старого.

Эта простая на первый взгляд проблема бросала вызов всем моим попыткам ее объяснить. Я придумал множество замечательных теорий: проблемы с моими классами CSS или с полями и отступами. Несоответствующие теги HTML. Ошибки браузера. Я попробовал три разных браузера и во всех получил одинаковые результаты.

Чувствуя себя сбитым с толку, я снова посмотрел на два раздела HTML в редакторе WordPress (текстовое представление) и подтвердил, что они полностью идентичны. Затем я попробовал встроенные в Firefox инструменты веб-разработчика для просмотра отображаемых элементов страницы и сравнил все их свойства CSS. Идентичны, но каким-то образом визуализированы по-разному. Я использовал инструменты разработчика, чтобы проверить точный HTML, полученный с моего веб-сервера, снова проверил два раздела и убедился, что они символьно идентичны. Инструмент Firefox источник страницы также подтвердил, что эти два раздела полностью идентичны.

К этому моменту я был готов обвинить космические лучи или магию вуду. Я обнаружил, что каждый раз, когда я копирую любой похожий раздел HTML, только что вставленный раздел будет отображаться в браузере с неправильным интервалом между элементами. Как такое могло быть? Затем я попробовал W3C Validator, который обнаружил некоторые другие проблемы с моей страницей, но ничего не могло объяснить такое поведение. И снова он подтвердил, что, несмотря на разную визуализацию в браузере, два раздела HTML идентичны.

Ясно, что что-то не складывалось. Я использовал curl для загрузки веб-страницы со своего веб-сервера, просмотрел локальную копию и увидел то же поведение, что и раньше. Но когда я открыл сохраненный документ .html с помощью шестнадцатеричного редактора, я наконец получил ответ. Эти два раздела HTML не были идентичными: в одном разделе использовался другой тип пробела, чем в другом.

Что за черт.

Я обнаружил, что исходный раздел HTML содержит неразрывные пробелы. Но вместо того, чтобы кодировать их с помощью они были закодированы юникод-символами C2A0. Не знаю, когда и как это произошло, но виню в этом WordPress. При просмотре этого раздела в редакторе HTML WordPress пробелы C2A0 выглядели как обычные пробелы, и при копировании раздела внутри редактора неразрывные пробелы автоматически преобразовывались в нормальные пробелы с шестнадцатеричным значением 20. Таким образом, скопированная версия отображалась по-другому, хотя исходный HTML оказался таким же.

Это похоже на ремейк 0 О, только хуже. Я даже не знал, что неразрывные пробелы имеют свою кодировку в Юникоде я подумал, что был единственным способом их закодировать. Я снова изменил HTML, чтобы использовать и теперь все работает нормально.

Я удивлен, сколько разных инструментов не смогли выявить это тонкое, но важное различие между типами пробелов в исходном HTML-коде. Редактор HTML WordPress не смог показать или правильно обработать разницу. Сбой инструментов веб-разработчика Firefox и инструментов источника страниц. Ошибка исходного представления валидатора W3C. Curl плюс шестнадцатеричный редактор был единственным способом окончательно установить достоверную информацию о точном содержании исходного кода HTML.
Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 473 (14 20 июня 2021)

21.06.2021 00:15:47 | Автор: admin
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.


Медиа|Веб-разработка|CSS|JavaScript


Медиа


podcast Подкаст Веб-стандарты 286: Высокопроизводительное хранилище для вашего приложения: Storage Foundation API
podcast Подкаст Callback Hell: Микрофронтенды и Module Federation, почему компании боятся открывать свой код, игровая выставка E3
podcast Новости 512 от CSSSR: Canvas-рендеринг, Lighthouse 8, пропорции в CSS, PHP 8.1 alpha, Next.js 11, Линус и антипрививочник
podcast video Подкаст Ленивый фронтендер #2 Kaiwa Show | Как сохранить любовь к веб-разработке
podcast Подкаст Фронтенд Юность #191: HR'ы немножко осатанели


Веб-разработка


habr <img>. Доклад Яндекса
habr Темизация. История, причины, реализация
habr DIV должен уйти: улучшаем HTML
en Изучение Eleventy с нуля. Бесплатный курс, состоящий из 31 урока
en Как я использовал WAAPI для создания библиотеки анимации
en Десять лет веб-компонентам



CSS


video :has в CSS псевдокласс из будущего на примере карточки новости
en Использование свойства `outline` в качестве схлопывающейся границы
en Идеальные всплывающие подсказки с обрезкой и маскированием CSS
en Оптический размер, скрытая сверхспособность вариативных шрифтов
en Краткое руководство по логическим свойствам CSS
en Застенчивая кнопка стоимостью 8 миллионов долларов
en Создание таблиц с липким верхним и нижним колонтитулами стало немного проще

JavaScript


habr Скрываем номера курьеров и клиентов с помощью key-value хранилища
habr Юмористичный обзор Rust с перспективы JavaScript
en Управление состоянием: двусторонние биндинги и расширенные средства форматирования биндингов
en Что такое букмарклеты? Как использовать JavaScript для создания букмарклета в Chromium и Firefox
en Тестирование использования памяти в JavaScript
en Двойные кавычки против одинарных кавычек против обратных кавычек в JavaScript
en sorting-algos-visualizer Визуализация популярных алгоритмов сортировки: QuickSort, MergeSort, HeapSort, BubbleSort, InsertionSort







Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Подробнее..

Категории

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

  • Имя: Макс
    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