Инструменты для отладки jQuery events

Written by elwood

Часто бывает нужно определить, какие обработчики навешаны на DOM-элементы. К сожалению, из коробки ни FireBug, ни Chrome Developer Tools не дают возможности удобно ответить на этот вопрос, так как показывают лишь прямые обработчики, которые были повешены присваиванием element.onclick = handler. Если же для этого использовался jQuery, то узнать о наличии обработчиков можно только просканировав дерево элементов до document, просматривая то, что хранится в $._data(element, ‘events’). Для этого я написал небольшой сниппет, который добавил в код сайта:

jquery-events

/**
 * Дампер jquery on- и live-обработчиков.
 * @author elwood
 * 16.10.13 15:43
 */
 
/**
 * Выводит дамп on- и live-обработчиков для указанного объекта и всех его родителей.
 * У тех обработчиков, у которых определён селектор, выводятся также дочерние узлы (относительно
 * элемента, к которому привязан обработчик), удовлетворяющие ему.
 * @param $object
 */
function dumpEvents($object) {
 
    var $current = $object;
    while ($current.length != 0) {
        console.info("processing %o", $current[0]);
        var eventsObject = $._data($current[0], 'events');
        for (var property in eventsObject) {
            console.info(property + ':');
            var handlers = eventsObject[property];
            for (var i = 0; i < handlers.length; i++) {
                if (typeof handlers[i].selector != undefined && handlers[i].selector != null) {
                    console.info("selector: " + handlers[i].selector + ", handler: %O", handlers[i].handler);
                    var $selected = $current.find(handlers[i].selector);
                    console.info("selected %d children:", $selected.length);
                    for (var j = 0; j < $selected.length; j++) {
                        console.info(">> %o", $selected[j]);
                    }
                } else {
                    console.info("handler: %O", handlers[i].handler);
                }
            }
        }
 
        $current = $current.parent();
    }
}
 
/**
 * Вариант использования с xpath (для удобства при работе в хроме).
 * @param xpath
 */
function dumpEventsX(xpath) {
    var $object = $(document).xpath(xpath);
    dumpEvents($object);
}
 
/**
 * Выводит live-обработчики, глобально привязанные к объекту document.
 */
function dumpDocumentEvents() {
    dumpEvents($(document));
}

Для работы dumpEventsX() необходимо наличие плагина jQuery.XPath. XPath я использовал для того, чтобы из панели Elements в хроме правой кнопкой можно было делать Copy XPath и потом в консоли вставлять этот xpath в качестве аргумента функции dumpEventsX().

Честно говоря, я глубоко не разбирался в том, как реализована подписка обработчиков с помощью jQuery, и есть ли другие варианты, которые работают иначе. Вроде бы, bind() и click() делают то же самое, только с конечным объектом (селектор у хендлера будет пустой, и на потомков хендлер уже не будет работать).

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

Для Сhrome

jQuery Debugger – в Elements появляются вкладки jQuery Data и jQuery Events для каждого DOM-элемента можно посмотреть, что к нему джикверивского привязано, очень удобное расширение

Вот как это выглядит:
jquery-debugger

Visual Event – очень красивый плагин, при нажатии на кнопку показывает все элементы с привязанными к ним событиями и кодом их обработчиков. Поддерживает события jQuery.
Event Spy – тоже полезная штука, но этот плагин, судя по всему, умеет работать только с непосредственно привязанными обработчиками. Код обработчиков тоже выводится, довольно удобно.

Для FireFox

FireQuery– аддон к FireBug, показывает данные, привязанные к DOM-элементам (правда, не очень удобная навигация) – полезный аддон
EventBug – тоже аддон к FireBug, добавляет вкладку Events к броузеру DOM-элементов
Visual Event – то же самое, что и в хроме, только для FireFox он выполнен в виде bookmarklet, то есть ссылки, которую нужно добавить в закладки, и нажимать когда работаешь со страницей. Наверное, и в Опере работает.

Расширяем терминологию

Written by elwood

На работе коллега Тимур Шакуров придумал шикарнейшее словечко ПРОКАСТ для обозначения всяких длинных полумагических команд наподобие следующей:

<@insomnia> Нужно выполнить всего три команды, чтобы поставить Gentoo
<@insomnia> cfdisk /dev/hda && mkfs.xfs /dev/hda1 && mount /dev/hda1 /mnt/gentoo/ && chroot /mnt/gentoo/ && env-update && . /etc/profile && emerge sync && cd /usr/portage && scripts/bootsrap.sh && emerge system && emerge vim && vi /etc/fstab && emerge gentoo-dev-sources && cd /usr/src/linux && make menuconfig && make install modules_install && emerge gnome mozilla-firefox openoffice && emerge grub && cp /boot/grub/grub.conf.sample /boot/grub/grub.conf && vi /boot/grub/grub.conf && grub && init 6
<@insomnia> это первая

Для тех, кто не в курсе, что такое ПРОКАСТ:

Готовим maven правильно: deployment в Tomcat

Written by elwood

cargo-banner

Задача

Есть многомодульный проект, собираемый с помощью maven. Среди модулей имеются несколько WAR-приложений, которые должны деплоиться в сервлет-контейнер (рассмотрим Томкат, но всё применимо и к любому другому контейнеру или серверу приложений). Причём желательно иметь возможность выборочно собирать и деплоить только одно из этих приложений. Развёртываться всё это добро должно уметь как в локальный контейнер программиста, так и на удалённый тестовый сервер.

Решение

У нас есть один главный модуль (тот, у которого packaging = pom) и несколько модулей. В pom.xml главного модуля мы пропишем два набора профилей. Один набор профилей будет отвечать за то, собирать ли указанный модуль или нет. Второй набор профилей будет отвечать за настройки окружения. Таким образом, мы превращаем мейвеновские профили в аналог USE-флагов в Gentoo Linux. Также в pom.xml главного модуля мы сконфигурируем плагин cargo-maven2-plugin, указав ему skip = true. А в тех дочерних модулях, которые предполагаются к развёртыванию, мы этот флаг установим в false. Таким образом, мы сможем вызывать cargo:deploy для главного модуля, но срабатывать он будет только у тех дочерних, которые мы укажем. Иные варианты, к сожалению, работать не будут (например, если конфигурировать плагин для дочернего модуля и вызывать напрямую у него, то при наличии зависимостей maven не сможет собрать модуль). А два набора профилей дадут нам возможность выборочно собирать и развёртывать приложения туда, куда нам хочется.

Код

pom.xml главного модуля:

<modules>
  <!-- Модули, собираемые всегда -->
  <module>common-dependency</module>
</modules>
<packaging>pom</packaging>
 
<profiles>
  <!-- Профили, относящиеся к настройкам окружения - при сборке мы должны указать только 1 профиль -->
  <profile>
    <id>env-igor-dev</id>
    <properties>
      <tomcatHost>localhost</tomcatHost>
      <tomcatPort>8091</tomcatPort>
      <tomcatManagerUser>tomcat</tomcatManagerUser>
      <tomcatManagerPassword>1</tomcatManagerPassword>
    </properties>
  </profile>
  <profile>
    <id>env-test</id>
    <properties>
      <tomcatHost>test.com</tomcatHost>
      <tomcatPort>8080</tomcatPort>
      <tomcatManagerUser>tom</tomcatManagerUser>
      <tomcatManagerPassword>fsKf2_3</tomcatManagerPassword>
    </properties>
  </profile>
 
  <!-- Профили, относящиеся к тому, что мы хотим собрать. При сборке можно указать 
       любую комбинацию этих профилей. -->
  <profile>
    <id>build-war1</id>
    <modules>
      <module>webapp-1</module>
    </modules>
  </profile>
  <profile>
    <id>build-war2</id>
    <modules>
      <module>webapp-2</module>
    </modules>
  </profile>
</profiles>
 
<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.cargo</groupId>
      <artifactId>cargo-maven2-plugin</artifactId>
      <configuration>
        <configuration>
          <properties>
            <cargo.hostname>${tomcatHost}</cargo.hostname>
            <cargo.protocol>http</cargo.protocol>
            <cargo.servlet.port>${tomcatPort}</cargo.servlet.port>
            <cargo.remote.username>${tomcatManagerUser}</cargo.remote.username>
            <cargo.remote.password>${tomcatManagerPassword}</cargo.remote.password>
          </properties>
          <type>runtime</type>
        </configuration>
        <container>
          <containerId>tomcat6x</containerId>
          <type>remote</type>
        </container>
        <!-- skip in parent pom and by default in all submodules -->
        <skip>true</skip>
      </configuration>
    </plugin>
  </plugins>
</build>

pom.xml одного из приложений:

<build>
  <plugins>
    <plugin>
      <artifactId>maven-war-plugin</artifactId>
      <configuration>
        <warName>war1</warName>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.codehaus.cargo</groupId>
      <artifactId>cargo-maven2-plugin</artifactId>
      <configuration>
        <deployables>
          <deployable>
            <location>webapp-1/target/war1.war</location>
          </deployable>
        </deployables>
        <skip>false</skip>
      </configuration>
    </plugin>
  </plugins>
</build>

pom.xml второго приложения составляется аналогично

Результат

Приложение собирается и деплоится одной командой
mvn clean package cargo:redeploy -P env-igor-dev,build-war1,build-war2

Update

Если у вас в war-приложении есть META-INF/context.xml, а в нём задан context path (например <Context antiJARLocking="true" path="/">), то cargo проигнорирует свойство deployable -> properties -> context и загрузит варник в ROOT.war. Поэтому если вам нужно, чтобы варник деплоился туда куда надо, настройте maven resources plugin с filtering=true чтобы path устанавливать в нужное вам значение.