Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Vaadin: пригодные доработки и слежения

Anna | 2.06.2014 | нет комментариев
Vaadin — компонентный UI фреймворк для создания веб-приложений на Java. Мы используем Vaadin в составе своей платформы CUBA на протяжении 4 лет и за это время собрали огромный навык работы с ним.

Vaadin был выбран нами по нескольким причинам:

  • Серверная модель программирования, не требующая использования JavaScript/HTML в прикладном коде
  • Вероятность создавать интенсивный AJAX UI
  • Уйма компонентов и сторонних аддонов

Из недостатков стоит подметить:

  • Высокие требования к памяти сервера, от того что все элементы пользовательского интерфейса и их данные хранятся в HTTP сессии
  • Трудность растяжения компонентов Vaadin и написания аддонов

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

Пустое место в GridLayout

Одной из особенностей корпоративного приложения является требование к изменению экранов интерфейса в зависимости от прав пользователя и состояния данных. Зачастую компоненты на форме размещаются по сетке с поддержкой GridLayout, и тогда при скрытии строк либо столбцов в стандартном Vaadin остаются пустые места отступов для заметных компонентов. Это поведение дозволено изменить, что затребует создания своего преемника GridLayout. Назовём его SuperGridLayout.

Нам потребуются:

  1. SuperGridLayout — преемник серверной части GridLayout
  2. SuperGridLayoutConnector — коннектор для связи сервера с виджетом, преемник GridLayoutConnector
  3. SuperGridLayoutWidget — сам виджет, преемник VGridLayout

Пока ещё не все компоненты Vaadin отлично поддаются растяжению, следственно не изумляйтесь некоторым хакам для переопределения package local способов. Мы обязаны сделать наши компоненты в пакетеcom.vaadin.ui. У разработчиков аддонов это вообще достаточно распространённая практика, правда подвижки в сторону расширяемости есть.

Сам SuperGridLayout не содержит никакой логики:

public class SuperGridLayout extends GridLayout {
}

В SuperGridLayoutConnector указано, что мы будем применять виджет SuperGridLayoutWidget. Vaadin определяет это по типу возвращаемого значения способа getWidget().

@Connect(SuperGridLayout.class)
public class SuperGridLayoutConnector extends GridLayoutConnector {
    @Override
    public SuperGridLayoutWidget getWidget() {
        return (SuperGridLayoutWidget) super.getWidget();
    }
}

Ну и сам код виджета с исправлением для скрытия пропусков:

SuperGridLayoutWidget

public class SuperGridLayoutWidget extends VGridLayout {
    // ..
    @Override
    void layoutCellsHorizontally() {        
        // ...
        for (int i = 0; i < cells.length; i  ) {
            for (int j = 0; j < cells[i].length; j  ) {
            // ...
            // Fix for GridLayout leaves an empty space for invisible components #VAADIN-12655
            // hide zero width columns
            if (columnWidths[i] > 0) {
                x  = columnWidths[i]   horizontalSpacing;
            }
        }       
        // ...
    }

    @Override
    void layoutCellsVertically() {
        // ...
        for (int column = 0; column < cells.length; column  ) {
            // ...
            for (int row = 0; row < cells[column].length; row  ) {
                // ...                
                // Fix for GridLayout leaves an empty space for invisible components #VAADIN-12655
                // hide zero height rows
                if (rowHeights[row] > 0) {
                    y  = rowHeights[row]   verticalSpacing;
                }
            }
        }
        // ...
    }
}

Сейчас необходимо добавить в свой план сборку виджет сета с новым компонентом. Это детально описано в документации Vaadin.
Полный код дозволено посмотреть здесь: https://github.com/jreznot/vaadin-super-grid

Выделение по правому клику в дереве и таблице

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

Назовём наше дерево SuperTree и заведём соответственно SuperTreeSuperTreeWidget иSuperTreeConnectorSuperTree — примитивный преемник Tree. А в SuperTreeWidget всецело скопируем код из VTree, в SuperTreeConnector — код из TreeConnector. Дальше изменим код в SuperTreeConnector, Дабы он применял виджет SuperTreeWiget и аннотацию @Connect(SuperTree.class).

У нас получилась своя реализация клиентской части для серверного компонента Tree. В SuperTreeConnectorзаведём флаг contextMenuSelection и аксессоры для него. В способе updateFromUIDL при выставленном флаге будем сбрасывать для виджета флаг rendering = false и прерывать исполнение. Это нужно, Дабы наше контекстное меню не было свёрнуто. Дальше в SuperTreeWidget.TreeNode добавим в способshowContextMenu выделение узла, если он не выделен:

#showContextMenu

public void showContextMenu(Event event) {
    if (!readonly && !disabled) {
        // Select node by right click
        if (!isSelected()) {
            toggleSelection();
            getConnector().setContextMenuSelection(true);
        }

        if (actionKeys != null) {
            int left = event.getClientX();
            int top = event.getClientY();
            top  = Window.getScrollTop();
            left  = Window.getScrollLeft();
            client.getContextMenu().showAt(this, left, top);
        }
        event.stopPropagation();
        event.preventDefault();
    }
}

Сейчас если пользователь будет кликать по узлу правой кнопкой мыши, наш узел будет непременно выделен.
Полный код здесь: https://github.com/jreznot/vaadin-super-tree

Жгучие клавиши для полей ввода

Так повелось в API Vaadin, что жгучие клавиши привязываются к объектам PanelWindow либо UI. Это значит, что добавляя листнеры для жгучих клавиш, к примеру, для поля, вы добавляете их к ближайшему по иерархии контейнеру-хранителю. Такое поведение приводит к тому, что для идентичных клавиш в 2-х полях теснее необходимо писать хитроумный код, ну и написание своих компонентов с жгучими клавишами усложняется на порядок. Если же легко обернуть все дублирующиеся компоненты в панели, то мы усложним наш экран для браузера.

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

В SuperTextField определим свой ActionManager, ответственный за жгучие клавиши этого поля.

SuperTextField

public class SuperTextField extends TextField implements Action.Container {
    //..

    /**
     * Keeps track of the Actions added to this component, and manages the
     * painting and handling as well.
     */
    protected ActionManager shortcutsManager;

    @Override
    public void paintContent(PaintTarget target) throws PaintException {
        super.paintContent(target);
        if (shortcutsManager != null) {
            shortcutsManager.paintActions(null, target);
        }
    }

    @Override
    protected ActionManager getActionManager() {
        if (shortcutsManager == null) {
            shortcutsManager = new ConnectorActionManager(this);
        }
        return shortcutsManager;
    }

    @Override
    public void changeVariables(Object source, Map<String, Object> variables) {
        super.changeVariables(source, variables);
        if (shortcutsManager != null) {
            shortcutsManager.handleActions(variables, this);
        }
    }

    @Override
    public void addShortcutListener(ShortcutListener listener) {
getActionManager().addAction(listener);
    }

    @Override
    public void removeShortcutListener(ShortcutListener listener) {
        getActionManager().removeAction(listener);
    }

    @Override
    public void addActionHandler(Action.Handler actionHandler) {
        getActionManager().addActionHandler(actionHandler);
    }

    @Override
    public void removeActionHandler(Action.Handler actionHandler) {
        getActionManager().removeActionHandler(actionHandler);
    }
}

В SuperTextFieldConnector добавим загрузку жгучих клавиш из JSON и передачу их виджету.

SuperTextFieldConnector

@Connect(SuperTextField.class)
public class SuperTextFieldConnector extends TextFieldConnector {

    @Override
    public SuperTextFieldWidget getWidget() {
        return (SuperTextFieldWidget) super.getWidget();
    }

    @Override
    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
        super.updateFromUIDL(uidl, client);
        // We may have actions attached to this text field
        if (uidl.getChildCount() > 0) {
            final int cnt = uidl.getChildCount();
            for (int i = 0; i < cnt; i  ) {
                UIDL childUidl = uidl.getChildUIDL(i);
                if (childUidl.getTag().equals("actions")) {
                    if (getWidget().getShortcutActionHandler() == null) {
                        getWidget().setShortcutActionHandler(new ShortcutActionHandler(uidl.getId(), client));
                    }
                    getWidget().getShortcutActionHandler().updateActionMap(childUidl);
                }
            }
        }
    }
}

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

SuperTextFieldWidget

public class SuperTextFieldWidget extends VTextField implements ShortcutActionHandler.ShortcutActionHandlerOwner {

    protected ShortcutActionHandler shortcutHandler;

    public SuperTextFieldWidget() {
            // handle shortcuts
            DOM.sinkEvents(getElement(), Event.ONKEYDOWN);
    }

    @Override
    public void onBrowserEvent(Event event) {
        super.onBrowserEvent(event);

        final int type = DOM.eventGetType(event);
        if (type == Event.ONKEYDOWN && shortcutHandler != null) {
            shortcutHandler.handleKeyboardEvent(event);
        }
    }

    public void setShortcutActionHandler(ShortcutActionHandler handler) {
        this.shortcutHandler = handler;
    }

    @Override
    public ShortcutActionHandler getShortcutActionHandler() {
        return shortcutHandler;
    }

    //..
}

Сейчас мы можем сделать сколько желательно полей SuperTextField с одними и теми же сочетаниями клавиш.
Полный код здесь: https://github.com/jreznot/vaadin-super-textfield

Жанры “-focus” для TabSheet, Table, CheckBox, Tree, MenuBar

В Vaadin для некоторых компонентов не хватает жанров разных состояний. Испробуем добавить селектор “-focus” для деревьев с фокусом.

Схема действий простая: заводим компонент FocusTreeFocusTreeConnector и FocusTreeWidget.

Добавляем жанр “-focus” в виджете:

FocusTreeWidget

public class FocusTreeWidget extends VTree {
    @Override
    public void onFocus(FocusEvent event) {
        super.onFocus(event);
        addStyleDependentName("focus");
    }

    @Override
    public void onBlur(BlurEvent event) {
        super.onBlur(event);
        removeStyleDependentName("focus");
    }
}

Сейчас остаётся только завести надобные CSS жанры для компонента с селектором “v-tree-focus”.
Пример здесь: https://github.com/jreznot/vaadin-focus-selector

Вероятность отображать в ComboBox значение, которого нет в списке опций

В платформе CUBA стандартным является мягкое удаление объектов из БД. Удаленные объекты недостижимы для применения, впрочем обязаны отображаться в составе других объектов, их использующих. То есть, если удалить определенный объект Клиент, то открыв Заказ, сделанный этим клиентом, в поле выбора клиента мы обязаны увидеть имя удаленного Клиента, но в списке выбора он должен отсутствовать. Впрочем Vaadin не допускает проставлять в поле с выпадающим списком значение, которое отсутствует в опциях.

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

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

SuperBeanContainer

public class SuperBeanContainer<IDTYPE, BEANTYPE> extends BeanContainer<IDTYPE, BEANTYPE> {

    protected Object missingBoxValue;

    public SuperBeanContainer(Class<? super BEANTYPE> type) {
        super(type);
    }

    @Override
    public boolean containsId(Object itemId) {
        boolean containsFlag = super.containsId(itemId);
        if (!containsFlag) {
            missingBoxValue = itemId;
        }
        return true;
    }

    @Override
    public List getItemIds() {
        List<IDTYPE> itemIds = super.getItemIds();
        if (missingBoxValue != null && !itemIds.contains(missingBoxValue)) {
            List<IDTYPE> newItemIds = new ArrayList<>(itemIds);
            newItemIds.add((IDTYPE) missingBoxValue);
            for (IDTYPE itemId : itemIds) {
                newItemIds.add(itemId);
            }
            itemIds = newItemIds;
        }

        return itemIds;
    }

    @Override
    public BeanItem<BEANTYPE> getItem(Object itemId) {
        if (missingBoxValue == itemId) {
            return new BeanItem(itemId);
        }

        return super.getItem(itemId);
    }

    @Override
    public int size() {
        int size = super.size();
        if (missingBoxValue != null) {
            size  ;
        }
        return size;
    }
}

Пример здесь: https://github.com/jreznot/vaadin-super-combobox

О переходе на Vaadin 7

В Vaadin 7 изменилось многое, включая поддержку браузеров. Огромнее не поддерживается IE7, заявлена помощь IE8 . Но совместно с тем возникли крупные задачи с продуктивностью в IE 8. Радикальным образом изменился процесс рендеринга компонентов, сейчас он поэтапный и использует насыщенные расчёты на JavaScript. Это поведение никак невозможно изменить. Некоторые «трудные» экраны (таблица с 10ю колонками в 5 вложенных вертикальных боксах) в IE8 отрисовываются в 10-20 раз неторопливей, чем в Chrome. При переходе либо выборе Vaadin 7 учтите это.
Мы решили эту задачу откровенно — поддерживаем в платформе Vaadin и 6, и 7 версии, а в плане приложения дозволено предпочесть, какую версию применять.

dev.vaadin.com/ticket/12797 — Баг проверен, но активности по нему пока нет.

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

Аддоны для Vaadin, которые мы перевели на 7 версию (может быть будут кому-то пригодны):

Для прототипирования на Vaadin мы используем комфортную заготовку с Maven, Groovy и Jetty:https://github.com/Haulmont/vaadin-sandbox — mvn clean package jetty:run

Оговорки

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

Описанные в статье хаки мы не применяем в таком виде, от того что поддерживаем свою версию Vaadin и можем добавлять в неё нужные хуки и protected API. https://github.com/Haulmont/vaadin. Допустимо, для вас это тоже будет лучшим вариантом, нежели копировать целые классы фреймворка. Благо git разрешает комфортно сливать метаморфозы из Upstream.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB