Monthly Archives: October 2013

Paginator для админок

Written by elwood

Простая листалка на JSP, подходит для админок. Для использования нужно заинклудить этот файл в основную JSP страницы, а также установить переменные url, total_records, current_page и page_size. url – базовый url для этой страницы (к нему через амперсанд будет дописываться параметр page). total_records – сколько всего записей имеется, current_page – индекс текущей страницы, начиная с нуля. page_size – размер страницы. Можно настроить, сколько страниц отображать слева и справа от текущей (если мы находимся глубоко в середине).

Выглядит примерно так:

pager

<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="utf-8" session="false" %>
 
<%--@elvariable id="url" type="java.lang.String"--%>
<%--@elvariable id="total_records" type="java.lang.Integer"--%>
<%--@elvariable id="current_page" type="java.lang.Integer"--%>
<%--@elvariable id="page_size" type="java.lang.Integer"--%>
 
<style type="text/css">
    div.pagination {
        margin-top: 10px;
        text-align: center;
    }
    .pagination a {
        color: #3b5998;
        text-decoration: none;
        border: 1px #c2d1df solid;
        padding: 2px 5px;
        margin-right: 5px;
    }
    .pagination a:hover {
        color: #3b5998;
        text-decoration: none;
        border: 1px #3b5998 solid;
    }
    a.current {
        background: #c2d6ed;
    }
</style>
 
<div class="pagination">
    <a href="${url}&page=${current_page-1>=0?current_page-1:0}" class="prev">&lt; Prev</a>
    <%
        Integer total_records = (Integer) request.getAttribute("total_records");
        Integer page_size = (Integer) request.getAttribute("page_size");
        Integer current_page = (Integer) request.getAttribute( "current_page" );
        int pages = total_records / page_size;
        int lastPage = pages * page_size < total_records ? pages : pages - 1;
        request.setAttribute("last_page", lastPage);
        // сколько ссылок отображается начиная с самой первой (не может быть установлено в 0)
        final int N_PAGES_FIRST = 1;
        // сколько ссылок отображается слева от текущей (может быть установлено в 0)
        final int N_PAGES_PREV = 1;
        // сколько ссылок отображается справа от текущей (может быть установлено в 0)
        final int N_PAGES_NEXT = 1;
        // сколько ссылок отображается в конце списка страниц (не может быть установлено в 0)
        final int N_PAGES_LAST = 1;
        if (N_PAGES_FIRST < 1 || N_PAGES_LAST < 1) throw new AssertionError(  );
        // показывать ли полностью все ссылки на страницы слева от текущей, или вставить многоточие
        boolean showAllPrev;
        // показывать ли полностью все ссылки на страницы справа от текущей, или вставить многоточие
        boolean showAllNext;
        showAllPrev = N_PAGES_FIRST >= (current_page - N_PAGES_PREV);
        showAllNext = current_page + N_PAGES_NEXT >= lastPage - N_PAGES_LAST;
        request.setAttribute( "N_PAGES_FIRST", N_PAGES_FIRST );
        request.setAttribute( "N_PAGES_PREV", N_PAGES_PREV );
        request.setAttribute( "N_PAGES_NEXT", N_PAGES_NEXT );
        request.setAttribute( "N_PAGES_LAST", N_PAGES_LAST );
        request.setAttribute( "showAllPrev", showAllPrev );
        request.setAttribute( "showAllNext", showAllNext );
    %>
    <%-- show left pages --%>
    <c:choose>
        <c:when test="${showAllPrev}">
            <c:if test="${current_page > 0}">
                <c:forEach begin="0" end="${current_page - 1}" var="p">
                    <a href="${url}&page=${p}">${p + 1}</a>
                </c:forEach>
            </c:if>
        </c:when>
        <c:otherwise>
            <c:forEach begin="0" end="${N_PAGES_FIRST - 1}" var="p">
                <a href="${url}&page=${p}">${p + 1}</a>
            </c:forEach>
            <span style="margin-right: 5px">...</span>
            <c:forEach begin="${current_page - N_PAGES_PREV}" end="${current_page - 1}" var="p">
                <a href="${url}&page=${p}">${p + 1}</a>
            </c:forEach>
        </c:otherwise>
    </c:choose>
    <%-- show current page --%>
    <a href="${url}&page=${current_page}" class="current">${current_page + 1}</a>
    <%-- show right pages --%>
    <c:choose>
        <c:when test="${showAllNext}">
            <c:forEach begin="${current_page + 1}" end="${last_page}" var="p">
                <a href="${url}&page=${p}">${p + 1}</a>
            </c:forEach>
        </c:when>
        <c:otherwise>
            <c:forEach begin="${current_page + 1}" end="${current_page + 1 + (N_PAGES_NEXT - 1)}" var="p">
                <a href="${url}&page=${p}">${p + 1}</a>
            </c:forEach>
            <span style="margin-right: 5px">...</span>
            <c:forEach begin="${last_page - (N_PAGES_LAST - 1)}" end="${last_page}" var="p">
                <a href="${url}&page=${p}">${p + 1}</a>
            </c:forEach>
        </c:otherwise>
    </c:choose>
    <a href="${url}&page=${current_page + 1 > last_page ? last_page : current_page + 1}" class="next">Next &gt;</a>
</div>

Инструменты для отладки 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> это первая

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