Статьи

Пользовательские события и Ajax Friendly Page-готовые проверки

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

Например, проверка того, изменилась ли плата, — это не так просто, учитывая, что все Trello — это ajaxy, а URL-адрес изменяется с помощью необнаружимого pushstate. Мы также хотим, чтобы он поддерживал открытие нескольких вкладок Trello, поэтому проверка URL-адреса не поможет. Было бы также хорошо, если бы мы могли как-то убедиться, что медленные загрузки страницы не влияют на состояние «страница готова» — в исходном расширении, из-за «чрезмерной доступности» Trello, страница была «готова» до загрузки контента — даже область платы была загружена через ajax, и поэтому было нелегко прикрепить события к основным элементам DOM пользовательского интерфейса Trello.

Для этой цели и для того, чтобы в будущем облегчить разработку расширений для Trello, я решил создать библиотеку TrelloUI, которая позаботится об этой конкретной проблеме для меня. Библиотека TrelloUI будет расширена за счет других функций в будущем, но сейчас давайте создадим нашу «готовую проверку».

Что мы строим

Мы будем создавать вспомогательную библиотеку, которую мы можем вызывать из нашего расширения Chrome (или аддона Firefox, если вы так склонны), который позволит нам присоединить слушатель события к объекту document для события trelloui-boardready . Как только это событие будет запущено, мы будем знать, что доска загружена, и мы можем присоединить события к элементам пользовательского интерфейса доски. Наконец, мы улучшим его, добавив больше событий для других вариантов использования, чтобы мы могли транслировать любое событие, которое захотим в будущем.

Антенна мачта знак

Мы будем тестировать библиотеку на расширение Chrome, включая ее как скрипт содержимого. Вы можете протестировать его на новой версии ChromeSkel_a , скелетном расширении Chrome, которое можно использовать «из коробки», или на версии Trello Helper, которую мы создали в предыдущей серии .

Все, что вам нужно, — это редактор и Chrome с активированным режимом разработчика (перейдите в chrome: extensions и поставьте флажок в «режиме разработчика»).

Здание библиотеки

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

Сценарии содержания

Клиентская библиотека Trello требует jQuery, поэтому мы включим ее в наш проект. Загрузите последнюю копию (желательно версию 2+) и включите ее в качестве скрипта содержимого. Создайте файл с именем trelloui.js и другой файл с именем trelloui.js а затем main.js их. Ваш блок скриптов контента должен выглядеть так:

 "content_scripts": [ { "matches": ["https://trello.com/b/*"], "js": [ "lib/jquery-2.1.1.min.js", "lib/TrelloUI/trelloui.js", "scripts/main.js" ], "run_at": "document_idle" } 

Вы можете выбрать желаемую структуру папок — мне нравится помещать «библиотеки» в «lib», но это не так важно.

Бутстрапирование

В trelloui.js мы начинаем с создания нового «класса».

 var TrelloUI = function () {}; 

Это просто функция, которую мы будем расширять с помощью некоторых свойств метода.

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

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

 TrelloUI.prototype._checkState = function () { return $('#board').hasClass('trelloui-boardready'); }; 

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

 var TrelloUI = function () { setInterval(this._checkState.bind(this), 1000); }; TrelloUI.prototype._checkState = function () { if (!$('#board').hasClass('trelloui-boardready')) { this._registerEvents(); } }; 

Это использует «конструктор», где мы, при вызове new TrelloUI в нашем основном коде, получаем, что TrelloUI автоматически устанавливает интервал для проверки, содержит ли элемент body класс, который мы хотим каждую секунду. Если это не так, мы вызываем _registerEvents (функция, которую мы еще не написали), чтобы добавить класс и отправить событие, как только появится доска.

Обратите внимание, что мы используем this._checkState.bind(this) вместо this._checkState потому что он this._checkState во время setInterval .

Создание нового события

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

 var TrelloUI = function () { var eventDefaults = { bubbles: true, cancelable: true }; this.possibleEvents = { boardEvent: new Event('trelloui-boardready', eventDefaults) }; setInterval(this._checkState.bind(this), 1000); }; 

Мы использовали eventDefaults для установки значений по умолчанию для любых других дополнительных событий, которые мы можем захотеть определить позже, поэтому нам не нужно повторяться. Пузыри означают, что событие переходит в родительские элементы из элемента, на котором оно запущено. Cancellable означает, что он может быть остановлен с помощью event.stopPropagation , если пользователь того пожелает. Эти флаги сейчас для нас практически ничего не значат, но они являются хорошими значениями по умолчанию. Затем мы определяем внутреннее свойство возможное событие, которое содержит все возможные события, которые может отправлять наш маленький эксперимент.

Опции и Конструктор

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

 var TrelloUI = function (options) { this._defaultOptions = { dispatchBoardReady: false }; this.options = jQuery.extend({}, this._defaultOptions, options); var eventDefaults = { bubbles: true, cancelable: true }; this.possibleEvents = { boardEvent: new Event('trelloui-boardready', eventDefaults) }; setInterval(this._checkState.bind(this), 1000); }; 

Здесь мы хотим, чтобы TrelloUI отправлял событие, когда доска готова, но мы учитываем наше потенциальное будущее желание реализовать другие события. Но проверка всех событий по умолчанию будет довольно ресурсоемкой. (Ну, не совсем — на самом деле, все, кроме самых слабых компьютеров, преуспели бы в обработке их всех, даже если бы мы имели дело с сотнями, но когда я вижу веб-страницы и расширения, использующие 2 ГБ + ОЗУ только для простоя, я склонен уклоняться принимать ресурсы как должное.)

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

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

 var tui = new TrelloUI({ dispatchBoardReady: true }); 

Здесь мы говорим TrelloUI создать экземпляр и следить за возможностью, когда он может вызвать событие boardReady. Если мы не дадим ему эту опцию, то значение по умолчанию в конструкторе остановит его от попыток сохранения ресурсов.

Событие стрельбы

ракета

Наконец, давайте создадим эту функцию запуска событий.

 TrelloUI.prototype._registerEvents = function () { var current = this; if (this.options.dispatchBoardReady) { var boardInterval = setInterval(function () { var board = $('#board'); if (board && !$(board).hasClass(current.possibleEvents.boardEvent.type)) { document.dispatchEvent(current.possibleEvents.boardEvent); $(board).addClass(current.possibleEvents.boardEvent.type); clearInterval(boardInterval); } }, 100); } }; 

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

Наконец, давайте улучшим _checkState чтобы он игнорировал проверку, если для параметра установлено значение false:

 TrelloUI.prototype._checkState = function () { if (this.options.dispatchBoardReady) { if (!$('#board').hasClass(this.possibleEvents.boardEvent.type)) { this._registerEvents(); } } }; 

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

Если вы сейчас добавите следующее в ваш скрипт main.js , вы сможете загрузить его в Chrome и увидеть «Board is ready» в вашей консоли:

 var tui = new TrelloUI({ dispatchBoardReady: true } ); document.addEventListener('trelloui-boardready', function() { console.log("Board is ready!"); }); 

Но… этого все еще недостаточно для нашего расширения из предыдущей серии . Там мы взаимодействуем со списками. И списки загружаются после платы. Очевидно, нам нужно событие listsReady .

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

 var TrelloUI = function (options) { this._defaultOptions = { dispatchBoardReady: false, dispatchListsReady: false }; this.options = jQuery.extend({}, this._defaultOptions, options); var eventDefaults = { bubbles: true, cancelable: true }; this.possibleEvents = { boardEvent: new Event('trelloui-boardready', eventDefaults), listsEvent: new Event('trelloui-listsready', eventDefaults) }; setInterval(this._checkState.bind(this), 1000); }; 

Затем мы обновляем _registerEvents , добавляя следующий блок:

 if (this.options.dispatchListsReady) { var listsInterval = setInterval(function() { var lists = $('.list'); if (lists.length > 0 && !$(lists[0]).hasClass(current.possibleEvents.listsEvent.type)) { document.dispatchEvent(current.possibleEvents.listsEvent); $(lists[0]).addClass(current.possibleEvents.listsEvent.type); clearInterval(listsInterval); } }, 100); } 

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

Наконец, давайте снова _checkState , добавив новый блок:

 if (this.options.dispatchListsReady) { var lists = $('lists'); if (!lists.length || !$(lists[0]).hasClass(this.possibleEvents.listsEvent.type)) { this._registerEvents(); } } 

Реализация

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

 var tui = new TrelloUI({ dispatchBoardReady: true, dispatchListsReady: true } ); document.addEventListener('trelloui-boardready', function() { console.log("Board is ready!"); }); document.addEventListener('trelloui-listsready', function() { console.log("Lists are ready!"); }); 

Каждый раз, когда вы меняете доску, вы должны получать уведомления о том, что доска и списки готовы. Добавьте свою логику вместо операторов console.log и сделайте что-нибудь волшебное!

Вывод

В этом коротком руководстве мы создали простую библиотеку для взаимодействия с пользовательским интерфейсом Trello — помощник, который запускает различные «готовые» события, которые могут помочь нам определить, когда «ajaxy» части завершили загрузку, чтобы мы могли правильно взаимодействовать с ними.

Мы все еще можем многое сделать для улучшения этой «библиотеки» — например, _checkState удаления зависимости jQuery или извлечения кода, аналогичного в _checkState и _registerEvents во что-то, что может быть разделено между ними. Однако сейчас это совершенно нормально для наших нужд — дайте нам знать, когда пользовательский интерфейс Trello готов к настройке! Хотите помочь? Запросы на извлечение приветствуются на репозитории Github !