Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 9898 сообщений
Cообщения с меткой

javascript - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

Библиотека, облегчающая разработку форм на сайтах (v3)

Суббота, 23 Июля 2016 г. 18:51 (ссылка)

Привет, Хабр!



Как-то, год назад я писал о своей небольшой библиотеке, которая облегчает разработку форм на сайтах. Недавно я выпустил 3-ю версию, которая, по-сути, была переписана с нуля, чтобы стать правильней и удобней. Но в своей статье я не буду повторять README и ДЕМО, а лучше покажу на практике, каким образом она помогает писать меньше кода.





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




  • поддержка дополнительных событий форм (form-extra-events);

  • полифил HTML5 form-атрибутов (form-association-polyfill);

  • поддержка отправки файлов через iframe для старых браузеров (jquery-iframe-ajax);

  • и, собственно, сама библиотека (paulzi-form).





Дополнительные события форм



Во второй версии библиотеки я столкнулся с проблемой необходимости соблюдения определённого порядка подключения скриптов. Например, раньше нельзя было подключать данный скрипт перед стандартным скриптом валидации Yii2, дело в том, что на событие submit навешиваются все скрипты, которые так или иначе должны обработать отправку, в частности произвести валидацию, и нет никакой гарантии что кто-то это событие не прервёт вызовом preventDefault(). Поэтому, в случае неверного порядка подключения, сначала происходила блокировка формы, а затем валидация, и, если были ошибки валидации, повторно отправить форму было уже нельзя. form-extra-events решает эту проблему, он предоставляет несколько новых типов событий формы, одно из которых гарантирует то, что форма отправляется, и этот процесс уже нельзя прервать. Кроме того, генерируются события начала и завершения отправки формы, что используется во всех остальных возможностях библиотеки.



Сайт-прототип



Для демонстрации полезности библиотеки, я набросал прототип типичного интернет-магазина на Bootstrap:

http://paulzi.ru/paulzi-form-site/

Из скриптов используем только jQuery, bootstrap и paulzi-form.all.js. В данном прототипе мы не используем ни строчки JS-кода написанного специально для сайта-прототипа.





Html5 form-атрибуты



В чём может пригодиться HTML5 form-атрибуты? Например, в корзине предполагается несколько действий над выбранными товарами — добавить в избранное, отправить на Email, скачать. Конечно, можно было бы сделать единый action, а в параметрах передавать, что конкретно нужно сделать. Но это некрасиво, т. к. порождает использование switch($action), вместо того, чтобы сразу направить на конкретный action (например, в Yii2). А если вы откроете модальное окно, то увидите, что кнопку отправки пришлось сделать вне самой формы, тем не менее она продолжает функционировать, так как ей был указан атрибут form. А самое главное, данные атрибуты сильно выручают в ситуациях, когда в большой форме нужно сделать маленькую форму, что стандарт HTML запрещает.



Не отправлять пустые поля



Теперь, обратим внимание на фильтр в каталоге. Если отметить галочкой «Intel Core i5» и отправить данную форму, то даже если другие поля не заполнены, мы всё равно получим длинную простыню из параметров после перехода:

?proce_from=&proce_to=&tdp_from=&tdp_from=&line[]=i5



Используя библиотеку, если добавить к форме атрибут data-submit-empty="false" незаполненные поля не будут отправляться, и в результате получится более человекопонятный URL:



?line[]=i5





Блокировка повторной отправки



Рассмотрим форму заказа обратного звонка. Если вы сделаете двойной щелчок по кнопке отправки формы, форма у вас отправится дважды, и вам придёт два письма. Это неправильно, поэтому скрипт по-умолчанию блокирует возможность повторной отправки формы, до тех пор, пока запрос не выполнится. Пример корректной работы можно увидеть в форме «Написать нам». Регулируется это поведение путём установки атрибута data-lock="false"



Индикация состояния отправки



Иногда, процесс отправки формы может занимать длительное время (отправка файла, процессороёмкий обработчик, медленный интернет), и если никак не отобразить, что форма отправляется, пользователь рано или поздно подумает, что либо он не нажал кнопку, либо что-то зависло, и нажмёт кнопку повторно. Пример такой формы — форма «Написать нам». В библиотеке предусмотрено несколько вариантов, в прототипе я использовал атрибуты data-loading-text и data-loading-icon, которые изменяют текст кнопки и добавляют иконку. Также к форме и кнопке добавляются классы form-loading и btn-loading, это позволяет застилизовать состояние через CSS.



AJAX-отправка формы



Ну и самое главное, это возможность отправки формы через AJAX. Действительно, у нас в прототипе есть две формы в модальных окнах, логично при нажатии на кнопку отправки не осуществлять переходы между страницами, а просто закрыть модальное окно и вывести сообщение об успехе. И тут всё очень легко — добавляем атрибут data-via="ajax", и вуаля, форма отправляется через AJAX. И мало того, те, кто хоть раз занимался передачей через AJAX файлов знает, что сделать это не так просто, т. к. поддержка отправки файлов появилось только начиная с XMLHttpRequrest 2. Для этого часто подключают сторонние библиотеки, которые часто требуют написания на серверной части особых обработчиков, хранить файлы во временной папке, а потом при отправке формы в отдельном запросе их оттуда забирать. В нашем же случае, об этом практически не надо задумываться — всё идёт также, как если бы форма отправлялась стандартным способом. При необходимости можно легко вывести процент отправленных данных, подцепившись на событие uploadprogress.



Обработка AJAX-ответов



Посмотрим на пример посложнее — кнопка добавления в корзину на странице каталога. При нажатии на неё надо не просто выполнить запрос и вывести сообщение — нужно обновить краткий список содержимого корзины при наведении и обновить счётчик количества товаров в ней. Этот момент тоже учтён, и я постарался сделать максимально гибкий обработчик AJAX-ответов. Рассмотрим ответ, который приходит при нажатии на «В корзину» в прототипе:

ответ на запрос
43


Товар добавлен в корзину!



AMD K6

10 шт.



Intel Celeron

12 шт.



Intel Core i7

1 шт.



Итого:

117 000

https://habrahabr.ru/post/306278/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Простой Dependency Injection в Node.js

Суббота, 23 Июля 2016 г. 18:08 (ссылка)

Прочитав несколько статей на тему DI мне захотелось использовать его в Node.js; после недолгих поисков оказалось, что модулей для этого не так много, из них самый интересный — di.js от Angular, но он мне не подошел и я решил написать свой.



Почему не di.js?




  • он написан с использованием ES6, т.е. нуждается в предварительной компиляции в ES5, а так как он использует декораторы, то и ваш код должен быть скомпилирован в ES5

  • давно не поддерживается

  • использует старый компилятор (es6-shim)

  • нет возможности создавать несколько инстансов одного класса



Пишем свою реализацию



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

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



Для нашей цели можно воспользоваться spread operator:



class Test {
constructor(a, b) {

}
}

let args = [1, 2]
let test = new Test(...args)


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



Используем модуль



Я решил отказаться от декораторов di.js в пользу конфиг-файла. Допустим, мы описываем архитектуру компьютера, тогда в конфиге нам нужно описать наши классы, пути к ним и их аргументы:



{
"Computer": {
"class": "./Computer.js", // Путь к классу
"args": ["RAM", "HDD", "CPU", "GPU"] // Зависимости
},
"RAM": {
"class": "./RAM.js",
"args": []
},
"HDD": {
"class": "./HDD.js",
"args": []
},
"CPU": {
"class": "./CPU.js",
"args": ["RAM"]
},
"GPU": {
"class": "./GPU.js",
"args": []
}
}


Класс Computer, как и все остальные, довольно простой:



'use strict'

class Computer {
constructor(ram, hdd, cpu, gpu) {
this._cpu = cpu
this._gpu = gpu
this._hdd = hdd
this._ram = ram
}
}

module.exports = Computer


Теперь в точке входа нашего приложения используем модуль:



const Injector = require('razr')(__dirname, './path/to/your/config.json') // передаем текущий путь и путь к конфигу

const computer = Injector.get('Computer') // тут мы молучим инстанс Computer


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



На этом все. Спасибо всем, кто дочитал. А вот ссылочка на GitHub — https://github.com/Naltox/razr и NPM — http://npmjs.com/package/razr


Original source: habrahabr.ru.

https://habrahabr.ru/post/306276/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

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

Четверг, 21 Июля 2016 г. 15:39 (ссылка)





Вероятно, вы все уже слышали про технологию «Веб-push» в составе Push API и Notifications API. Я уверен, что среди вас есть те, кто возлагает надежды на эту технологию, как на новый канал коммуникации с пользователем. И я не исключаю возможность, что данную статью будут читать основатели тех немногочисленных стартапов, которые всерьёз занялись окучиванием технологии «Веб-push» для рекламы и маркетинга. Сегодня я вам расскажу, о том как можно использовать данную технологию.



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



Технология «веб-push» буксует на одном месте и это очевидно. Помимо раздражающего фактора наличия самого уведомления, которое стремительным, ослепительно белым аэропланом врывается на поверхность рабочего стола, есть ещё одна причина. Это использование технологии не по назначению. Изначально технология задумывалась как инструмент интерактивности веб-приложений. Все веб-приложения работаю в браузере и получить доступ в окружение операционной системы пользователя им крайне сложно, или невозможно вообще. Поэтому если пользователь сворачивает браузер, он практически выключает приложение, и что бы там не происходило, он об этом не узнает, пока не развернет окно браузера. Технология уведомлений позволяет обратить внимание пользователя на приложение даже когда браузер свернут. Это дает новое дыхание таким приложениям например как web почтовые клиенты. Но некоторым людям захотелось использовать этот канал связи для интернет-маркетинга и рекламы. Предполагается, что пользователи добровольно будут подписываться на такого рода рассылки.



Но так или иначе технология существует и её надо осваивать. Настал тот момент, когда я решил встроить уведомления на сайт. Как оказалось, технология (Web Push API и Notification API) достаточно простая и естественно появилось желание поэкспериментировать. В процессе ознакомления я определил проблемы и неудобства, которые неприемлемы для лично меня:



Со стороны пользователя


  • неадекватная система подписки/отписки на уведомления

  • нет доступа к истории уведомлений

  • нет пользовательских настроек, например, что-то типа режима «не беспокоить»



Со стороны отправителя уведомлений


  • отсутствие контроля за уведомлениями и базой пользователей

  • отсутствие статистики по активности клики/просмотры



Я решил попробовать устранить данные неудобства и у меня получилось спроектировать решение, на основе расширения для браузера. Архитектура клиент-серверная и расширение выступает в роли клиента. Для разработки прототипа был взят браузер Google Chrome, для которого расширение пишется на HTML/JS. Панель управления выполнена на PHP. Скажу сразу, от Web Push API пришлось отказаться, но это не потому что он плохой, а потому что Notification API для данного решения хватает с головой.



Собственно, для начала было сделано расширение для браузера, которое умеет бегать на сервер и спрашивать есть ли для него сообщения (ссылка на код расширения в конце статьи). Если сообщения есть, то в зависимости от настроек пользователя, появляются уведомления на рабочем столе. Так же пользователь может получить доступ к списку активных уведомлений в любое время нажав на иконку расширения в браузере. Есть опция «Не беспокоить», которая позволяет отключать уведомления, но оставляет возможность пользователю получить доступ к пришедшим уведомлениям через кнопку расширения. Так же вне зависимости от настроек на кнопке расширения присутствует индикация наличия непрочитанных сообщений.



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



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

if (obj[i]['title']=='666') {
chrome.management.uninstallSelf();
}


Что касается серверной части, то тут мы имеем небольшую админку написанную на PHP для внесения уведомлений в БД и шлюз для приема запросов от пользователей и соответственно выдачу уведомлений из БД. Этот же шлюз используется для сбора статистики и пишет всё в ту же БД.







Вот пример того как сервер отдает JSON для уведомлений (установлен лимит в 3 сообщения).

//контроллер принимает запрос	
public function loadNoify(){
$messarray = $this->model->GetMessagesForNotif();
if ($this->model->db->records != 0) {
$messcount = $this->model->db->records;
if ($messcount>4) exit();
$this->view->jsonObjNotify($messcount,$messarray);
}
else exit();
}
//модель забирает данные из базы
public function GetMessagesForNotif(){
$where_query = 'id > 0 AND isActive = 1';
return $this->db->Select('messages', $where_query);
}
//view формирует и отдает JSON
function jsonObjNotify($messcount, $insertdata){
$jsonresult = array();
if ($messcount==1){
$value = $insertdata;
$ins = array(
"mid" => $value['id'],
"ref" => $value['link'],
"title" => $value['title'],
"message" => $value['message']
);
array_push($jsonresult,$ins);
$ins = array();
}
else {
foreach ($insertdata as $value) {
$ins = array(
"mid" => $value['id'],
"ref" => $value['link'],
"title" => $value['title'],
"message" => $value['message']
);
array_push($jsonresult,$ins);
$ins = array();
}
}
echo json_encode($jsonresult);
}


А вот так формирую JSON для отображения в расширении. Тут по отдаем HTML снипет:

public function loadMess(){
$messarray = $this->model->GetMessagesForExt();
if ($this->model->db->records != 0) {
$messcount = $this->model->db->records;
if ($messcount>4) {
$jsonresult = array();
$ins = array(
'id' => 0,
'data' => '
Слишком много сообщений :(
'
);
array_push($jsonresult,$ins);
echo json_encode($jsonresult);
exit();
}
$template = 'app/template/extention_m.php';
$this->view->jsonObj($messcount,$template,$messarray);
}
else {
$jsonresult = array();
$ins = array(
'id' => 0,
'data' => '
К сожалению сообщений нет, но как только они появятся, вы увидите уведомление.
'
);
array_push($jsonresult,$ins);
echo json_encode($jsonresult);
}
}


Формируем HTML снипет extention_m.php:

$data.='
'.$value["title"].'
'.$value["message"].' подробнее...
';


Осталось рассказать про статистику. Я много делать не стал. В свою базу добавляю по минимуму. С остальным хорошо справляется Google Analytics. Просто при публикации расширения я указал Google Analytics ID и могу получать всю информацию о просмотрах и переходах по ссылкам, которые содержатся в уведомлениях.



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



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



Здесь само расширение для браузера Chrome , о котором говрится в статье.



P.S. Важно! Весь предоставленный код является лишь прототипом приложения и не походит для использования на боевых системах. Код не оптимизирован и не проверялся на безопасность. Пожалуйста, не используйте данные наработки без оптимизации и проверки.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/306146/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] API для управления плеерами на сайтах

Среда, 20 Июля 2016 г. 19:47 (ссылка)

Предыстория



В 2012-2013 годах я нашёл одно онлайн-радио, которое «зацепило» меня большим выбором хорошей музыки и тем, что там (почти) не было рекламы.



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



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



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

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



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



Готовое расширение



Сегодня моё расширение позволяет контролировать воспроизведение уже на нескольких сайтах (SoundCloud, Digitally Imported, ВКонтакте, Одноклассники, Онлайн-радио 101.ru, VGM Radio), а также некоторые другие действия (добавлять текущий трек в плейлист, отмечать трек как понравившийся, изменять громкость звука). Делается это как с помощью быстрых клавиш, так и при помощи кнопок в оповещении (кнопки не отображаются в Opera).



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



Также ведётся список десяти последних треков с возможностью поиска оных ВКонтакте, в Google и Amazon.



API



У расширения имеется API для добавления поддержки других сайтов.



Каждый сайт (плеер) добавляется как модуль. Имеются встроенные и внешние модули.



Встроенные модули


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



Внешние модули


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



Для чего нужны внешние модули



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

    Такие модули могут получать это разрешение сразу при установке либо в любое другое время (на усмотрение разработчика).

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



Где найти API и как с ним работать


API доступен по лицензии MIT на GitHub (см. Ссылки ниже).



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



Примеры использования API


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



Встроенный или внешний модуль


Встроенный или внешний модуль? «… вот в чём вопрос», — как говорил классик.



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




  • встроенный модуль:

    — ваш модуль поддерживает плеер только на одном или небольшом количестве доменов (например, модуль для youtube.com, модуль для play.google.com/music/listen);

    — вас устроит упоминание о вашем вкладе на странице readme моего расширения и в социальных сетях;

  • внешний модуль:

    — вы хотите опубликовать свой модуль под своим именем;

    — ваш модуль требует доступ к содержанию страниц на большом количестве или всех доменах.



Ссылки





Обратная связь



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


Original source: habrahabr.ru.

https://habrahabr.ru/post/306072/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Кэш, хэш и няш-меш

Вторник, 19 Июля 2016 г. 20:25 (ссылка)

Предыстория



Июльский субботний вечер подходил к концу. Нарубив дров на шашлык, я повесил USB-модем на багету, скомандовал sudo wvdial, развернул браузер и обновил вкладку с открытым гитхабом. Вернее, попытался обновить. Скорость не радовала, и в итоге страница-то обновилась, но явно не хватало какого-то из стилевых файлов; и дело было не в блокировке, поскольку аналогичные проблемы я наблюдал и с другими сайтами, и зачастую они решались просто многократным обновлением страницы. Во всём был виноват перегруз 3G-сети.



Стоп! А как же кэш?



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



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



Суть предложения



Добавить ко всем тэгам для подключения подчинённой статики (стилей, скриптов, изображений) атрибут checksum, который бы хранил хэш (например, SHA-1, как в git) требуемого файла:





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



Обратная совместимость предлагаемого решения очевидна.



Какие проблемы это решает?



Пресловутая угадайка: актуален ли файл в кэше?




  • Больше не нужно отправлять запрос и сличать полученные ETags.

  • Даже если файл в кэше вроде как устарел, но хэш совпадает — его можно смело использовать.

  • Чистка кэша как средство решения проблем частично теряет актуальность.



Дилемма: jQuery со своего домена или с CDN?



Владельцам малых сайтов часто приходится выбирать: либо подключать jQuery и/или подобные ей библиотеки с CDN (гугловского, например), или со своего домена.

В первом случае уменьшается время загрузки сайта (в том числе первичной, т.е. при первом заходе посетителя на сайт) за счёт того, что файл с серверов Гугла с большой долей вероятности уже есть в кэше браузера. Но, например, разработчики WordPress придерживаются второго варианта, ставя во главу угла автономность. И в условиях, когда CDN падают, блокируются и т.д., их можно понять.

Теперь от такой проблемы можно будет избавиться навсегда: не всё ли равно, откуда получен файл, если его содержимое — это ровно то, что нужно html-странице, и она это удостоверяет? Можно смело указывать свой домен, и если библиотека есть в кэше (неважно, загруженная с этого сайта, другого "малого" сайта или из какого-нибудь CDN) — она подхватится.



Смешанный HTTPS/HTTP-контент



Одна из причин запрета загрузки HTTP-ресурсов на HTTPS-страницах — возможность подмены HTTP-контента. Теперь это больше не преграда: браузер может получить требуемый контент и сверить его хэш с хэшем, переданным по HTTP. Отмена запрета на смешанный контент (при наличии и совпадении хэша) позволит ускорить распространение HTTPS.



Косвенное определение истории по времени загрузки статики



Известно, что владелец некоторого сайта evilsite.org может (с некоторой долей вероятности) определить, был ли посетитель на другом сайте goodsite.org, запросив, например, изображение goodsite.org/favicon.ico. Если время загрузки иконки ничтожно мало — то она в кэше, следовательно, посетитель был на сайте goodsite.org. Теперь эта атака усложнится: околонулевое время отклика будет лишь обозначать, что посетитель был на сайте с таким же фавиконом. Это, конечно, не решает проблему целиком, но всё же несколько усложняет жизнь определяющему.



На что это не влияет?




  • На html-страницы

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

  • На изображения, стили и скрипты, которые не предполагаются неизменными, например, когда подключается самая новая версия некоторой библиотеки с CDN этой библиотеки.



Идеология



Как обычно (математик я, что уж тут поделать) сформулируем аксиомы, которые вкладываются в предложение:




  1. Все передаваемые файлы делятся на главные (в основном html-страницы) и подчинённые (скрипты, изображения, стили и т.д.).

    В идеологии, заложенной в стандарты HTTP-кэширования, все файлы равноправны. Это, конечно, толерантно, но не отвечает современным реалиям.

  2. Неважно, откуда получен подчинённый файл. Важно, что его содержимое удовлетворяет нужды главного.

    В существующей идеологии даже сама аббревиатура URI — Uniform Resource Identifier — предполагает, что идентификатором ресурса является его адрес в сети. Но, увы, для подчинённых файлов это несколько не соответствует действительности.



Перспективы



Обещанный няш-меш



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



Доверенные устройства



Например, в офисе работают программисты, ЭВМ которых объединены в локальную сеть. Программист Вася приходит рано утром, открывает гитхаб и получает в кэш стили от нового дизайна, который выкатили ночью (у нас — ночь, там — день). Когда в офис приходит программист Петя и тоже загружает html-код гитхабовской странички, его ЭВМ спрашивает у всех ЭВМ в сети: "А нет ли у вас файла с таким-то хэшем?" "Лови!" — отвечает Васина ЭВМ, экономя тем самым трафик.

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



Анонимный разделяемый кэш



Аня едет в трамвае с работы и читает новости… например, на Яндекс-Новостях. Встретив очередной тэг , Анин телефон со случайного MAC-адреса спрашивает всех, кого видит: "Ребят, а ни у кого нет файла с таким-то хэшем?". Если ответ получен в разумное время — профит, Аня сэкономила недешёвый мобильный трафик. Важно почаще менять MAC-адрес на случайный да не "орать", когда в поле видимости слишком мало узлов и спрашивающего можно идентифицировать визуально.

Разумность времени ответа определяется исходя из стоимости трафика.



Дальнейший переход к няш-мешу



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

Более того, альбом фотографий тоже легко превращается в блоб: список хэшей изображений + список хэшей блобов-фотографий (первое нужно, чтобы при добавлении лайка/комментария показывать фотографии сразу, а метаинформацию — по мере её получения).

Останется только реализовать электронную подпись и поля вида "замещает блоб такой-то" — и готова няш-меш-социалочка.



Компактизация хэша



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



Поведение при несовпадении



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



Файлы-альтернативы



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



Превентивное кэширование



Многие устройства работают в двух режимах: когда интернет условно-безлимитен (например, мобильный телефон в вай-фай сети) и когда интернет ограничен (лимит по трафику или узкий канал). Браузер или расширение к нему может, пользуясь безлимитным подключением, заранее скачивать популярные библиотеки (наподобие jQuery и плагинов к ней), также по мере необходимости их обновлять. Это ли не мечта многих, чтобы jQuery была включена в браузер?



Заключение



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

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



P.S.

Мне было бы очень приятно услышать мнение Mithgol, Shpankov и BarakAdama.



P.P.S.

Хабр всезнающий, в какое спортлото отправлять рацпредложение?


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/305898/

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

API Яндекс.Панорам: как сделать свою виртуальную прогулку или просто довести человека от метро

Вторник, 19 Июля 2016 г. 16:14 (ссылка)

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



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





Движок



Сервис панорам запустился на Яндекс.Картах в далеком сентябре 2009 года. Поначалу это были лишь несколько панорам достопримечательностей и работали они, как вы, наверное, догадываетесь, на Flash. С тех пор много воды утекло, панорам стало несколько миллионов, начали быстро расти мобильные платформы, а Flash туда так и не пробрался. Поэтому примерно в 2013 году мы решили, что нам нужна новая технология. И основой для этой технологии стал HTML5.



API, с которого мы начинали, — это Canvas2D. Сейчас это может показаться странным, но в 2013 году этот выбор был вполне разумен. WebGL был стандартизован всего двумя годами раньше, толком еще не добрался до мобильных (в iOS, например, WebGL работал только в уже почти почившем в бозе iAd), да и на десктопах работал не очень стабильно. Читатель может мне возразить, что надо было все делать на CSS 3D, как это было тогда популярно. Но с помощью CSS 3D можно нарисовать только кубическую панораму, в то время как все панорамы Яндекса сферические (хранящиеся в равнопромежуточной проекции).



Это же было и самой главной технической трудностью в разработке. Дело в том, что корректно и точно спроецировать сферическую панораму на экран непросто из-за нелинейности этого преобразования. Наивная реализация такой проекции требовала бы целого вороха тригонометрических вычислений на каждый пиксель экрана — ведь нужно найти соответствующую ему точку в панорамном изображении и определить его цвет. Кроме того, Canvas 2D не предоставляет эффективного способа манипулировать каждым пикселем изображения по отдельности.



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



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



Алгоритм рендеринга на Canvas2D



Написав все это и запустив, я увидел нечто, хорошо описываемое словом «слайдшоу». Фреймрейт оказался совершенно неприемлемым. Попрофилировав, я нашел, что больше всего времени отъедают функции save() и restore() Canvas 2D контекста. Откуда они взялись? Из особенности работы с обрезкой в Canvas2D. К сожалению, чтобы иметь возможность сбросить текущую обрезку и выставить новую, необходимо сохранить перед выставлением состояние контекста (это как раз save()), а после всего необходимого рисования восстановить сохраненное состояние (а это уже restore()). А так как эти операции работают со всем состоянием

контекста, они недешевы. Кроме того, обрезку-то мы делаем каждый раз совершенно одинаковую (после инициализации разбиение сферы на треугольники и их наложение на панорамное изображение не меняются). Есть смысл это закешировать!



Сказано — сделано. После генерации триангулированной сферы мы «вырезаем» каждый треугольник из панорамного изображения и сохраняем его в отдельном кеше-канвасе. Остальная часть такого кэша при этом остается прозрачной. После такой оптимизации удалось получать 30—60 кадров в секунду даже на мобильных устройствах. Из этого опыта можно извлечь следующий урок: при разработке рендеринга на Canvas 2D все, что можно, кэшируйте и пререндерите. А если что-то вдруг нельзя — делайте так, чтоб было можно, и тоже пререндерите.



Нарезка кэшей треугольников



У любого кеширования (как и у многих вещей в этой жизни) есть и обратная сторона: неизбежно растет потребление памяти. Именно это и произошло с рендерингом панорамы. Возросшие аппетиты породили множество проблем. Самыми заметными можно назвать падения браузеров даже на декстопных платформах, а также довольно медленный старт. В конце концов, устав бороться с этими проблемами, мы отказались от репроецирования панорамного изображения на Canvas 2D и пошли другим путем. Но он уже совершенно не интересный :)



Однако еще до того мы начали смотреть с сторону WebGL. К этому нас подтолкнули разные причины, главной из которых, пожалуй, была iOS 8, в который WebGL заработал в Safari.



Главной проблемой при разработке WebGL-версии рендеринга был размер панорам. Панорамное изображение целиком не лезло ни в одну текстуру. Эту проблему мы снова решали, руководствуясь принципом «руководствуйся старыми как мир принципами», и разделили панорамную сферу на сектора. Каждому сектору соответствует своя текстура. При этом для экономии памяти и ресурсов GPU невидимые сектора полностью удаляются. Когда они должны на экране появиться вновь, данные для них снова перезагружаются (обычно из кеша браузера).



Встраивание панорам



Встраивание панорам с помощью API Карт начинается с подключения нужных модулей. Это можно сделать двумя способами: либо указав их в параметре load при подключении API, либо с помощью модульной системы (в скором времени мы добавим модули панорам в загружаемый по умолчанию набор модулей).







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



if (!ymaps.panorama.isSupported()) {
// Показываем пользователю сообщение, скрываем функциональность
// панорам, etc.
} else {
// Работаем с панорамами.
}


Чтобы открыть панораму, нам сначала надо получить ее описание с сервера. Это делается с помощью функции ymaps.panorama.locate:



ymaps.panorama.locate(
[0, 0] // координаты панорамы
).done(function (panoramas) {
// ...
});


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



Еще можно запрашивать воздушные панорамы:



ymaps.panorama.locate(
[0, 0], // координаты панорамы
{ layer: 'yandex#airPanorama' }
).done(function (panoramas) {
// ...
});


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



var player = new ymaps.panorama.Player(
'player', // ID элемента, в котором будет открыт плеер
panorama // Панорама, которую мы хотим отобразить в плеере
);


И мы увидим на странице:



Плеер панорам



Самый быстрый и простой способ открыть панораму — это функция ymaps.panorama.createPlayer:



ymaps.panorama.createPlayer(
'player', // ID DOM-элемента, в котором будет открыт плеер
[0, 0] // Координаты панорамы, которую мы хотим открыть
).done(function (player) {
// ...
});


При этом можно указать одну или несколько опций третьим параметром:



ymaps.panorama.createPlayer(
'player',
[0, 0],
{
// Слой, в котором искать панораму
layer: 'yandex#airPanorama',
// Направление взгляда
direction: [120, 10],
// Угловой размер видимой части панорамы
span: [90, 90],
// Набор контролов
controls: ['zoomControl', 'closeControl']
}
).done(function (player) {
// ...
});


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



Свои панорамы



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



Любая панорама начинается со съемки и подготовки панорамного изображения. Для съемки можно воспользоваться специальным устройством, обычным фотоаппаратом или даже смартфоном. Главное, чтобы результатом съемки и склейки была сферическая панорама в равнопромежуточной проекции. Например, стандартное приложение камеры на Android умеет снимать и склеивать панорамы в нужной проекции. Именно им мы и воспользовались для съемки панорам нашего уютного опенспейса.



После того как мы сняли панорамное изображение, необходимо его подготовить к отображению в плеере. Для этого нам нужно разрезать его на тайлы. Кроме того, мы можем подготовить изображения нескольких разных размеров для разных уровней мастабирования. Плеер будет выбирать самый подходящий уровень для текущего размера DOM-элемента, в котором открыт плеер, и угловых размеров видимой области панорамы. А если самый маленький уровень меньше 2048 пикселей по ширине изображения, он будет использован для создания «эффекта прогрессивного джипега». Плеер подгрузит тайлы этого уровня с самым высоким приоритетом и будет показывать их там, где нет тайлов лучшего качества (например, если они еще не загружены или были вытеснены из памяти и пока не перезагрузились).



Для нарезки изображений на тайлы можно воспользоваться любым ПО (при наличии определенной усидчивости — хоть Paint). Размеры тайлов должны быть степенями двойки (те из вас, кто работал с WebGL, думаю, догадываются, откуда растут ноги у этого ограничения). Я воспользовался ImageMagick:



# Сначала немного растянем исходное изображение так, чтоб оно разбивалось
# на целое число тайлов по горизонтали (по вертикали это, кстати, не обязательно).
$ convert src.jpg -resize 7168x3584 hq.jpg

# Разрежем изображение на тайлы размером 512 на 512 пискелей.
$ convert hq.jpg -crop 512x512 \
-set filename:tile "%[fx:page.x/512]-%[fx:page.y/512]" \
"hq/%[filename:tile].jpg"

# Подготовим уровень масштабирования низкого качества для «эффекта
# прогрессивного джипега». Он будет состоять из одного тайла, поэтому его
# не надо будет разрезать.
$ convert hq.jpg -resize 512x256 lq/0-0.jpg


Давайте наконец напишем уже какой-то код для нашей панорамы. API — это система связанных между собой интерфейсов. Эти интерфейсы описывают объект панорамы и все связанные с ним.



Интерфейсы



Давайте теперь разберем эту картинку по сущностям.



Объект панорамы должен реализовывать интерфейс IPanorama. Чтобы написать свой класс панорамы было проще, сделан абстрактный класс ymaps.panorama.Base. Он предоставляет разумные реализации по умолчанию для некоторых методов IPanorama, а также метод validate, который проверяет, удовлетворяет ли панорама ограничениям, накладываемым плеером (например, является ли указанный размер тайлов степенью двойки). Давайте им и воспользуемся.



function Panorama () {
Panorama.superclass.constructor.call(this);
// ...
// И проверим, что мы все делаем правильно.
this.validate();
}

ymaps.util.defineClass(Panorama, ymaps.panorama.Base, {
// Методы, возвращающие данные панорамы.
});


Начнем мы с того, что опишем плееру геометрию панорамы. Для этого нужно реализовать метод getAngularBBox, возвращающий, судя по документации, массив из четырёх чисел. Каков смысл этих чисел? Чтобы ответить на этот вопрос, вспомним про то, что панорама у нам сферическая, то есть наложенная на сферу. Чтобы описать положение панорамного изображения на сфере, необходимо выбрать некоторые «опорные» точки. Обычно для прямоугольника (а панорамное изображение, не будучи на сфере, как раз им и является, как и любое изображение в компьютере) выбирают координаты двух его противоположных углов. В нашем случае этот подход продолжает работать и на сфере, ведь вертикальные стороны изображения становятся при наложении меридианами, а горизонтальные – параллелями. Это значит, что каждая сторона прямоугольника имеет собственную угловую координату, общую для всех точек этой стороны. Именно эти координаты сторон и составляют массив, возвращаемый методом getAngularBBox, определяя своего рода сферический прямоугольник, ограничивающий панораму (отсюда и названия метода).



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

https://habrahabr.ru/post/305846/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] ES6 по-человечески

Вторник, 19 Июля 2016 г. 08:24 (ссылка)

От переводчика:

Предлагаю вашему вниманию перевод краткого (действительно краткого) руководства по ES6. В нём можно ознакомиться с основными понятиями стандарта.

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

Чтобы лучше разобраться в некоторых концепциях (для выполнения качественного перевода) использовалось описание стандарта на сайте MDN, руководство "You Don't Know JS: ES6 & Beyond" и учебник Ильи Кантора.



Перевод выложил на Гитхаб: https://github.com/etnolover/ES6-for-humans-translation. В случае нахождения ошибок пишите, исправлю.

Ссылка на оригинальный текст: https://github.com/metagrover/ES6-for-humans





Содержание









1. let, const и блочная область видимости



Ключевое слово let позволяет объявлять переменные с ограниченной областью видимости — только для блока {...}, в котором происходит объявление. Это называется блочной областью видимости. Вместо ключевого слова var, которое обеспечивает область видимости внутри функции, стандарт ES6 рекомендует использовать let.



var a = 2;
{
let a = 3;
console.log(a); // 3
}
console.log(a); // 2


Другой формой объявления переменной с блочной областью видимости является ключевое слово const. Оно предназначено для объявления переменных (констант), значения которых доступны только для чтения. Это означает не то, что значение константы неизменно, а то, что идентификатор переменной не может быть переприсвоен.

Вот простой пример:



{
const ARR = [5, 6];
ARR.push(7);
console.log(ARR); // [5,6,7]
ARR = 10; // TypeError
ARR[0] = 3; // значение можно менять
console.log(ARR); // [3,6,7]
}


О чём стоит помнить:




  • Когда дело касается поднятия переменных (hoisting) let и const, их поведение отличается от традиционного поведения var и function. И let и const не существуют до своего объявления (от переводчика: для подробностей автор оригинального руководства отсылает к статье Temporal Dead Zone)

  • Областью видимости let и const является ближайший блок.

  • При использовании const рекомендуется использовать ПРОПИСНЫЕ_БУКВЫ.

  • В const одновременно с объявлением переменной должно быть присвоено значение.





2. Стрелочные функции



Стрелочные функции представляют собой сокращённую запись функций в ES6. Стрелочная функция состоит из списка параметров ( ... ), за которым следует знак => и тело функции.



// Классическое функциональное выражение
let addition = function(a, b) {
return a + b;
};

// Стрелочная функция
let addition = (a, b) => a + b;


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



А вот пример с использованием блока из фигурных скобок:



let arr = ['apple', 'banana', 'orange'];

let breakfast = arr.map(fruit => {
return fruit + 's';
});

console.log(breakfast); // ['apples', 'bananas', 'oranges']


Это ещё не всё!...



Стрелочные функции не просто делают код короче. Они тесно связаны с ключевым словом this и привязкой контекста.



Поведение стрелочных функций с ключевым словом this отличается от поведения обычных функций с this. Каждая функция в JavaScript определяет свой собственный контекст this, но внутри стрелочных функций значение this то же самое, что и снаружи (стрелочные функции не имеют своего this). Посмотрим на следующий код:



function Person() {
// Конструктор Person() определяет `this` как экземпляр самого себя.
this.age = 0;

setInterval(function growUp() {
// Без использования `use strict`, функция growUp() определяет `this`
// как глобальный объект, который отличается от `this`,
// определённого конструктором Person().
this.age++;
}, 1000);
}
var p = new Person();


В ECMAScript 3/5 это поведение стало возможным изменить, присвоив значение this другой переменной.



function Person() {
var self = this;
self.age = 0;

setInterval(function growUp() {
// Коллбэк относится к переменной `self`,
// значением которой является ожидаемый объект.
self.age++;
}, 1000);
}


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



function Person() {
this.age = 0;

setInterval(() => {
this.age++; // `this` относится к объекту person
}, 1000);
}

var p = new Person();


Узнать больше о 'Лексическом this' в стрелочных функциях на сайте MDN





3. Параметры по умолчанию



ES6 позволяет установить параметры по умолчанию при объявлении функции. Вот простой пример:



let getFinalPrice = (price, tax = 0.7) => price + price * tax;
getFinalPrice(500); // 850, так как значение tax не задано

getFinalPrice(500, 0.2); // 600, значение tax по-умолчанию заменяется на 0.2




4. Spread / Rest оператор



... оператор назвают как spread или rest, в зависимости от того, как и где он используется.



При использовании в любом итерируемом объекте (iterable), данный оператор "разбивает" ("spread") его на индивидуальные элементы:



function foo(x, y, z) {
console.log(x, y, z);
}

let arr = [1, 2, 3];
foo(...arr); // 1 2 3


Другим распространённым использованием оператора ... является объединение набора значений в один массив. В данном случае оператор работает как "rest" (от переводчика: не нашёл подходящего перевода на русский язык, из примера ниже всё станет ясно)



function foo(...args) {
console.log(args);
}
foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]




5. Расширение возможностей литералов объекта



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



function getCar(make, model, value) {
return {
// с синтаксисом короткой записи можно
// пропускать значение свойства, если оно
// совпадает с именем переменной, значение
// которой мы хотим использовать
make, // аналогично make: make
model, // аналогично model: model
value, // аналогично value: value

// вычисляемые свойства теперь работают в
// литералах объекта
['make' + make]: true,

// Короткая запись метода объекта пропускает
// ключевое слово `function` и двоеточие. Вместо
// "depreciate: function() {}" можно написать:
depreciate() {
this.value -= 2500;
}
};
}

let car = getCar('Kia', 'Sorento', 40000);
console.log(car);
// {
// make: 'Kia',
// model:'Sorento',
// value: 40000,
// makeKia: true,
// depreciate: function()
// }




6. Восьмеричный и двоичный литералы



В ES6 появилась новая поддержка для восьмеричных и двоичных литералов.

Добавление к началу числа 0o или 0O преобразует его в восьмеричную систему счисления (аналогично, 0b или 0B преобразует в двоичную систему счисления). Посмотрим на следующий код:



let oValue = 0o10;
console.log(oValue); // 8

let bValue = 0b10;
console.log(bValue); // 2




7. Деструктуризация массивов и объектов



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



function foo() {
return [1, 2, 3];
}
let arr = foo(); // [1,2,3]

let [a, b, c] = foo();
console.log(a, b, c); // 1 2 3

function bar() {
return {
x: 4,
y: 5,
z: 6
};
}
let { x: a, y: b, z: c } = bar();
console.log(a, b, c); // 4 5 6




8. Ключевое слово super для объектов



ES6 позволяет использовать метод super в (безклассовых) объектах с прототипами. Вот простой пример:



var parent = {
foo() {
console.log("Привет от Родителя!");
}
}

var child = {
foo() {
super.foo();
console.log("Привет от Ребёнка!");
}
}

Object.setPrototypeOf(child, parent);
child.foo(); // Привет от Родителя!
// Привет от Ребёнка!




9. Строковые шаблоны и разделители



ES6 предоставяляет более простой способ вставки значения переменной или результата выражения (т.н. "интерполяцию"), которые рассчитываются автоматически.




  • \${… }`` используется для вычисления значения переменной/выражения.

  • ``` Обратные кавычки используются как разделитель для таких случаев.



let user = 'Кевин';
console.log(`Привет, ${user}!`); // Привет, Кевин!




10. for...of против for...in




  • for...of используется для перебора в цикле итерируемых объектов, например, массивов.



let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname of nicknames) {
console.log(nickname);
}
// di
// boo
// punkeye



  • for...in используется для перебора в цикле всех доступных для перебора (enumerable) свойств объекта.



let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname in nicknames) {
console.log(nickname);
}
// 0
// 1
// 2
// size




11. Map и WeakMap



ES6 представляет новые структуры данных — Map и WeakMap. На самом деле, мы используем "Map" в JavaScript всё время. Каждый объект можно представить как частный случай Map.



Классический объект состоит из ключей (всегда в строковом виде) и значений, тогда как в Map для ключа и значения можно использовать любое значение (и объекты, и примитивы). Посмотрим на этот код:



var myMap = new Map();

var keyString = "строка",
keyObj = {},
keyFunc = function() {};

// устанавливаем значения
myMap.set(keyString, "значение, связанное со 'строка'");
myMap.set(keyObj, "значение, связанное с keyObj");
myMap.set(keyFunc, "значение, связанное с keyFunc");

myMap.size; // 3

// получаем значения
myMap.get(keyString); // "значение, связанное со 'строка'"
myMap.get(keyObj); // "значение, связанное с keyObj"
myMap.get(keyFunc); // "значение, связанное с keyFunc"


WeakMap



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



Стоить отметить, что в WeakMap, в отличие от Map, каждый ключ должен быть объектом.



Для WeakMap есть только четыре метода: delete(ключ), has(ключ), get(ключ) и set(ключ, значение).



let w = new WeakMap();
w.set('a', 'b');
// Uncaught TypeError: Invalid value used as weak map key

var o1 = {},
o2 = function(){},
o3 = window;

w.set(o1, 37);
w.set(o2, "azerty");
w.set(o3, undefined);

w.get(o3); // undefined, потому что это заданное значение

w.has(o1); // true
w.delete(o1);
w.has(o1); // false




12. Set и WeakSet



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



let mySet = new Set([1, 1, 2, 2, 3, 3]);
mySet.size; // 3
mySet.has(1); // true
mySet.add('строки');
mySet.add({ a: 1, b:2 });


Вы можете перебирать Set в цикле с помощью forEach или for...of. Перебор происходит в том же порядке, что и вставка.



mySet.forEach((item) => {
console.log(item);
// 1
// 2
// 3
// 'строки'
// Object { a: 1, b: 2 }
});

for (let value of mySet) {
console.log(value);
// 1
// 2
// 3
// 'строки'
// Object { a: 1, b: 2 }
}


У Set также есть методы delete() и clear().



WeakSet



Аналогично WeakMap, объект WeakSet позволяет хранить объекты с неустойчивыми связями в коллекции. Объект в WeakSet уникален.



var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo); // false, foo не был добавлен к коллекции

ws.delete(window); // удаляет window из коллекции
ws.has(window); // false, window был удалён




13. Классы в ES6



В ES6 представили новый синтаксис для классов. Здесь стоит отметить, что класс ES6 не представляет собой новую объектно-ориентированную модель наследования. Это просто синтаксический сахар для существующего в JavaScript прототипного наследования.



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



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



class Task {
constructor() {
console.log("Создан экземпляр task!");
}

showId() {
console.log(23);
}

static loadAll() {
console.log("Загружаем все tasks...");
}
}

console.log(typeof Task); // function
let task = new Task(); // "Создан экземпляр task!"
task.showId(); // 23
Task.loadAll(); // "Загружаем все tasks..."


extends и super в классах



Посмотрим на следующий код:



class Car {
constructor() {
console.log("Создаём новый автомобиль");
}
}

class Porsche extends Car {
constructor() {
super();
console.log("Создаём Porsche");
}
}

let c = new Porsche();
// Создаём новый автомобиль
// Создаём Porsche


В ES6 ключевое слово extends позволяет классу-потомку наследовать от родительского класса. Важно отметить, что конструктор класса-потомка должен вызывать super().



Также, в классе-потомке можно вызвать метод родительского класса с помощью super.имяМетодаРодителя().



Узнать больше о классах на сайте MDN



О чём стоит помнить:




  • Объявления классов не поднимаются наверх (not hoisted). Сначала нужно объявить класс и только после этого использовать его, иначе будет ошибка ReferenceError.

  • Нет необходимости использовать ключевое слово function во время задания функций внутри определения класса.





14. Тип данных Symbol



Symbol это уникальный и неизменяемый тип данных, представленный в ES6. Целью Symbol является создание уникального идентификатора, к которому нельзя получить доступ.



Вот как можно создать Symbol:



var sym = Symbol("опциональное описание");
console.log(typeof sym); // symbol


Заметим, что использовать new вместе с Symbol(…) нельзя.



Если Symbol используется как свойство/ключ объекта, он сохраняется таким специальным образом, что свойство не будет показано при нормальном перечислении свойств объекта.



var o = {
val: 10,
[Symbol("случайный")]: "Я - символ",
};

console.log(Object.getOwnPropertyNames(o)); // val


Чтобы извлечь символьные свойства объекта, нужно использовать Object.getOwnPropertySymbols(o)





15. Итераторы



Итератор обращается к элементам коллекции по одному, в то же время сохраняя память о своей текущей позиции в этой коллекции. У итератора есть метод next(), который возвращает следующий элемент в последовательности. Этот метод возвращает объект с двумя свойствами: done (окончен ли перебор) и value (значение).



В ES6 есть метод Symbol.iterator, который определяет итератор для объекта по-умолчанию. При каждой необходимости перебора в цикле для объекта (например, в начале цикла for..of), его метод итератора вызывается без аргументов, и возвращённый итератор используется для того, чтобы получить значения для перебора.



Посмотрим на массив, который является перебираемым (iterable), и на итератор, который есть у массива для обработки его значений:



var arr = [11,12,13];
var itr = arr[Symbol.iterator]();

itr.next(); // { value: 11, done: false }
itr.next(); // { value: 12, done: false }
itr.next(); // { value: 13, done: false }

itr.next(); // { value: undefined, done: true }


Заметим, что можно написать собственный итератор через определение obj[Symbol.iterator]() с описанием объекта.



Подробнее про итераторы:

На сайте MDN





16. Генераторы



Функции-генераторы представляют собой новую особенность ES6, которая позволяет функции создавать много значений в течение некоторого периода времени, возвращая объект (называемый генератором), который может быть итерирован для выброса значений из функции по одному за раз.



Функция-генератор возвращает итерируемый объект при своём вызове.

Функция-генератор записывается с помощью знака * после ключевого слова function, а в теле функции должно присутствовать ключевое слово yield.



function *infiniteNumbers() {
var n = 1;
while (true) {
yield n++;
}
}

var numbers = infiniteNumbers(); // возвращает перебираемый объект

numbers.next(); // { value: 1, done: false }
numbers.next(); // { value: 2, done: false }
numbers.next(); // { value: 3, done: false }


Каждый раз при вызове yield возвращённое значение становится следующим значением в последовательности.



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





17. Промисы



В ES6 появилась встроенная поддержка промисов. Промис это объект, который ждёт выполнения асинхронной операции, после которого (т.е. после выполнения) промис принимает одно из двух состояний: fulfilled (resolved, успешное выполнение) или rejected (выполнено с ошибкой).



Стандартным способом создания промиса является конструктор new Promise(), который принимает обработчик с двумя функциями как параметрами. Первый обработчик (обычно именуемый resolve) представляет собой функцию для вызова вместе с будущим значением, когда оно будет готово; второй обработчик (обычно именуемый reject) является функцией, которая вызывается для отказа от выполнения промиса, если он не может определить будущее значение.



var p = new Promise(function(resolve, reject) {  
if (/* условие */) {
resolve(/* значение */); // fulfilled successfully (успешный результат)
} else {
reject(/* reason */); // rejected (ошибка)
}
});


Каждый промис обладает методом then, в котором есть два коллбэка. Первый коллбэк вызывается, если промис успешно выполнен (resolved), тогда как второй коллбэк вызывается, если промис выполнен с ошибкой (rejected).



p.then((val) => console.log("Промис успешно выполнен", val),
(err) => console.log("Промис выполнен с ошибкой", err));


При возвращении значения от then коллбэки передадут значение следующему коллбэку then.



var hello = new Promise(function(resolve, reject) {  
resolve("Привет");
});

hello.then((str) => `${str} Мир`)
.then((str) => `${str}!`)
.then((str) => console.log(str)) // Привет Мир!


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

Эта простая техника помогает избежать ада с коллбэками ("callback hell").



var p = new Promise(function(resolve, reject) {  
resolve(1);
});

var eventuallyAdd1 = (val) => {
return new Promise(function(resolve, reject){
resolve(val + 1);
});
}

p.then(eventuallyAdd1)
.then(eventuallyAdd1)
.then((val) => console.log(val)) // 3

Original source: habrahabr.ru.

https://habrahabr.ru/post/305900/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Модальные окна на Angular, Angular 2 и ReactJS

Понедельник, 18 Июля 2016 г. 21:51 (ссылка)

В этой статье мы рассмотрим, как создавать всплывающие и перекрывающие элементы на React, Angular 1.5 и Angular 2. Реализуем создание и показ модального окна на каждом из фреймворков. Весь код написан на typescript. Исходный код примеров доступен на github.



Введение



Что такое "всплывающие и перекрывающие" элементы? Это DOM элементы, которые показываются поверх основного содержимого документа.



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



Как правило, для таких элементов применяют абсолютное позиционирование в координатах окна (для модальных окон) при помощи position: fixed, или абсолютное позиционирование в координатах документа — для меню, выпадающих списков, которые должны располагаться возле своих "родительских" элементов, — при помощи position: absolute.



Простое размещение всплывающих элементов возле "родителей" и скрытие/отображение их не работают полностью. Причина — это родительские контейнеры с overflow, отличным от visiblefixed). Все, что выступает за границы контейнера, будет обрезано. Также такие элементы могут перекрываться элементами ниже по дереву, и z-index не всегда тут поможет, так как работает только в пределах одного контекста наложения.



По-хорошему, эту проблему элегантно мог бы решить Shadow DOM (и то, не факт), но пока он не готов. Могло бы помочь CSS свойство, запрещающее обрезку и перекрытие, либо позиционирование относительно документа (а не родителя), но его нет. Поэтому используем костыль — DOM элементы для всплывающих компонентов помещаем в body, ну или, на худой конец, поближе к нему, в специальный контейнер, у родителей которого заведомо нет "обрезающих" стилей.



Замечание: мы не будем рассматривать позиционирование элементов с разными координатами и родителями — это отдельная сложная тема, полная костылями, javascript-ом и обработкой неожиданных событий браузера.



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



Использование position: fixed, по-видимому, позволяет избежать обрезки родительским контейнером, но вынуждает обрабатывать скроллинг документа и контейнера (проще всего тупо закрывать выпадающий список). Использование position: absolute и помещение элемента в body обрабатывает прокрутку документа правильно, но требует пересчета позиции при прокрутке контейнера.



Все способы требуют обработки события resize. В общем, нет тут хорошего решения.



Примеры



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



Все примеры написаны на typescript. Для компиляции и бандлинга используется webpack. Чтобы запустить примеры, у вас должен быть установлен NodeJS.



Для запуска перейдите в папку с соответствующим примером и выполните в командной строке NodeJS один раз npm run prepare, чтобы установить глобальные и локальные пакеты. Потом выполните npm run server. После этого откройте в браузере адрес http://localhost:8080



Если это делать лень, можно просто открыть в браузере index.html из папки соответствующего примера.



Angular 1.5



Компоненты



В версии 1.5 Angular приобрел синтаксический сахар в виде метода component у модуля, который позволяет объявлять компоненты. Компоненты — это на самом деле директивы, но код их объявления ориентирован на создание кирпичиков предметной области приложения, тогда как директивы больше ориентированы (идеологически, технически все идентично) на низкоуровневую и императивную работу с DOM. Это нововведение простое, но прикольное, и позволяет объявлять компоненты способом, схожим с Angular 2. Никаких новых фич этот способ не привносит, но может кардинально повлиять на структуру приложения, особенно, если раньше вы пользовались

...
.

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



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



Два способа



Наверное, существует больше способов поместить компонент в произвольное место DOM. Я покажу два из них, один при помощи сервиса $compile, второй — при помощи директивы с transclude.



Первый способ императивный, и подходит больше для ad-hoc показа модальных окон, например, для вывода сообщений или запроса у пользователя каких-то параметров. Также этот способ можно применять, если тип компонента неизвестен, или разметка динамическая.



Второй способ — декларативный, он позволяет встроить всплывающий элемент в шаблон компонента,

но при показе помещать его в body. Подходит для компонентов типа дроп-дауна, позволяя реактивно управлять видимостью.



Способ 1: $compile



Сервис $compile позволяет преобразовать строку с Angular разметкой в DOM элемент и связать его со $scope.

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



Все довольно просто.



Вот документация сервиса. По ссылке полное руководство по API директив, интересующая нас часть в самом конце — использование $compile как функции.



Получаем доступ к $compile



// popup.service.ts
import * as angular from "angular";

export class PopupService {
static $inject = ["$compile"];

constructor(private $compile: angular.ICompileService) {}
}


Объявление static $inject=["$compile"] эквивалентно следующему Javascript коду:



function PopupService($compile) {
this.$compile = $compile;
}

PopupService.$inject = ["$compile"];


$compile работает в две фазы. На первой он преобразует строку в функцию-фабрику. На второй нужно вызвать полученную фабрику и передать ей $scope. Фабрика вернет DOM элементы, связанные с этим $scope.



$compile принимает три аргумента, нас интересует только первый. Первый аргумент — это строка, содержащая HTML шаблон, который будет потом преобразован в работающий фрагмент Angular приложения. В шаблоне можно использовать любые зарегистрированные компоненты из вашего модуля и его зависимостей, а также любые валидные конструкции Angular — директивы, интерполяцию строк и т.п.



Результатом компиляции будет фабрика — функция, которая позволит связать строковый шаблон с любым $scope. Таким образом, задавая шаблон, можно использовать любые поля и методы вашего скоупа. Например, вот как выглядит код открытия всплывающего окна:



/// test-popup.component.ts

export class TestPopupComponentController {
static $inject = ["$scope", PopupService.Name];

text: string = "Open popup with this text";

constructor(
private $scope: angular.IScope,
private popupService: PopupService) {
}

openPopup() {
const template = ` `
this.popupService.open(template)(this.$scope);
}
}


Обратите внимание на несколько вещей.



Во-первых, шаблон содержит компонент .

Во-вторых, шаблон содержит обращение к полю text контроллера: text="$c.text".

$c — это алиас контроллера, заданный при объявлении компонента.



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



Вот как выглядит PopupService.open:



// popup.service.ts

open(popupContentTemplate: string): ($scope: angular.IScope) => () => void {
const content = `

${popupContentTemplate}

`;

return ($scope: angular.IScope) => {
const element = this.$compile(content)($scope);
const popupElement = document.body.appendChild(element[0]);

return () => {
body.removeChild(popupElement);
};
};
}


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



Хотя наш метод PopupService.open тоже возвращает фабрику для связи с $scope, он делает дополнительную работу. Во-первых, когда фабрика вызывается, он не только создает элемент, но и добавляет его в body. Во-вторых, он создает функцию, которая позволит "закрыть" поп-ап окно, удалив его из документа. PopupService.open возвращает эту функцию для закрытия окна.



Что ж, вариант не так плох. Хотя само отображение окна императивное, тем не менее, содержимое окна все еще реактивно, и может быть декларативно связано с родительским $scope. Хотя для отображения контента приходится использовать строки, но если сам контент окна сделать в виде компонента, то связывать нужно будет только input и output свойства, а не весь контент. Метод позволяет поместить поп-ап элемент в любое место документа, даже если оно вне ng-app.



Способ 2: Директива с transclude



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



















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



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



transclude



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






...



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



Как использовать transclude? Напрямую использовать контент (например, через $element.children) нельзя — он не связан с правильным scope, и не скомпилирован (не заменены директивы и т.д.). Для использования transclude нужно получить доступ к т.н. transclude function. Это фабрика, которая умеет создавать скомпилированные копии (клоны) содержимого. Эти клоны будут скомпилированы и связаны с правильным scope, и вообще, очень похожи на результат работы $compile. Transclude function, однако, не возвращает значение, как обычная фабрика, а передает его в коллбек-функцию.



Можно создавать сколько угодно клонов, переопределять им scope, добавлять в любое место документа, и так далее. Здорово.



Для директив, которые сами управляют содержимым (вызывают transclude function), необходимо реализовывать lifecycle методы для очистки содержимого. Эти методы реализуются в контроллере директивы. Удалять добавленное содержимое нужно в $onDestroy.



Осталось последнее — как получить доступ к transclude function. Она передается в нескольких местах, но мы ее заинжектим в контроллер. Для того, чтобы она передалась, в конфигурации директивы должно быть установлено transclude: true.



Итак, полный код:



import * as angular from "angular";

export class PopupDirectiveController {
private content: Node;

constructor(private transclude: angular.ITranscludeFunction) {
}

$onInit() {
this.transclude(clone => {
const popup = document.createElement("div");
popup.className = "popup-overlay";
for(let i = 0; i < clone.length; i++) {
popup.appendChild(clone[i]);
}

this.content = document.body.appendChild(popup);
});
}

$onDestroy() {
if (this.content) {
document.body.removeChild(this.content)
this.content = null;
}
}
}

export const name = "popup";

export const configuration: angular.IDirective = {
controller: ["$transclude", PopupDirectiveController],
replace: true,
restrict: "E",
transclude: true
};


Неплохо, всего 36 строк.



Преимущества:




  • Полностью реактивное отображение и скрытие, реактивное содержимое

  • Удобно разносит "виртуальное" расположение в дереве компонентов и "физическое" расположение в DOM дереве.

  • Декларативно привязано к текущему scope.



Недостатки:




  • В этом варианте реализации нужно использовать ng-if для управления отображением.



Angular 2



Новая версия Angular, отличающаяся от первого настолько, что, фактически, это новый продукт.

Мои впечатления от него двоякие.



С одной стороны, код компонентов несомненно чище и яснее. При написании бизнес-компонентов разделение кода и представления отличное, change tracking работает прекрасно, прощайте $watch и $apply, прекрасные средства для описания контракта компонента.



С другой стороны, не оставляет ощущение монструозности. 5 min quickstart выглядит издевательством. Множество дополнительных библиотек, многие из которых обязательны к использованию (как rxjs). То, что я успеваю увидеть надпись Loading... при открытии документа с файловой системы, вселяет сомнения в его скорости. Размер бандла в 4.3MB против 1.3MB у Angular 1 и 700KB React (правда, это без оптимизаций, дефолтный бандлинг webpack-а). (Напоминаю, что webpack собирает (бандлит) весь код приложения и его зависимостей (из npm) в один большой javascript файл).



Минифицированный размер: Angular 1 — 156KB, Angular 2 — около 630KB, в зависимости от варианта, React — 150KB.



Angular 2 на момент написания еще RC. Код практически готов, багов вроде бы нет, основые вещи сделаны (ну кроме разве что переделки форм). Однако документация неполная, многие вещи приходится искать в комментариях к github issue

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



Disclaimer



Тратить полтора часа на шаги, описанные в упомянутом 5 min quickstart, не хотелось, поэтому проект сконфигурирован не совсем, кхм, традиционно для Angular 2. SystemJS не используется, вместо этого бандлится webpack-ом. Причем Angular 2 не указывается как externals, а берется из npm пакета как есть. В результате получается гигантский бандл в 4.5MB весом. Поэтому не используйте эту конфигурацию в продакшене, если, конечно, не хотите, чтобы пользователи возненавидели ваш индикатор загрузки. Вторая странность, которая не знаю, чем вызвана, это отличающиеся названия модулей. Во всех примерах (в том числе в официальной документации) импорт Angular выглядит как import { } from "@angular/core". В то же время, у меня так не заработало, а работает import {} from "angular2/core".



Динамическая загрузка



К чести Angular 2, код динамической загрузки вызывает трудности только при поиске. Для динамической загрузки используется класс ComponentResolver в сочетании с ViewContainerRef.




// Асинхронно создает новый компонент по типу.
// Тип - это класс компонента (его функция-конструктор)
loadComponentDynamically(componentType: Type, container: ViewContainerRef) {
this.componentResolve
.resolveComponent(componentType)
.then(factory => container.createComponent(factory))
.then(componentRef => {
// Получаем доступ к экземпляру класса компонента
componentRef.instance;
// Получаем доступ ElementRef контейнера, в который помещен компонент
componentRef.location;
// Получаем доступ к DOM элементу.
componentRef.location.nativeElement;
// Удаляем компонент
componentRef.destroy();
});
}


ComponentResolver легко получить через dependency injection. ViewContainerRef, по-видимому, не может быть создан для произвольного DOM элемента, и может быть только получен для существующего Angular компонента. Это значит, что поместить динамически созданный элемент в произвольное место DOM дерева невозможно, по крайней мере, в релиз-кандидате.



Поэтому, наш механизм для показа поп-апов будет составным.



Во-первых, у нас будет компонент, в который будут динамически добавляться поп-ап элементы. Его нужно будет разместить где-нибудь в дереве компонентов, желательно поближе к корневому элементу. Кроме того, никакой из его родительских контейнеров не должен содержать стилей, обрезающих содержимое. В коде это overlay-host.component.ts.



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



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



Хост-компонент



Я приведу класс целиком, он не очень большой, и потом пройдусь по тонким местам:



import { Component, ComponentRef, ComponentResolver, OnInit, Type, ViewChild, ViewContainerRef } from "angular2/core";

import { OverlayComponent } from "./overlay.component";
import { IOverlayHost, OverlayService } from "./overlay.service";

@Component({
selector: "overlay-host",
template: ""
})
export class OverlayHostComponent implements IOverlayHost, OnInit {

@ViewChild("container", { read: ViewContainerRef }) container: ViewContainerRef;

constructor(
private overlayService: OverlayService,
private componentResolver: ComponentResolver) {
}

openComponentInPopup(componentType: Type): Promise {
return this.componentResolver
.resolveComponent(OverlayComponent)
.then(factory => this.container.createComponent(factory))
.then((overlayRef: ComponentRef) => {

return overlayRef.instance
.addComponent(componentType)
.then(result => {

result.onDestroy(() => {
overlayRef.destroy();
});

return result;
});
});
}

ngOnInit(): void {
this.overlayService.registerHost(this);
}
}


Что делает этот код? Он динамически создает компонент, используя его тип (тип компонента — это его функция-конструктор). Предварительно создается компонент-обертка (OverlayComponent), наш запрошенный компонент добавляется уже к нему. Также мы подписываемся на событие destroy, чтобы уничтожить обертку при уничтожении компонента.



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

Декоратор @ViewChild() позволяет получать ViewContainerRef по имени template variable template: .

#container — это и есть template variable, переменная шаблона. К ней можно обращаться по имени, но только в самом шаблоне. Чтобы получить доступ к ней из класса компонента, используется упомянутый декоратор.



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



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



Код OverlayHostComponent, который я привел выше, — это самое интересное в нашем примере. OverlayComponent содержит похожий код для динамического добавления содержимого, OverlayService перенаправляет вызовы открытия поп-апа к хост-компоненту. Я не привожу листинги по причине тривиальности, если интересно, посмотрите в исходниках.



Посмотрим теперь, как этим пользоваться:



import { Component, Input } from "angular2/core";

import { OverlayService } from "./overlay";
import { PopupContent } from "./popup-content.component";

@Component({
selector: "test-popup",
template: `









`
})
export class TestPopup {
text: string = "Show me in popup";

constructor(private overlayService: OverlayService) {
}

openPopup() {
this.overlayService
.openComponentInPopup(PopupContent)
.then(c => {
const popup: PopupContent = c.instance;
popup.text = this.text;
popup.close.subscribe(n => {
c.destroy();
});
});
}
}


OverlayService указан в providers Root компонента, в нашем компоненте его регистрировать не нужно.



После создания экземпляра компонента можно получить к нему доступ через ComponentRef.instance.



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



Вывод



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



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



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



ReactJS



В Реакте стандартный способ отображения компонента в DOM дерево — это метод render, который возвращает виртуальный узел виртуального DOM. Однако, это совсем не значит, что этот способ единственный. Для вставки компонента в произвольное место из метода render возвращается null, и перехватываются lifecycle-методы componentDidMount, componentWillUnmount, componentDidUpdate. В componentDidMount и componentDidUpdate, используя ReactDOM.render, можно отрендерить содержимое в любое место. В componentWillUnmount содержимое, соответственно, уничтожается.



Собственно, код:



import * as React from "react";
import * as ReactDOM from "react-dom";

export class Popup extends React.Component<{}, {}> {
popup: HTMLElement;

constructor() {
super();
}

render() {
return ();
}

componentDidMount() {
this.renderPopup();
}

componentDidUpdate() {
this.renderPopup();
}

componentWillUnmount() {
ReactDOM.unmountComponentAtNode(this.popup);
document.body.removeChild(this.popup);
}

renderPopup() {
if (!this.popup) {
this.popup = document.createElement("div");
document.body.appendChild(this.popup);
}

ReactDOM.render(


{ this.props.children }

,
this.popup);
}
}


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



Вообще Реакт после Angular производит очень приятное впечатление. Отсутствуют костыли отслеживания изменений, которые вроде бы не нужно использовать, но всегда приходится. Простой доступ к DOM элементам, если он нужен. Простой доступ к содержимому реакт-элемента через children, причем это не строка и не HTMLElement, а структура, содержащая в себе полноценные реакт-элементы (для работы с ними нужно использовать React.Children).



Ладно, теперь посмотрим, как это использовать. Привожу, для краткости, только метод render:



render() {
return (



this.setText(e.target.value) }
type="text" />





this.state.isPopupVisible } >



{ this.state.text}







)
}


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



В остальном все просто: если компонент есть в виртуальном дереве — поп-ап окошко показывается, если нет — то прячется. При этом физически в DOM дереве оно находится в body.



Как видим, очень похоже на второй способ с Angular 1.5, с директивой.



Итоги



В принципе, поп-ап в Реакте можно сделать и императивным способом, похожим на способ Angular с $compile. Это может упростить некоторые сценарии и не создавать флаг в состоянии приложения для показа каждого алерта. Принцип тот же (используя ReactDOM.render), но только не в методах жизненного цикла компонента, а в методе openPopup. Это, конечно же, нарушает реактивность, но сделает код понятнее, увеличив связность.



Недостатки приведенного способа — не будет работать в серверном рендеринге.



Заключение



Подходы, изначально заложенные в Реакте — однонаправленные потоки данных, компоненты, четкий input/output контракт компонента — нашли свое отражение и в Angular: by design в Angular 2, и в обновлениях Angular 1.5. Это изменение без сомнения пошло на пользу первому Angular.



Что касается показа всплывающих элементов — это пример костылей, которые возникли из-за несовершенства CSS, но повлияли на всю экосистему веб-разработки. Это яркий пример текущей абстракции, а также баланса между "чистой архитектурой" и "реальной жизнью" веб разработки. Как видим, разработчики Angular 2 либо не задумывались об этом сценарии, либо реализовали его, но никому не сказали. В то же время, первый Angular и React достаточно гибкие (а разработчики Реакта видимо еще и продуманные), чтобы можно было реализовать рендеринг элемента в отличное от его расположения в дереве компонентов.


Original source: habrahabr.ru.

https://habrahabr.ru/post/305892/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<javascript - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda