package org.treblereel.gwt.widget.client; import java.util.Set; import com.google.gwt.cell.client.AbstractSafeHtmlCell; import com.google.gwt.cell.client.ValueUpdater; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.safecss.shared.SafeStyles; import com.google.gwt.safecss.shared.SafeStylesUtils; import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.google.gwt.text.shared.SimpleSafeHtmlRenderer; import com.google.gwt.user.client.ui.AbstractImagePrototype; import com.google.gwt.view.client.SelectionModel; public class ExpandableCell<T extends TreeNode<T>> extends AbstractSafeHtmlCell<String> { /** * The HTML templates used to render the folder cell. */ interface NodeImageTemplate extends SafeHtmlTemplates { /** * The template for this Cell, which includes styles and a value. * * @param styles * the styles to include in the style attribute of the div * @param value * the safe value. Since the value type is {@link SafeHtml}, * it will not be escaped before including it in the * template. Alternatively, you could make the value type * String, in which case the value would be escaped. * @return a {@link SafeHtml} instance */ @SafeHtmlTemplates.Template("<div name=\"{0}\" style=\"{1}\">{2}</div>") SafeHtml cell(String name, SafeStyles styles, SafeHtml value); @SafeHtmlTemplates.Template("<div name=\"{0}\" style=\"{1}\"> {2}</div>") SafeHtml cellNode(String name, SafeStyles styles, SafeHtml value); } private TreeGrid treeGrid; private static org.treblereel.gwt.widget.client.images.Icons icons = GWT .create(org.treblereel.gwt.widget.client.images.Icons.class); /** * Create a singleton instance of the templates used to render the cell. */ private static NodeImageTemplate nodeImageTemplate = GWT .create(NodeImageTemplate.class); private static final SafeHtml ICON_ARROW_CLOSE = makeImage(icons .getClose16()); private static final SafeHtml ICON_ARROW_OPEN = makeImage(icons.getOpen16()); private static final SafeHtml ICON_FOLDER_CLOSE = makeImage(icons .getFolder16()); private static final SafeHtml ICON_FOLDER_OPEN = makeImage(icons .getFolderOpen16()); private static final SafeHtml ICON_CLEAR = makeImage(icons.getClear()); private static final SafeHtml ICON_DOCUMENT = makeImage(icons.getDocument()); private static final SafeHtml ICON_CHECHBOX_TRUE = makeImage(icons .getCheckboxTrue()); private static final SafeHtml ICON_CHECHBOX_FALSE = makeImage(icons .getCheckboxFalse()); private static final SafeHtml ICON_CHECHBOX_CHILDS_SELECTED = makeImage(icons .getCheckboxChildsSelected()); /** * Make icons available as SafeHtml * * @param resource * @return */ private static SafeHtml makeImage(ImageResource resource) { AbstractImagePrototype proto = AbstractImagePrototype.create(resource); return proto.getSafeHtml(); } T currentRow; private SafeHtml rendered; private SafeStyles imageWidthStyle; public ExpandableCell(SafeHtmlRenderer<String> renderer, Set<String> consumedEvents) { super(renderer, consumedEvents); } public <T extends TreeNode<T>> ExpandableCell(TreeGrid<T> treeGrid) { super(SimpleSafeHtmlRenderer.getInstance(), "click", "keydown"); this.treeGrid = treeGrid; } private void createClearIcon(SafeHtmlBuilder sb, SafeStyles imgStyle, int times) { imageWidthStyle = SafeStylesUtils .fromTrustedString("float:left;width:16.0px;"); for (int i = 0; i < times; i++) { rendered = nodeImageTemplate.cell("ICON_ARROW__CLOSE", imageWidthStyle, ICON_CLEAR); sb.append(rendered); } } /** * @param sb * @param imgStyle */ @SuppressWarnings("unchecked") private void createCheckBoxImg(SafeHtmlBuilder sb, SafeStyles imgStyle) { imageWidthStyle = SafeStylesUtils .fromTrustedString("float:left;width:16.0px;margin-right:2px;"); SelectionModel<T> sm = (SelectionModel<T>) treeGrid.getSelectionModel(); boolean selected = sm.isSelected(currentRow); if (selected) { if (currentRow.getChildren().size() > 0) { if (currentRow.isChidlrenSelected()) { rendered = nodeImageTemplate.cell("ICON_CHECKBOX", imageWidthStyle, ICON_CHECHBOX_CHILDS_SELECTED); } else { rendered = nodeImageTemplate.cell("ICON_CHECKBOX", imageWidthStyle, ICON_CHECHBOX_TRUE); } } else { rendered = nodeImageTemplate.cell("ICON_CHECKBOX", imageWidthStyle, ICON_CHECHBOX_TRUE); } } else { rendered = nodeImageTemplate.cell("ICON_CHECKBOX", imageWidthStyle, ICON_CHECHBOX_FALSE); } sb.append(rendered); } private void createClosedArrowAndFolderSafeHtml(SafeHtmlBuilder sb, SafeStyles imgStyle) { SafeHtml rendered; if (currentRow.getChildren().size() != 0) { rendered = nodeImageTemplate.cell("ICON_ARROW__CLOSE", imgStyle, ICON_ARROW_CLOSE); } else { SafeStyles imageWidthStyle = SafeStylesUtils .fromTrustedString("float:left;width:16.0px;"); rendered = nodeImageTemplate.cell("ICON_ARROW__CLOSE", imageWidthStyle, ICON_CLEAR); } sb.append(rendered); if (treeGrid.isSelectable()) { createCheckBoxImg(sb, imgStyle); } rendered = nodeImageTemplate.cell("ICON_FOLDER__CLOSE", imgStyle, ICON_FOLDER_CLOSE); sb.append(rendered); } private void createLeafSafeHtml(SafeHtmlBuilder sb, SafeStyles imgStyle) { rendered = nodeImageTemplate.cell("ICON_DOCUMENT", imgStyle, ICON_DOCUMENT); sb.append(rendered); } private void createOpenArrowAndFolderSafeHtml(SafeHtmlBuilder sb, SafeStyles imgStyle) { SafeHtml rendered; if (currentRow.getChildren().size() != 0) { rendered = nodeImageTemplate.cell("ICON_ARROW__OPEN", imgStyle, ICON_ARROW_OPEN); sb.append(rendered); } if (treeGrid.isSelectable()) { createCheckBoxImg(sb, imgStyle); } rendered = nodeImageTemplate.cell("ICON_FOLDER__OPEN", imgStyle, ICON_FOLDER_OPEN); sb.append(rendered); } /** * Intern action * * @param value * selected value * @param valueUpdater * value updater or the custom value update to be called */ private void doAction(String value, ValueUpdater<String> valueUpdater) { // Trigger a value updater. In this case, the value doesn't actually // change, but we use a ValueUpdater to let the app know that a value // was clicked. if (valueUpdater != null) valueUpdater.update(value); } /** * Called when an event occurs in a rendered instance of this Cell. The * parent element refers to the element that contains the rendered cell, NOT * to the outermost element that the Cell rendered. */ @Override public void onBrowserEvent(com.google.gwt.cell.client.Cell.Context context, Element parent, String value, NativeEvent event, com.google.gwt.cell.client.ValueUpdater<String> valueUpdater) { // Let AbstractCell handle the keydown event. super.onBrowserEvent(context, parent, value, event, valueUpdater); // Handle the click event. if ("click".equals(event.getType())) { // Ignore clicks that occur outside of the outermost element. EventTarget eventTarget = event.getEventTarget(); if (parent.isOrHasChild(Element.as(eventTarget))) { Element el = Element.as(eventTarget); if (el.getNodeName().equalsIgnoreCase("IMG")) { doAction(el.getParentElement().getAttribute("name"), valueUpdater); } } } } /** * onEnterKeyDown is called when the user presses the ENTER key will the * Cell is selected. You are not required to override this method, but its a * common convention that allows your cell to respond to key events. */ @Override protected void onEnterKeyDown(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) { doAction(value, valueUpdater); } @SuppressWarnings("unchecked") @Override protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) { currentRow = (T) treeGrid.getDataStore().getList() .get(context.getIndex()); /* * Always do a null check on the value. Cell widgets can pass null to * cells if the underlying data contains a null, or if the data arrives * out of order. */ if (data == null) { return; } // generate the image cell SafeStyles imgStyle = SafeStylesUtils .fromTrustedString("float:left;cursor:hand;cursor:pointer;"); SafeStyles valueStyle = SafeStylesUtils .fromTrustedString("float:left;"); SafeHtml rendered; if (currentRow.getChildren().size() > 0) { createClearIcon(sb, imgStyle, currentRow.getLevel()); if (!currentRow.isExpand()) { createClosedArrowAndFolderSafeHtml(sb, imgStyle); } else { createOpenArrowAndFolderSafeHtml(sb, imgStyle); } } else { createClearIcon(sb, imgStyle, currentRow.getLevel() + 1); if (treeGrid.isSelectable()) { createCheckBoxImg(sb, imgStyle); } createLeafSafeHtml(sb, imgStyle); } // generate the value cell rendered = nodeImageTemplate.cellNode("EXPANDABLE_VALUE", valueStyle, SafeHtmlUtils.fromString(data.asString())); sb.append(rendered); } }