olgacosta
olgacosta

Reputation: 1138

How to open GWT CellTree nodes by clicking on text not icon

I'm using GWT CellTree complex example as discribed in javadoc. But to open tree node I must click on little arrow on the left of the node. I want to open tree by clicking on text. I searched some help and found out that I can use ClickableTextCell. Truly said, I don't understand where to start. Can you help me or provide another solutions? I want that node look like an anchor: when I mouse on the text, cursor become pointer and text is underline.

public <T> NodeInfo<?> getNodeInfo(T value) {
      if (value == null) {
        ListDataProvider<Composer> dataProvider = new ListDataProvider<CellTreeExample2.Composer>(
            composers);
        Cell<Composer> cell = new AbstractCell<Composer>() {
          @Override
          public void render(Context context, Composer value, SafeHtmlBuilder sb) {
            if (value != null) {
              sb.appendEscaped(value.getName());
            }
          }
        };
        return new DefaultNodeInfo<Composer>(dataProvider, cell);
      } else if (value instanceof Composer) {
        ListDataProvider<Playlist> dataProvider = new ListDataProvider<Playlist>(
            ((Composer) value).getPlaylists());
        Cell<Playlist> cell = new AbstractCell<Playlist>() {
          @Override
          public void render(Context context, Playlist value, SafeHtmlBuilder sb) {
            if (value != null) {
              sb.appendEscaped(value.getName());
            }
          }
        };
        return new DefaultNodeInfo<Playlist>(dataProvider, cell);
      } else if (value instanceof Playlist) {
        ListDataProvider<String> dataProvider = new ListDataProvider<String>(
            ((Playlist) value).getSongs());
        return new DefaultNodeInfo<String>(dataProvider, new TextCell(),
            selectionModel, null);
      }

      return null;
    }
    public boolean isLeaf(Object value) {
      if (value instanceof String) {
        return true;
      }
      return false;
    }

Upvotes: 1

Views: 612

Answers (3)

Edward Corrigall
Edward Corrigall

Reputation: 472

Rough Idea

Use a SingleSelectionModel and pass it into your DefaultNodeInfo. Add an onSelectionChanged handler, such that when it is called:

  1. Get the selected item
  2. Find the index of the item in the associated ListDataProvider, this will be used to open the associated child index
  3. Use the CellTree root TreeNode to open the associated child index
  4. Clear the selection model so that when the item is selected again (consecutively), the onSelectionChanged event will be fire again. This will emulate toggling.

Example: addSelectionChangeHandler

Add selection change handler.

composerSingleSelectionModel = new SingleSelectionModel<Composer>(COMPOSER_KEY_PROVIDER);
// ...
composerSingleSelectionModel.addSelectionChangeHandler(
    new SelectionChangeEvent.Handler() {
        @Override
        public void onSelectionChange(SelectionChangeEvent event) {
            final TreeNode rootTreeNode = cellTree.getRootTreeNode();
            final Composer selectedComposer = composerSingleSelectionModel.getSelectedObject();
            if (selectedComposer == null) return;
            final int index = composerListDataProvider.getList().indexOf(selectedComposer);
            if (index < 0) return;
            final boolean isOpen = rootTreeNode.isChildOpen(index);
            rootTreeNode.setChildOpen(index, !isOpen);
            /* Clear what is currently selected, so that
            * onSelectionChange is fired again when the same item is
            * selected consecutively.
            */
            composerSingleSelectionModel.clear();
        }
    });

Example: Custom TreeViewModel

Pass in composer selection model to custom TreeViewModel.

public class MusicTreeViewModel implements TreeViewModel {
    // Define constructor to pass in properties ...

    @Override
    public <T> NodeInfo<?> getNodeInfo(T value) {
        if (value == null) {
            return new DefaultNodeInfo<Composer>(composerListDataProvider, composerCell, composerSelectionModel, null);
        } else if (value instanceof Composer) {
            final Composer composer = (Composer) value;
            final Object composerKey = composerListDataProvider.getKey(composer);
            final ListDataProvider<Playlist> playlistListDataProvider = getPlaylistListDataProvider(composerKey);
            return new DefaultNodeInfo<Playlist>(playlistListDataProvider, playlistCell, playlistSelectionModel, null);
        } else {
            throw new IllegalArgumentException(
                "Unsupported object type: " + value.getClass().getName());
        }
    }

    @Override
    public boolean isLeaf(Object value) {
        if (value instanceof Composer) {
            return ((Composer) value).getPlaylists().isEmpty();
        } else if (value instanceof Playlist) {
            return true;
        }
        return false;
    }
}

Upvotes: 1

You must use a Cell for catch the click on the cell (like ClickableTextCell). In my project, I implemented this sytem for just first levels of an Tree :

Cell<String> nodeCell = new AbstractCell<String>("click", "keydown") {
    @Override
    public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) {
        String eventType = event.getType();
        // Special case the ENTER key for a unified user experience.
        if ("click".equals(eventType) || ("keydown".equals(eventType) && event.getKeyCode() == KeyCodes.KEY_ENTER)) {
            tree.getRootTreeNode().setChildOpen(context.getIndex(), !tree.getRootTreeNode().isChildOpen(context.getIndex()));
        }
    }

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

For open more levels, you must use setChildOpen in cascade :

tree.getRootTreeNode().setChildOpen(1, true).setChildOpen(1, true).setChildOpen(1, true);

Upvotes: 1

Thomas Broyer
Thomas Broyer

Reputation: 64541

Because you probably don't want those nodes to also be selectable, I'd use a NoSelectionModel.

Whenever you click on those nodes, call setChildOpen() on the parent TreeNode to toggle its state. To get the parent TreeNode, use setChildOpen(index, true) on the grandparent TreeNode (recursively up to getRootTreeNode(): because you know the node has already been loaded and is open (you're responding to an event on a child node), you can be sure that setChildOpen will return the TreeNode rather than null.
Finally, to get the index to pass to the setChildOpen methods, just use an indexOf() on the parent "domain object"'s list of children (i.e. composers.indexOf(composer), composer.getPlaylists().indexOf(playlist), etc.). This assumes that you can easily get the parent of a given object (the composer of a given playlist), either by maintaining bidirectional relations (playlist.getComposer().getPlaylists().indexOf(playlist)), or by building a map of the child→parent relations.

Below are some building-blocks that you'd call from the NoSelectionModel's SelectionHandler:

void toggleComposerOpen(Composer composer) {
  int index = composers.indexOf(composer);
  TreeNode rootTreeNode = tree.getRootTreeNode();
  rootTreeNode.setChildOpen(index, !rootTreeNode.isChildOpen(index));
}

void togglePlaylistOpen(Playlist playlist) {
  Composer composer = playlist.getComposer();
  TreeNode composerTreeNode = getTreeNode(composer);
  int index = composer.getPlaylist().indexOf(playlist);
  composer.setChildOpen(index, !composer.isChildOpen(index));
}

private void TreeNode getTreeNode(Composer composer) {
  int index = composers.indexOf(composer);
  return tree.getRootTreeNode().setChildOpen(index, true);
}

Upvotes: 1

Related Questions