ISIS-537: moved impl of LinksSelectorPanelAbstract (.java) to subclasses.
Project: http://git-wip-us.apache.org/repos/asf/isis/repo Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/b1677ca8 Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/b1677ca8 Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/b1677ca8 Branch: refs/heads/master Commit: b1677ca880b7a27a831ecf84e387952aca12a241 Parents: 3b80b5e Author: Dan Haywood <[email protected]> Authored: Sun Nov 9 11:57:48 2014 +0000 Committer: Dan Haywood <[email protected]> Committed: Mon Nov 10 10:21:35 2014 +0000 ---------------------------------------------------------------------- .../CollectionContentsLinksSelectorPanel.java | 408 ++++++++++++++++--- .../links/EntityLinksSelectorPanel.java | 352 +++++++++++++++- .../links/LinksSelectorPanelAbstract.java | 40 +- 3 files changed, 724 insertions(+), 76 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/isis/blob/b1677ca8/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/selector/links/CollectionContentsLinksSelectorPanel.java ---------------------------------------------------------------------- diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/selector/links/CollectionContentsLinksSelectorPanel.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/selector/links/CollectionContentsLinksSelectorPanel.java index 81f7ece..096903c 100644 --- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/selector/links/CollectionContentsLinksSelectorPanel.java +++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/selector/links/CollectionContentsLinksSelectorPanel.java @@ -19,26 +19,52 @@ package org.apache.isis.viewer.wicket.ui.components.collectioncontents.selector.links; -import java.util.List; +import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons; +import java.util.ArrayList; +import java.util.List; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; import com.google.common.collect.Lists; - +import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; +import org.apache.wicket.MarkupContainer; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.AbstractLink; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.IModel; - +import org.apache.wicket.model.Model; import org.apache.isis.applib.annotation.Render.Type; +import org.apache.isis.core.commons.lang.StringExtensions; import org.apache.isis.core.metamodel.facets.members.render.RenderFacet; import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation; +import org.apache.isis.viewer.wicket.model.hints.IsisUiHintEvent; import org.apache.isis.viewer.wicket.model.hints.UiHintContainer; +import org.apache.isis.viewer.wicket.model.hints.UiHintPathSignificant; +import org.apache.isis.viewer.wicket.model.links.LinkAndLabel; +import org.apache.isis.viewer.wicket.model.links.LinksProvider; import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel; +import org.apache.isis.viewer.wicket.ui.CollectionContentsAsFactory; import org.apache.isis.viewer.wicket.ui.ComponentFactory; import org.apache.isis.viewer.wicket.ui.ComponentType; +import org.apache.isis.viewer.wicket.ui.components.additionallinks.AdditionalLinksPanel; import org.apache.isis.viewer.wicket.ui.components.collection.CollectionCountProvider; import org.apache.isis.viewer.wicket.ui.components.collection.CollectionPanel; import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory; import org.apache.isis.viewer.wicket.ui.components.collectioncontents.unresolved.CollectionContentsAsUnresolvedPanelFactory; +import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract; +import org.apache.isis.viewer.wicket.ui.panels.PanelUtil; import org.apache.isis.viewer.wicket.ui.selector.links.LinksSelectorPanelAbstract; +import org.apache.isis.viewer.wicket.ui.util.Components; +import org.apache.isis.viewer.wicket.ui.util.CssClassAppender; +import org.apache.isis.viewer.wicket.ui.util.CssClassRemover; /** * Provides a list of links for selecting other views that support @@ -49,29 +75,317 @@ import org.apache.isis.viewer.wicket.ui.selector.links.LinksSelectorPanelAbstrac * Most of the heavy lifting is factored out into the superclass, * {@link LinksSelectorPanelAbstract}. */ -public class CollectionContentsLinksSelectorPanel extends LinksSelectorPanelAbstract<EntityCollectionModel> implements CollectionCountProvider { +public class CollectionContentsLinksSelectorPanel + extends PanelAbstract<EntityCollectionModel> implements UiHintPathSignificant, CollectionCountProvider { private static final long serialVersionUID = 1L; + private static final String INVISIBLE_CLASS = "link-selector-panel-invisible"; + private static final int MAX_NUM_UNDERLYING_VIEWS = 10; + + private static final String ID_ADDITIONAL_LINKS = "additionalLinks"; + + private static final String ID_VIEWS = "views"; + private static final String ID_VIEW_LIST = "viewList"; + private static final String ID_VIEW_LINK = "viewLink"; + private static final String ID_VIEW_ITEM = "viewItem"; + private static final String ID_VIEW_ITEM_TITLE = "viewItemTitle"; + private static final String ID_VIEW_ITEM_ICON = "viewItemIcon"; + + private static final String UIHINT_VIEW = "view"; + private static final String ID_VIEW_BUTTON_TITLE = "viewButtonTitle"; + private static final String ID_VIEW_BUTTON_ICON = "viewButtonIcon"; + + private final ComponentType componentType; + private final String underlyingIdPrefix; + + private ComponentFactory selectedComponentFactory; + protected Component selectedComponent; + + /** + * May be <tt>null</tt>, depending upon the model implementation. + */ + protected WebMarkupContainer additionalLinks; + public CollectionContentsLinksSelectorPanel(final String id, final EntityCollectionModel model, final ComponentFactory factory) { - super(id, ComponentType.COLLECTION_CONTENTS.toString(), model, factory); + super(id, model); + this.underlyingIdPrefix = ComponentType.COLLECTION_CONTENTS.toString(); + this.componentType = factory.getComponentType(); } - /* (non-Javadoc) - * @see org.apache.isis.viewer.wicket.ui.selector.links.LinksSelectorPanelAbstract#onInitialize() + /** + * Build UI only after added to parent. */ - @Override public void onInitialize() { super.onInitialize(); + ComponentFactory componentFactory = getComponentFactoryRegistry().findComponentFactoryElseFailFast(getComponentType(), getModel()); + addAdditionalLinks(getModel()); + addUnderlyingViews(underlyingIdPrefix, getModel(), componentFactory); applyCssVisibility(additionalLinks, selectedComponent instanceof CollectionCountProvider); } - - @Override - protected EntityCollectionModel dummyOf(EntityCollectionModel model) { - return model.asDummy(); + + + protected void addAdditionalLinks(final EntityCollectionModel model) { + if(!(model instanceof LinksProvider)) { + permanentlyHide(ID_ADDITIONAL_LINKS); + return; + } + LinksProvider linksProvider = (LinksProvider) model; + List<LinkAndLabel> links = linksProvider.getLinks(); + + addAdditionalLinks(this, links); + } + + protected void addAdditionalLinks(MarkupContainer markupContainer, List<LinkAndLabel> links) { + if(links == null || links.isEmpty()) { + Components.permanentlyHide(markupContainer, ID_ADDITIONAL_LINKS); + return; + } + links = Lists.newArrayList(links); // copy, to serialize any lazy evaluation + + additionalLinks = new AdditionalLinksPanel(ID_ADDITIONAL_LINKS, links); + markupContainer.addOrReplace(additionalLinks); + } + + private void addUnderlyingViews(final String underlyingIdPrefix, final EntityCollectionModel model, final ComponentFactory factory) { + final List<ComponentFactory> componentFactories = findOtherComponentFactories(model, factory); + + final int selected = honourViewHintElseDefault(componentFactories, model); + + final CollectionContentsLinksSelectorPanel selectorPanel = this; + + // create all, hide the one not selected + final Component[] underlyingViews = new Component[MAX_NUM_UNDERLYING_VIEWS]; + int i = 0; + final EntityCollectionModel emptyModel = dummyOf(model); + for (ComponentFactory componentFactory : componentFactories) { + final String underlyingId = underlyingIdPrefix + "-" + i; + + Component underlyingView = componentFactory.createComponent(underlyingId,i==selected? model: emptyModel); + underlyingViews[i++] = underlyingView; + selectorPanel.addOrReplace(underlyingView); + } + + // hide any unused placeholders + while(i<MAX_NUM_UNDERLYING_VIEWS) { + String underlyingId = underlyingIdPrefix + "-" + i; + permanentlyHide(underlyingId); + i++; + } + + // selector + if (componentFactories.size() <= 1) { + permanentlyHide(ID_VIEWS); + } else { + final Model<ComponentFactory> componentFactoryModel = new Model<>(); + + selectorPanel.selectedComponentFactory = componentFactories.get(selected); + componentFactoryModel.setObject(selectorPanel.selectedComponentFactory); + + final WebMarkupContainer views = new WebMarkupContainer(ID_VIEWS); + + final Label viewButtonTitle = new Label(ID_VIEW_BUTTON_TITLE, "Hidden"); + views.addOrReplace(viewButtonTitle); + + final Label viewButtonIcon = new Label(ID_VIEW_BUTTON_ICON, ""); + views.addOrReplace(viewButtonIcon); + + final WebMarkupContainer container = new WebMarkupContainer(ID_VIEW_LIST); + + views.addOrReplace(container); + views.setOutputMarkupId(true); + + this.setOutputMarkupId(true); + + final ListView<ComponentFactory> listView = new ListView<ComponentFactory>(ID_VIEW_ITEM, componentFactories) { + + private static final long serialVersionUID = 1L; + + @Override + protected void populateItem(ListItem<ComponentFactory> item) { + + final int underlyingViewNum = item.getIndex(); + + final ComponentFactory componentFactory = item.getModelObject(); + final AbstractLink link = new AjaxLink<Void>(ID_VIEW_LINK) { + private static final long serialVersionUID = 1L; + @Override + public void onClick(AjaxRequestTarget target) { + CollectionContentsLinksSelectorPanel linksSelectorPanel = CollectionContentsLinksSelectorPanel.this; + linksSelectorPanel.setViewHintAndBroadcast(underlyingViewNum, target); + + final EntityCollectionModel dummyModel = dummyOf(model); + for(int i=0; i<MAX_NUM_UNDERLYING_VIEWS; i++) { + final Component component = underlyingViews[i]; + if(component == null) { + continue; + } + final boolean isSelected = i == underlyingViewNum; + applyCssVisibility(component, isSelected); + component.setDefaultModel(isSelected? model: dummyModel); + } + + selectorPanel.selectedComponentFactory = componentFactory; + selectorPanel.selectedComponent = underlyingViews[underlyingViewNum]; + selectorPanel.onSelect(target); + target.add(selectorPanel, views); + } + + @Override + protected void onComponentTag(ComponentTag tag) { + super.onComponentTag(tag); + Buttons.fixDisabledState(this, tag); + } + }; + + IModel<String> title = nameFor(componentFactory); + Label viewItemTitleLabel = new Label(ID_VIEW_ITEM_TITLE, title); + link.add(viewItemTitleLabel); + + Label viewItemIcon = new Label(ID_VIEW_ITEM_ICON, ""); + link.add(viewItemIcon); + + boolean isEnabled = componentFactory != selectorPanel.selectedComponentFactory; + if (!isEnabled) { + viewButtonTitle.setDefaultModel(title); + IModel<String> cssClass = cssClassFor(componentFactory, viewButtonIcon); + viewButtonIcon.add(AttributeModifier.replace("class", "ViewLinkItem " + cssClass.getObject())); + link.setVisible(false); + } else { + IModel<String> cssClass = cssClassFor(componentFactory, viewItemIcon); + viewItemIcon.add(new CssClassAppender(cssClass)); + } + + item.add(link); + } + + private IModel<String> cssClassFor(final ComponentFactory componentFactory, Label viewIcon) { + IModel<String> cssClass = null; + if (componentFactory instanceof CollectionContentsAsFactory) { + CollectionContentsAsFactory collectionContentsAsFactory = (CollectionContentsAsFactory) componentFactory; + cssClass = collectionContentsAsFactory.getCssClass(); + viewIcon.setDefaultModelObject(""); + viewIcon.setEscapeModelStrings(true); + } + if (cssClass == null) { + String name = componentFactory.getName(); + cssClass = Model.of(StringExtensions.asLowerDashed(name)); + // Small hack: if there is no specific CSS class then we assume that background-image is used + // the span.ViewItemLink should have some content to show it + // FIX: find a way to do this with CSS (width and height don't seems to help) + viewIcon.setDefaultModelObject("     "); + viewIcon.setEscapeModelStrings(false); + } + return cssClass; + } + + private IModel<String> nameFor(final ComponentFactory componentFactory) { + IModel<String> name = null; + if (componentFactory instanceof CollectionContentsAsFactory) { + CollectionContentsAsFactory collectionContentsAsFactory = (CollectionContentsAsFactory) componentFactory; + name = collectionContentsAsFactory.getTitleLabel(); + } + if (name == null) { + name = Model.of(componentFactory.getName()); + } + return name; + } + }; + container.add(listView); + addOrReplace(views); + } + + for(i=0; i<MAX_NUM_UNDERLYING_VIEWS; i++) { + Component component = underlyingViews[i]; + if(component != null) { + if(i != selected) { + component.add(new CssClassAppender(INVISIBLE_CLASS)); + } else { + selectedComponent = component; + } + } + } + } + + + + protected void setViewHintAndBroadcast(int viewNum, AjaxRequestTarget target) { + final UiHintContainer uiHintContainer = getUiHintContainer(); + if(uiHintContainer == null) { + return; + } + uiHintContainer.setHint(CollectionContentsLinksSelectorPanel.this, UIHINT_VIEW, ""+viewNum); + send(getPage(), Broadcast.EXACT, new IsisUiHintEvent(uiHintContainer, target)); } - + /** + * Iterates up the component hierarchy looking for a parent + * {@link org.apache.isis.viewer.wicket.ui.components.collection.CollectionPanel}, and if so adds to ajax target so that it'll + * be repainted. + * + * <p> + * Yeah, agreed, it's a little bit hacky doing it this way, because it bakes + * in knowledge that this component is created, somehow, by a parent {@link org.apache.isis.viewer.wicket.ui.components.collection.CollectionPanel}. + * Perhaps it could be refactored to use a more general purpose observer pattern? + * + * <p> + * In fact, I've since discovered that Wicket has an event bus, which is used by the + * {@link UiHintContainer hinting mechanism}. So this ought to be relatively easy to do. + */ + protected void onSelect(AjaxRequestTarget target) { + Component component = this; + while(component != null) { + if(component instanceof CollectionPanel) { + CollectionPanel collectionPanel = (CollectionPanel) component; + boolean hasCount = collectionPanel.hasCount(); + if(hasCount) { + collectionPanel.updateLabel(target); + } + if(additionalLinks != null) { + applyCssVisibility(additionalLinks, hasCount); + } + return; + } + component = component.getParent(); + } + } + + + protected static void applyCssVisibility(final Component component, final boolean visible) { + if(component == null) { + return; + } + AttributeModifier modifier = visible ? new CssClassRemover(INVISIBLE_CLASS) : new CssClassAppender(INVISIBLE_CLASS); + component.add(modifier); + } + + protected int honourViewHintElseDefault(final List<ComponentFactory> componentFactories, final IModel<?> model) { + // honour hints ... + final UiHintContainer hintContainer = getUiHintContainer(); + if(hintContainer != null) { + String viewStr = hintContainer.getHint(this, UIHINT_VIEW); + if(viewStr != null) { + try { + int view = Integer.parseInt(viewStr); + if(view >= 0 && view < componentFactories.size()) { + return view; + } + } catch(NumberFormatException ex) { + // ignore + } + } + } + + // ... else default + int initialFactory = determineInitialFactory(componentFactories, model); + if(hintContainer != null) { + hintContainer.setHint(this, UIHINT_VIEW, ""+initialFactory); + // don't broadcast (no AjaxRequestTarget, still configuring initial setup) + } + return initialFactory; + } + + /** * return the index of {@link CollectionContentsAsUnresolvedPanelFactory unresolved panel} if present and not eager loading; * else the index of {@link CollectionContentsAsAjaxTablePanelFactory ajax table} if present, @@ -92,11 +406,44 @@ public class CollectionContentsLinksSelectorPanel extends LinksSelectorPanelAbst return 0; } - @Override + private List<ComponentFactory> findOtherComponentFactories(final EntityCollectionModel model, final ComponentFactory ignoreFactory) { + final List<ComponentFactory> componentFactories = getComponentFactoryRegistry().findComponentFactories(componentType, model); + ArrayList<ComponentFactory> otherFactories = Lists.newArrayList(Collections2.filter(componentFactories, new Predicate<ComponentFactory>() { + @Override + public boolean apply(final ComponentFactory input) { + return input != ignoreFactory; + } + })); + return ordered(otherFactories); + } + protected List<ComponentFactory> ordered(List<ComponentFactory> componentFactories) { return orderAjaxTableToEnd(componentFactories); } + + @Override + public void renderHead(final IHeaderResponse response) { + super.renderHead(response); + PanelUtil.renderHead(response, LinksSelectorPanelAbstract.class); + } + + + + + + + + /** + * Ask for a dummy (empty) {@link Model} to pass into those components that are rendered but will be + * made invisible using CSS styling. + */ + protected EntityCollectionModel dummyOf(EntityCollectionModel model) { + return model.asDummy(); + } + + + static List<ComponentFactory> orderAjaxTableToEnd(List<ComponentFactory> componentFactories) { int ajaxTableIdx = findAjaxTable(componentFactories); if(ajaxTableIdx>=0) { @@ -134,38 +481,6 @@ public class CollectionContentsLinksSelectorPanel extends LinksSelectorPanelAbst return renderFacet != null && renderFacet.value() == Type.EAGERLY; } - /** - * Iterates up the component hierarchy looking for a parent - * {@link CollectionPanel}, and if so adds to ajax target so that it'll - * be repainted. - * - * <p> - * Yeah, agreed, it's a little bit hacky doing it this way, because it bakes - * in knowledge that this component is created, somehow, by a parent {@link CollectionPanel}. - * Perhaps it could be refactored to use a more general purpose observer pattern? - * - * <p> - * In fact, I've since discovered that Wicket has an event bus, which is used by the - * {@link UiHintContainer hinting mechanism}. So this ought to be relatively easy to do. - */ - protected void onSelect(AjaxRequestTarget target) { - super.onSelect(target); - Component component = this; - while(component != null) { - if(component instanceof CollectionPanel) { - CollectionPanel collectionPanel = (CollectionPanel) component; - boolean hasCount = collectionPanel.hasCount(); - if(hasCount) { - collectionPanel.updateLabel(target); - } - if(additionalLinks != null) { - applyCssVisibility(additionalLinks, hasCount); - } - return; - } - component = component.getParent(); - } - } @Override public Integer getCount() { @@ -177,5 +492,4 @@ public class CollectionContentsLinksSelectorPanel extends LinksSelectorPanelAbst } } - } http://git-wip-us.apache.org/repos/asf/isis/blob/b1677ca8/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.java ---------------------------------------------------------------------- diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.java index 378f277..7cab8d8 100644 --- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.java +++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.java @@ -19,15 +19,45 @@ package org.apache.isis.viewer.wicket.ui.components.entity.selector.links; -import java.util.List; +import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons; +import java.util.ArrayList; +import java.util.List; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import org.apache.wicket.AttributeModifier; +import org.apache.wicket.Component; +import org.apache.wicket.MarkupContainer; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.AbstractLink; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.IModel; - +import org.apache.wicket.model.Model; +import org.apache.isis.core.commons.lang.StringExtensions; +import org.apache.isis.viewer.wicket.model.hints.IsisUiHintEvent; import org.apache.isis.viewer.wicket.model.hints.UiHintContainer; +import org.apache.isis.viewer.wicket.model.hints.UiHintPathSignificant; +import org.apache.isis.viewer.wicket.model.links.LinkAndLabel; +import org.apache.isis.viewer.wicket.model.links.LinksProvider; import org.apache.isis.viewer.wicket.model.models.EntityModel; +import org.apache.isis.viewer.wicket.ui.CollectionContentsAsFactory; import org.apache.isis.viewer.wicket.ui.ComponentFactory; import org.apache.isis.viewer.wicket.ui.ComponentType; +import org.apache.isis.viewer.wicket.ui.components.additionallinks.AdditionalLinksPanel; +import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract; +import org.apache.isis.viewer.wicket.ui.panels.PanelUtil; import org.apache.isis.viewer.wicket.ui.selector.links.LinksSelectorPanelAbstract; +import org.apache.isis.viewer.wicket.ui.util.Components; +import org.apache.isis.viewer.wicket.ui.util.CssClassAppender; +import org.apache.isis.viewer.wicket.ui.util.CssClassRemover; /** * Provides a list of links for selecting other views that support @@ -37,20 +67,47 @@ import org.apache.isis.viewer.wicket.ui.selector.links.LinksSelectorPanelAbstrac * Most of the heavy lifting is factored out into the superclass, * {@link LinksSelectorPanelAbstract}. */ -public class EntityLinksSelectorPanel extends LinksSelectorPanelAbstract<EntityModel> { +public class EntityLinksSelectorPanel extends PanelAbstract<EntityModel> implements UiHintPathSignificant { + private static final long serialVersionUID = 1L; + private static final String INVISIBLE_CLASS = "link-selector-panel-invisible"; + private static final int MAX_NUM_UNDERLYING_VIEWS = 10; + + private static final String ID_ADDITIONAL_LINKS = "additionalLinks"; + public static final String ID_ADDITIONAL_LINK = "additionalLink"; + + private static final String ID_VIEWS = "views"; + private static final String ID_VIEW_LIST = "viewList"; + private static final String ID_VIEW_LINK = "viewLink"; + private static final String ID_VIEW_ITEM = "viewItem"; + private static final String ID_VIEW_ITEM_TITLE = "viewItemTitle"; + private static final String ID_VIEW_ITEM_ICON = "viewItemIcon"; + + private static final String UIHINT_VIEW = "view"; + private static final String ID_VIEW_BUTTON_TITLE = "viewButtonTitle"; + private static final String ID_VIEW_BUTTON_ICON = "viewButtonIcon"; + + private final ComponentType componentType; + private final String underlyingIdPrefix; + + private ComponentFactory selectedComponentFactory; + protected Component selectedComponent; + + /** + * May be <tt>null</tt>, depending upon the model implementation. + */ + protected WebMarkupContainer additionalLinks; + + public EntityLinksSelectorPanel(final String id, final EntityModel model, final ComponentFactory factory) { - super(id, ComponentType.ENTITY.toString(), model, factory); + super(id, model); + this.underlyingIdPrefix = ComponentType.ENTITY.toString(); + this.componentType = factory.getComponentType(); } - @Override - protected EntityModel dummyOf(EntityModel model) { - return model; - } - @Override protected int determineInitialFactory(List<ComponentFactory> componentFactories, IModel<?> model) { return 0; } @@ -61,4 +118,281 @@ public class EntityLinksSelectorPanel extends LinksSelectorPanelAbstract<EntityM return null; } + + /** + * Build UI only after added to parent. + */ + public void onInitialize() { + super.onInitialize(); + ComponentFactory componentFactory = getComponentFactoryRegistry().findComponentFactoryElseFailFast(getComponentType(), getModel()); + addAdditionalLinks(getModel()); + addUnderlyingViews(underlyingIdPrefix, getModel(), componentFactory); + } + + protected void addAdditionalLinks(final EntityModel model) { + if(!(model instanceof LinksProvider)) { + permanentlyHide(ID_ADDITIONAL_LINKS); + return; + } + LinksProvider linksProvider = (LinksProvider) model; + List<LinkAndLabel> links = linksProvider.getLinks(); + + addAdditionalLinks(this, links); + } + + protected void addAdditionalLinks(MarkupContainer markupContainer, List<LinkAndLabel> links) { + if(links == null || links.isEmpty()) { + Components.permanentlyHide(markupContainer, ID_ADDITIONAL_LINKS); + return; + } + links = Lists.newArrayList(links); // copy, to serialize any lazy evaluation + + additionalLinks = new AdditionalLinksPanel(ID_ADDITIONAL_LINKS, links); + markupContainer.addOrReplace(additionalLinks); + } + + private void addUnderlyingViews(final String underlyingIdPrefix, final EntityModel model, final ComponentFactory factory) { + final List<ComponentFactory> componentFactories = findOtherComponentFactories(model, factory); + + final int selected = honourViewHintElseDefault(componentFactories, model); + + final EntityLinksSelectorPanel selectorPanel = this; + + // create all, hide the one not selected + final Component[] underlyingViews = new Component[MAX_NUM_UNDERLYING_VIEWS]; + int i = 0; + final EntityModel emptyModel = dummyOf(model); + for (ComponentFactory componentFactory : componentFactories) { + final String underlyingId = underlyingIdPrefix + "-" + i; + + Component underlyingView = componentFactory.createComponent(underlyingId,i==selected? model: emptyModel); + underlyingViews[i++] = underlyingView; + selectorPanel.addOrReplace(underlyingView); + } + + // hide any unused placeholders + while(i<MAX_NUM_UNDERLYING_VIEWS) { + String underlyingId = underlyingIdPrefix + "-" + i; + permanentlyHide(underlyingId); + i++; + } + + // selector + if (componentFactories.size() <= 1) { + permanentlyHide(ID_VIEWS); + } else { + final Model<ComponentFactory> componentFactoryModel = new Model<>(); + + selectorPanel.selectedComponentFactory = componentFactories.get(selected); + componentFactoryModel.setObject(selectorPanel.selectedComponentFactory); + + final WebMarkupContainer views = new WebMarkupContainer(ID_VIEWS); + + final Label viewButtonTitle = new Label(ID_VIEW_BUTTON_TITLE, "Hidden"); + views.addOrReplace(viewButtonTitle); + + final Label viewButtonIcon = new Label(ID_VIEW_BUTTON_ICON, ""); + views.addOrReplace(viewButtonIcon); + + final WebMarkupContainer container = new WebMarkupContainer(ID_VIEW_LIST); + + views.addOrReplace(container); + views.setOutputMarkupId(true); + + this.setOutputMarkupId(true); + + final ListView<ComponentFactory> listView = new ListView<ComponentFactory>(ID_VIEW_ITEM, componentFactories) { + + private static final long serialVersionUID = 1L; + + @Override + protected void populateItem(ListItem<ComponentFactory> item) { + + final int underlyingViewNum = item.getIndex(); + + final ComponentFactory componentFactory = item.getModelObject(); + final AbstractLink link = new AjaxLink<Void>(ID_VIEW_LINK) { + private static final long serialVersionUID = 1L; + @Override + public void onClick(AjaxRequestTarget target) { + EntityLinksSelectorPanel linksSelectorPanel = EntityLinksSelectorPanel.this; + linksSelectorPanel.setViewHintAndBroadcast(underlyingViewNum, target); + + final EntityModel dummyModel = dummyOf(model); + for(int i=0; i<MAX_NUM_UNDERLYING_VIEWS; i++) { + final Component component = underlyingViews[i]; + if(component == null) { + continue; + } + final boolean isSelected = i == underlyingViewNum; + applyCssVisibility(component, isSelected); + component.setDefaultModel(isSelected? model: dummyModel); + } + + selectorPanel.selectedComponentFactory = componentFactory; + selectorPanel.selectedComponent = underlyingViews[underlyingViewNum]; + selectorPanel.onSelect(target); + target.add(selectorPanel, views); + } + + @Override + protected void onComponentTag(ComponentTag tag) { + super.onComponentTag(tag); + Buttons.fixDisabledState(this, tag); + } + }; + + IModel<String> title = nameFor(componentFactory); + Label viewItemTitleLabel = new Label(ID_VIEW_ITEM_TITLE, title); + link.add(viewItemTitleLabel); + + Label viewItemIcon = new Label(ID_VIEW_ITEM_ICON, ""); + link.add(viewItemIcon); + + boolean isEnabled = componentFactory != selectorPanel.selectedComponentFactory; + if (!isEnabled) { + viewButtonTitle.setDefaultModel(title); + IModel<String> cssClass = cssClassFor(componentFactory, viewButtonIcon); + viewButtonIcon.add(AttributeModifier.replace("class", "ViewLinkItem " + cssClass.getObject())); + link.setVisible(false); + } else { + IModel<String> cssClass = cssClassFor(componentFactory, viewItemIcon); + viewItemIcon.add(new CssClassAppender(cssClass)); + } + + item.add(link); + } + + private IModel<String> cssClassFor(final ComponentFactory componentFactory, Label viewIcon) { + IModel<String> cssClass = null; + if (componentFactory instanceof CollectionContentsAsFactory) { + CollectionContentsAsFactory collectionContentsAsFactory = (CollectionContentsAsFactory) componentFactory; + cssClass = collectionContentsAsFactory.getCssClass(); + viewIcon.setDefaultModelObject(""); + viewIcon.setEscapeModelStrings(true); + } + if (cssClass == null) { + String name = componentFactory.getName(); + cssClass = Model.of(StringExtensions.asLowerDashed(name)); + // Small hack: if there is no specific CSS class then we assume that background-image is used + // the span.ViewItemLink should have some content to show it + // FIX: find a way to do this with CSS (width and height don't seems to help) + viewIcon.setDefaultModelObject("     "); + viewIcon.setEscapeModelStrings(false); + } + return cssClass; + } + + private IModel<String> nameFor(final ComponentFactory componentFactory) { + IModel<String> name = null; + if (componentFactory instanceof CollectionContentsAsFactory) { + CollectionContentsAsFactory collectionContentsAsFactory = (CollectionContentsAsFactory) componentFactory; + name = collectionContentsAsFactory.getTitleLabel(); + } + if (name == null) { + name = Model.of(componentFactory.getName()); + } + return name; + } + }; + container.add(listView); + addOrReplace(views); + } + + for(i=0; i<MAX_NUM_UNDERLYING_VIEWS; i++) { + Component component = underlyingViews[i]; + if(component != null) { + if(i != selected) { + component.add(new CssClassAppender(INVISIBLE_CLASS)); + } else { + selectedComponent = component; + } + } + } + } + + + + protected void setViewHintAndBroadcast(int viewNum, AjaxRequestTarget target) { + final UiHintContainer uiHintContainer = getUiHintContainer(); + if(uiHintContainer == null) { + return; + } + uiHintContainer.setHint(this, UIHINT_VIEW, ""+viewNum); + send(getPage(), Broadcast.EXACT, new IsisUiHintEvent(uiHintContainer, target)); + } + + /** + * Overrideable hook. + */ + protected void onSelect(AjaxRequestTarget target) { + } + + /** + * Ask for a dummy (empty) {@link Model} to pass into those components that are rendered but will be + * made invisible using CSS styling. + */ + protected EntityModel dummyOf(EntityModel model) { + return model; + } + + protected static void applyCssVisibility(final Component component, final boolean visible) { + if(component == null) { + return; + } + AttributeModifier modifier = visible ? new CssClassRemover(INVISIBLE_CLASS) : new CssClassAppender(INVISIBLE_CLASS); + component.add(modifier); + } + + protected int honourViewHintElseDefault(final List<ComponentFactory> componentFactories, final IModel<?> model) { + // honour hints ... + final UiHintContainer hintContainer = getUiHintContainer(); + if(hintContainer != null) { + String viewStr = hintContainer.getHint(this, UIHINT_VIEW); + if(viewStr != null) { + try { + int view = Integer.parseInt(viewStr); + if(view >= 0 && view < componentFactories.size()) { + return view; + } + } catch(NumberFormatException ex) { + // ignore + } + } + } + + // ... else default + int initialFactory = determineInitialFactory(componentFactories, model); + if(hintContainer != null) { + hintContainer.setHint(this, UIHINT_VIEW, ""+initialFactory); + // don't broadcast (no AjaxRequestTarget, still configuring initial setup) + } + return initialFactory; + } + + + private List<ComponentFactory> findOtherComponentFactories(final EntityModel model, final ComponentFactory ignoreFactory) { + final List<ComponentFactory> componentFactories = getComponentFactoryRegistry().findComponentFactories(componentType, model); + ArrayList<ComponentFactory> otherFactories = Lists.newArrayList(Collections2.filter(componentFactories, new Predicate<ComponentFactory>() { + @Override + public boolean apply(final ComponentFactory input) { + return input != ignoreFactory; + } + })); + return ordered(otherFactories); + } + + protected List<ComponentFactory> ordered(List<ComponentFactory> otherFactories) { + return otherFactories; + } + + + @Override + public void renderHead(final IHeaderResponse response) { + super.renderHead(response); + PanelUtil.renderHead(response, LinksSelectorPanelAbstract.class); + } + + + } http://git-wip-us.apache.org/repos/asf/isis/blob/b1677ca8/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/selector/links/LinksSelectorPanelAbstract.java ---------------------------------------------------------------------- diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/selector/links/LinksSelectorPanelAbstract.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/selector/links/LinksSelectorPanelAbstract.java index e494ce8..2b6b96f 100644 --- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/selector/links/LinksSelectorPanelAbstract.java +++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/selector/links/LinksSelectorPanelAbstract.java @@ -73,20 +73,20 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa private static final String ID_VIEW_ITEM = "viewItem"; private static final String ID_VIEW_ITEM_TITLE = "viewItemTitle"; private static final String ID_VIEW_ITEM_ICON = "viewItemIcon"; - + private static final String UIHINT_VIEW = "view"; private static final String ID_VIEW_BUTTON_TITLE = "viewButtonTitle"; private static final String ID_VIEW_BUTTON_ICON = "viewButtonIcon"; private final ComponentType componentType; private final String underlyingIdPrefix; - + private ComponentFactory selectedComponentFactory; protected Component selectedComponent; /** * May be <tt>null</tt>, depending upon the model implementation. - * + * * @see #addAdditionalLinks(IModel) */ protected WebMarkupContainer additionalLinks; @@ -97,7 +97,7 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa this.underlyingIdPrefix = underlyingIdPrefix; this.componentType = factory.getComponentType(); } - + /** * Build UI only after added to parent. */ @@ -115,7 +115,7 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa } LinksProvider linksProvider = (LinksProvider) model; List<LinkAndLabel> links = linksProvider.getLinks(); - + addAdditionalLinks(this, links); } @@ -125,25 +125,25 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa return; } links = Lists.newArrayList(links); // copy, to serialize any lazy evaluation - + additionalLinks = new AdditionalLinksPanel(ID_ADDITIONAL_LINKS, links); markupContainer.addOrReplace(additionalLinks); } - + private void addUnderlyingViews(final String underlyingIdPrefix, final T model, final ComponentFactory factory) { final List<ComponentFactory> componentFactories = findOtherComponentFactories(model, factory); final int selected = honourViewHintElseDefault(componentFactories, model); final LinksSelectorPanelAbstract<T> selectorPanel = LinksSelectorPanelAbstract.this; - + // create all, hide the one not selected final Component[] underlyingViews = new Component[MAX_NUM_UNDERLYING_VIEWS]; int i = 0; final T emptyModel = dummyOf(model); for (ComponentFactory componentFactory : componentFactories) { final String underlyingId = underlyingIdPrefix + "-" + i; - + Component underlyingView = componentFactory.createComponent(underlyingId,i==selected? model: emptyModel); underlyingViews[i++] = underlyingView; selectorPanel.addOrReplace(underlyingView); @@ -155,13 +155,13 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa permanentlyHide(underlyingId); i++; } - + // selector if (componentFactories.size() <= 1) { permanentlyHide(ID_VIEWS); } else { final Model<ComponentFactory> componentFactoryModel = new Model<>(); - + selectorPanel.selectedComponentFactory = componentFactories.get(selected); componentFactoryModel.setObject(selectorPanel.selectedComponentFactory); @@ -174,21 +174,21 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa views.addOrReplace(viewButtonIcon); final WebMarkupContainer container = new WebMarkupContainer(ID_VIEW_LIST); - + views.addOrReplace(container); views.setOutputMarkupId(true); - + this.setOutputMarkupId(true); - + final ListView<ComponentFactory> listView = new ListView<ComponentFactory>(ID_VIEW_ITEM, componentFactories) { private static final long serialVersionUID = 1L; @Override protected void populateItem(ListItem<ComponentFactory> item) { - + final int underlyingViewNum = item.getIndex(); - + final ComponentFactory componentFactory = item.getModelObject(); final AbstractLink link = new AjaxLink<Void>(ID_VIEW_LINK) { private static final long serialVersionUID = 1L; @@ -196,7 +196,7 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa public void onClick(AjaxRequestTarget target) { LinksSelectorPanelAbstract<T> linksSelectorPanel = LinksSelectorPanelAbstract.this; linksSelectorPanel.setViewHintAndBroadcast(underlyingViewNum, target); - + final T dummyModel = dummyOf(model); for(int i=0; i<MAX_NUM_UNDERLYING_VIEWS; i++) { final Component component = underlyingViews[i]; @@ -207,7 +207,7 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa applyCssVisibility(component, isSelected); component.setDefaultModel(isSelected? model: dummyModel); } - + selectorPanel.selectedComponentFactory = componentFactory; selectorPanel.selectedComponent = underlyingViews[underlyingViewNum]; selectorPanel.onSelect(target); @@ -277,7 +277,7 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa container.add(listView); addOrReplace(views); } - + for(i=0; i<MAX_NUM_UNDERLYING_VIEWS; i++) { Component component = underlyingViews[i]; if(component != null) { @@ -365,7 +365,7 @@ public abstract class LinksSelectorPanelAbstract<T extends IModel<?>> extends Pa return otherFactories; } - + @Override public void renderHead(final IHeaderResponse response) { super.renderHead(response);
