GWT CellTree с необязательным всплывающим меню, вызываемым по щелчку TreeNode - PullRequest
2 голосов
/ 13 января 2012

Я хотел бы создать GWT CellTree с дополнительным всплывающим меню, которое вызывается при щелчке TreeNode.

Итак, я создал CustomTreeModel. Вот оно:

public class CustomTreeModel implements TreeViewModel {

/**
 * Save visited URL.  We'll use it later to determine if tree node needs to be opened.
 * We decode the query string in URL so that token has a chance of matching (e.g., convert %20 to space).
 */
private final String url = URL.decodeQueryString(Window.Location.getHref());

private final NavNode navNode;
private final TokenService<MainEventBus> tokenService;

/**
 * A selection model shared across all nodes in the tree.
 */
private final SingleSelectionModel<NavNode> selectionModel = new SingleSelectionModel<NavNode>();

public CustomTreeModel(NavNode navNode, TokenService tokenService) {
    this.navNode = navNode;
    this.tokenService = tokenService;
}

@Override
public <T> NodeInfo<?> getNodeInfo(T value) {
    DefaultNodeInfo<NavNode> result = null;
    if (value == null) {
        // LEVEL 0.
        // We passed null as the root value. Return the immediate descendants.
        result = new DefaultNodeInfo<NavNode>(getDataProvider(navNode), getCell(), selectionModel, null);

    } else if (value instanceof NavNode) {
        // all other levels
        // We pass a node, return its immediate descendants.

        // select node if URL contains params in node's target or one of node's option's target
        NavNode currNode = (NavNode) value;
        if (isSelected(currNode)) {
            selectionModel.setSelected(currNode, true);
        }
        if (currNode.hasOptions()) { // add pop-up menu to this node if it has options
            result = new DefaultNodeInfo<NavNode>(getDataProvider(currNode), getCell(), selectionModel, new NodeSelectionEventManager(currNode), null);
        } else {
            result = new DefaultNodeInfo<NavNode>(getDataProvider(currNode), getCell(), selectionModel, null);
        }
    }
    return result;
}

@Override
public boolean isLeaf(Object value) {
    boolean result = true;
    if (value == null) {
        if (navNode.hasChildren()) {
            result = false;
        }
    } else if (value instanceof NavNode) {
        NavNode currentNode = (NavNode) value;
        if (currentNode.hasChildren()) {
            result = false;
        }
    }
    return result;
}

// Create a data provider that contains the immediate descendants.
private ListDataProvider<NavNode> getDataProvider(NavNode node) {
    return new ListDataProvider<NavNode>(node.getChildren());
}

// Create a cell to display a descendant.
private Cell<NavNode> getCell() {
    Cell<NavNode> cell = new AbstractCell<NavNode>() {
        @Override
        public void render(Context context, NavNode value, SafeHtmlBuilder sb) {
            if (value != null) {
                sb.appendEscaped(value.getName());
            }
        }
    };
    return cell;
}

private boolean isSelected(NavNode node) {
    boolean selected = false;
    if (node != null) {
        if (url.contains(tokenService.getToken(node))) {
            selected = true;
        } else {
            for (NavOption option: node.getOptions()) {
                if (url.contains(tokenService.getToken(option))) {
                    selected = true;
                    break;
                }
            }
        }
    }
    return selected;
}

class NavNodeSelectionHandler implements SelectionChangeEvent.Handler {

    private final VerticalPanel optionsContainer;
    private final DecoratedPopupPanel optionsPopup;

    public NavNodeSelectionHandler() {
        optionsPopup = new DecoratedPopupPanel(true);
        optionsContainer = new VerticalPanel();
        optionsContainer.setWidth("125px");

        // TODO provide a debug id... this will most likely necessitate generation of a unique key
        optionsPopup.setWidget(optionsContainer);
    }

    @Override
    public void onSelectionChange(SelectionChangeEvent event) {
        NavNode node = selectionModel.getSelectedObject();
        for (NavOption option: node.getOptions()) {
            optionsContainer.add(new Hyperlink(option.getName(), tokenService.getToken(option)));
        }
        // Reposition the popup relative to node
        UIObject source = (UIObject) event.getSource();
        int left = source.getAbsoluteLeft() + 25;
        int top = source.getAbsoluteTop();
        optionsPopup.setPopupPosition(left, top);

        // Show the popup
        optionsPopup.show();
    }
}


class NodeSelectionEventManager implements CellPreviewEvent.Handler<NavNode> {

    private final VerticalPanel optionsContainer;
    private final DecoratedPopupPanel optionsPopup;

    public NodeSelectionEventManager(NavNode node) {
        optionsPopup = new DecoratedPopupPanel(true);
        optionsContainer = new VerticalPanel();
        optionsContainer.setWidth("125px");
        for (NavOption option: node.getOptions()) {
            optionsContainer.add(new Hyperlink(option.getName(), tokenService.getToken(option)));
        }
        // TODO provide a debug id... this will most likely necessitate generation of a unique key
        optionsPopup.setWidget(optionsContainer);
    }

    @Override
    public void onCellPreview(CellPreviewEvent<NavNode> event) {
        // Reposition the popup relative to node
        UIObject source = (UIObject) event.getDisplay();
        int left = source.getAbsoluteLeft() + 25;
        int top = source.getAbsoluteTop();
        optionsPopup.setPopupPosition(left, top);

        // Show the popup
        optionsPopup.show();

    }

}

}

Я использую общий компонент (NavNode), чтобы помочь мне определить, когда у меня есть лист и когда у меня есть опция (NavOption) или опции, которые содержат цель, используемую для построения гиперссылки.

Я хочу, чтобы при нажатии на узел (TreeNode) в CellTree появлялось всплывающее меню (DecoratedPopupPanel), но только для тех узлов, у которых есть параметры.

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

Вот вариант, который добавляет SelectionChangeEvent.Handler к SingleSelectionModel

if (currNode.hasOptions()) { // add pop-up menu to this node if it has options
            selectionModel.addSelectionChangeHandler(new NavNodeSelectionHandler());
            result = new DefaultNodeInfo<NavNode>(getDataProvider(currNode), getCell(), selectionModel, null);
        }

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

CellTree, TreeViewModel, SelectionModel и друзья - одни из самых тупых API, с которыми мне приходилось сталкиваться.

Был бы очень признателен за помощь эксперта GWT!

1 Ответ

0 голосов
/ 14 января 2012

Мой умный коллега смог найти решение.

Вот что мы получили:

public class CustomTreeModel implements TreeViewModel {

/**
 * Save visited URL.  We'll use it later to determine if tree node needs to be opened.
 * We decode the query string in URL so that token has a chance of matching (e.g., convert %20 to space).
 */
private final String url = URL.decodeQueryString(Window.Location.getHref());

private final NavNode navNode;
private final TokenService<MainEventBus> tokenService;

/**
 * A selection model shared across all nodes in the tree.
 */
private final SingleSelectionModel<NavNode> selectionModel = new SingleSelectionModel<NavNode>();

public CustomTreeModel(NavNode navNode, TokenService tokenService) {
    this.navNode = navNode;
    this.tokenService = tokenService;
}

@Override
public <T> NodeInfo<?> getNodeInfo(T value) {
    DefaultNodeInfo<NavNode> result = null;
    if (value == null) {
        // LEVEL 0.
        // We passed null as the root value. Return the immediate descendants.
        result = new DefaultNodeInfo<NavNode>(getDataProvider(navNode), getCell(), selectionModel, null);

    } else if (value instanceof NavNode) {
        // all other levels
        // We pass a node, return its immediate descendants.

        // select node if URL contains params in node's target or one of node's option's target
        NavNode currNode = (NavNode) value;
        if (isSelected(currNode)) {
            selectionModel.setSelected(currNode, true);
        }
        result = new DefaultNodeInfo<NavNode>(getDataProvider(currNode), getCell(), selectionModel, null);
    }
    return result;
}

@Override
public boolean isLeaf(Object value) {
    boolean result = true;
    if (value == null) {
        if (navNode.hasChildren()) {
            result = false;
        }
    } else if (value instanceof NavNode) {
        NavNode currentNode = (NavNode) value;
        if (currentNode.hasChildren()) {
            result = false;
        }
    }
    return result;
}

// Create a data provider that contains the immediate descendants.
private ListDataProvider<NavNode> getDataProvider(NavNode node) {
    return new ListDataProvider<NavNode>(NavNodeUtil.getHeadedChildren(node.getChildren(), 1));
}

// Create a cell to display a descendant.
private Cell<NavNode> getCell() {
    return new TreeCell();
}

private boolean isSelected(NavNode node) {
    boolean selected = false;
    if (node != null) {
        if (url.contains(tokenService.getToken(node))) {
            selected = true;
        } else {
            for (NavOption option: node.getOptions()) {
                if (url.contains(tokenService.getToken(option))) {
                    selected = true;
                    break;
                }
            }
        }
    }
    return selected;
}

class TreeCell extends AbstractCell<NavNode> {

    public TreeCell() {
        super("click", "keydown");
    }

    @Override
    public void onBrowserEvent(Context context, Element parent, NavNode currNode,
            NativeEvent event, ValueUpdater<NavNode> valueUpdater) {
        // Check that the value is not null.
        if (currNode == null) {
            return;
        }

        if (currNode.hasOptions()) { // add pop-up menu to this node if it has options
            final DecoratedPopupPanel optionsPopup = new DecoratedPopupPanel(true);
            final VerticalPanel optionsContainer = new VerticalPanel();
            optionsContainer.setWidth("125px");
            for (NavOption option: currNode.getOptions()) {
                optionsContainer.add(new Hyperlink(option.getName(), tokenService.getToken(option)));
            }
            // TODO provide a debug id... this will most likely necessitate generation of a unique key
            optionsPopup.setWidget(optionsContainer);
            // Reposition the popup relative to node
            final int left = parent.getAbsoluteLeft() + 25;
            final int top = parent.getAbsoluteTop();

            optionsPopup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
                @Override
                public void setPosition(int offsetWidth, int offsetHeight) {
                    optionsPopup.setPopupPosition(left, top);
                }
            });
        }

        super.onBrowserEvent(context, parent, currNode, event, valueUpdater);
    }

    @Override
    public void render(Context context, NavNode value, SafeHtmlBuilder sb) {
        if (value != null) {
            sb.appendEscaped(value.getName());
        }
    }
}

}

Обратите внимание, TreeCell переопределяет onBrowserEvent.Отсюда мы можем получить указатель на узел и расположить всплывающее окно.Всплывающее окно создается с помощью обратного вызова.Странно!

NavNodeUtil совершает магию, подсчитывая дочерние элементы и добавляя категориальные заголовки A, B, C ... Z для дочерних узлов, которые превышают определенный порог.

...