summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorMarc Englund <marc.englund@itmill.com>2008-10-10 13:45:38 +0000
committerMarc Englund <marc.englund@itmill.com>2008-10-10 13:45:38 +0000
commita526c3f838808d270d457dc9d978c5cd548cfce3 (patch)
tree3b56be24722798099087a95b5376381abf20cb62 /src/com
parent189692557ac697932596a313152b5d0e47c29dfe (diff)
downloadvaadin-framework-a526c3f838808d270d457dc9d978c5cd548cfce3.tar.gz
vaadin-framework-a526c3f838808d270d457dc9d978c5cd548cfce3.zip
More for Sampler; view mode switcher etc
svn changeset:5628/svn branch:trunk
Diffstat (limited to 'src/com')
-rw-r--r--src/com/itmill/toolkit/demo/sampler/Feature.java7
-rw-r--r--src/com/itmill/toolkit/demo/sampler/ModeSwitch.java105
-rw-r--r--src/com/itmill/toolkit/demo/sampler/SamplerApplication.java408
-rw-r--r--src/com/itmill/toolkit/demo/sampler/features/DummyFeature.gifbin8417 -> 2005 bytes
-rw-r--r--src/com/itmill/toolkit/demo/sampler/features/DummyFeature.java4
5 files changed, 430 insertions, 94 deletions
diff --git a/src/com/itmill/toolkit/demo/sampler/Feature.java b/src/com/itmill/toolkit/demo/sampler/Feature.java
index 44eaa1bdb1..8772c0afd9 100644
--- a/src/com/itmill/toolkit/demo/sampler/Feature.java
+++ b/src/com/itmill/toolkit/demo/sampler/Feature.java
@@ -17,10 +17,13 @@ abstract public class Feature {
protected Example example = null;
+ public String getPathName() {
+ return getClass().getSimpleName();
+ }
+
/** Get the name of the feature. Override if needed. */
public String getName() {
- String[] cn = this.getClass().getName().split("\\.");
- return cn[cn.length - 1];
+ return getClass().getSimpleName();
}
abstract public String getDescription();
diff --git a/src/com/itmill/toolkit/demo/sampler/ModeSwitch.java b/src/com/itmill/toolkit/demo/sampler/ModeSwitch.java
new file mode 100644
index 0000000000..100679e73e
--- /dev/null
+++ b/src/com/itmill/toolkit/demo/sampler/ModeSwitch.java
@@ -0,0 +1,105 @@
+package com.itmill.toolkit.demo.sampler;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.itmill.toolkit.terminal.Resource;
+import com.itmill.toolkit.ui.Button;
+import com.itmill.toolkit.ui.CustomComponent;
+import com.itmill.toolkit.ui.OrderedLayout;
+import com.itmill.toolkit.ui.Button.ClickEvent;
+
+public class ModeSwitch extends CustomComponent {
+
+ OrderedLayout layout = new OrderedLayout(
+ OrderedLayout.ORIENTATION_HORIZONTAL);
+
+ HashMap idToButton = new HashMap();
+ Object mode = null;
+
+ public ModeSwitch() {
+ setCompositionRoot(layout);
+ setStyleName("ModeSwitch");
+ }
+
+ public Object getMode() {
+ return mode;
+ }
+
+ public void setMode(Object mode) {
+ if (idToButton.containsKey(mode)) {
+ this.mode = mode;
+ updateStyles();
+ fireEvent(new ModeSwitchEvent());
+ }
+ }
+
+ public void addListener(ModeSwitchListener listener) {
+ super.addListener(listener);
+ }
+
+ public void addMode(Object id, String caption, String description,
+ Resource icon) {
+ if (idToButton.containsKey(id)) {
+ removeMode(id);
+ }
+ Button b = new Button();
+ if (caption != null) {
+ b.setCaption(caption);
+ }
+ if (description != null) {
+ b.setDescription(description);
+ }
+ if (icon != null) {
+ b.setIcon(icon);
+ }
+ b.setData(id);
+ b.addListener(new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ setMode(event.getButton().getData());
+ }
+ });
+ idToButton.put(id, b);
+ layout.addComponent(b);
+ updateStyles();
+ }
+
+ public void removeMode(Object id) {
+ Button b = (Button) idToButton.remove(id);
+ layout.removeComponent(b);
+ updateStyles();
+ }
+
+ private void updateStyles() {
+ boolean first = true;
+ for (Iterator it = layout.getComponentIterator(); it.hasNext();) {
+ Button b = (Button) it.next();
+ String isOn = (b.getData() == mode ? "-on" : "");
+ if (first) {
+ first = false;
+ b.setStyleName("first" + isOn);
+ } else if (!it.hasNext()) {
+ b.setStyleName("last" + isOn);
+ } else {
+ b.setStyleName("mid" + isOn);
+ }
+ }
+ }
+
+ public interface ModeSwitchListener extends Listener {
+
+ }
+
+ public class ModeSwitchEvent extends Event {
+ private static final long serialVersionUID = -576318750089478453L;
+
+ public ModeSwitchEvent() {
+ super(ModeSwitch.this);
+ }
+
+ public Object getMode() {
+ return ModeSwitch.this.getMode();
+ }
+ }
+
+}
diff --git a/src/com/itmill/toolkit/demo/sampler/SamplerApplication.java b/src/com/itmill/toolkit/demo/sampler/SamplerApplication.java
index 1c61706ea4..cf91d7f758 100644
--- a/src/com/itmill/toolkit/demo/sampler/SamplerApplication.java
+++ b/src/com/itmill/toolkit/demo/sampler/SamplerApplication.java
@@ -1,23 +1,31 @@
package com.itmill.toolkit.demo.sampler;
+import java.net.URL;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.LinkedList;
import com.itmill.toolkit.Application;
-import com.itmill.toolkit.data.Container;
import com.itmill.toolkit.data.Item;
import com.itmill.toolkit.data.Property;
import com.itmill.toolkit.data.Property.ValueChangeEvent;
import com.itmill.toolkit.data.util.HierarchicalContainer;
import com.itmill.toolkit.data.util.ObjectProperty;
+import com.itmill.toolkit.demo.sampler.ModeSwitch.ModeSwitchEvent;
import com.itmill.toolkit.demo.sampler.features.DummyFeature;
import com.itmill.toolkit.terminal.ClassResource;
+import com.itmill.toolkit.terminal.DownloadStream;
+import com.itmill.toolkit.terminal.ExternalResource;
import com.itmill.toolkit.terminal.Resource;
import com.itmill.toolkit.terminal.ThemeResource;
import com.itmill.toolkit.ui.Button;
import com.itmill.toolkit.ui.Component;
+import com.itmill.toolkit.ui.CustomComponent;
import com.itmill.toolkit.ui.Embedded;
import com.itmill.toolkit.ui.ExpandLayout;
+import com.itmill.toolkit.ui.GridLayout;
+import com.itmill.toolkit.ui.Label;
import com.itmill.toolkit.ui.SplitPanel;
import com.itmill.toolkit.ui.Table;
import com.itmill.toolkit.ui.Tree;
@@ -27,6 +35,7 @@ import com.itmill.toolkit.ui.Button.ClickListener;
public class SamplerApplication extends Application {
+ // Main structure, root is always a FeatureSet that is not shown
private static final FeatureSet features = new FeatureSet("All",
new Feature[] {
// Main sets
@@ -74,55 +83,181 @@ public class SamplerApplication extends Application {
});
- SplitPanel split = null;
-
- FeatureList currentList = null;
- FeatureView featureView = null;
-
- Container.Ordered allFeatures = null;
- Property currentFeature = new ObjectProperty(null, Feature.class);
+ // All features in one container
+ private static final HierarchicalContainer allFeatures = features
+ .getContainer(true);
public void init() {
setTheme("example");
- setMainWindow(new MainWindow());
+ setMainWindow(new SamplerWindow());
}
- private class MainWindow extends Window {
+ // Supports multiple browser windows
+ public Window getWindow(String name) {
+ Window w = super.getWindow(name);
+ if (w == null) {
+ w = new SamplerWindow();
+ w.setName(name);
+ addWindow(w);
+ // secondary windows will support normal reload if this is
+ // enabled, but the url gets ugly:
+ // w.open(new ExternalResource(w.getURL()));
- MainWindow() {
- allFeatures = (Container.Ordered) features.getContainer(true);
+ }
+ return w;
+ }
- ExpandLayout main = new ExpandLayout();
- setLayout(main);
- main.setSizeFull();
+ /**
+ * Gets absolute path for given Feature
+ *
+ * @param f
+ * the Feature whose path to get, of null if not found
+ * @return the path of the Feature
+ */
+ String getPathFor(Feature f) {
+ if (allFeatures.containsId(f)) {
+ String path = f.getPathName();
+ f = (Feature) allFeatures.getParent(f);
+ while (f != null) {
+ path = f.getPathName() + "/" + path;
+ }
+ return path;
+ }
+ return null;
+ }
+ /**
+ * The main window for Sampler, contains the full application UI.
+ *
+ */
+ private class SamplerWindow extends Window {
+ private FeatureList currentList = new FeatureGrid();
+ private FeatureView featureView = new FeatureView();
+ private Property currentFeature = new ObjectProperty(null,
+ Feature.class);
+
+ private MainArea mainArea = new MainArea();
+
+ SamplerWindow() {
+ // Main top/expanded-bottom layout
+ ExpandLayout mainExpand = new ExpandLayout();
+ setLayout(mainExpand);
+ mainExpand.setSizeFull();
+
+ // topbar (navigation)
ExpandLayout nav = new ExpandLayout(
ExpandLayout.ORIENTATION_HORIZONTAL);
- main.addComponent(nav);
+ mainExpand.addComponent(nav);
nav.setHeight("40px");
nav.setWidth("100%");
nav.setStyleName("topbar");
- split = new SplitPanel(SplitPanel.ORIENTATION_HORIZONTAL);
+ // Upper left logo
+ Component logo = createLogo();
+ nav.addComponent(logo);
+ nav.setComponentAlignment(logo, ExpandLayout.ALIGNMENT_LEFT,
+ ExpandLayout.ALIGNMENT_VERTICAL_CENTER);
+ nav.expand(logo);
+
+ // Previous sample
+ Button b = createPrevButton();
+ nav.addComponent(b);
+ nav.setComponentAlignment(b, ExpandLayout.ALIGNMENT_LEFT,
+ ExpandLayout.ALIGNMENT_VERTICAL_CENTER);
+ // Next sample
+ b = createNextButton();
+ nav.addComponent(b);
+ nav.setComponentAlignment(b, ExpandLayout.ALIGNMENT_LEFT,
+ ExpandLayout.ALIGNMENT_VERTICAL_CENTER);
+
+ // Main left/right split; hidden menu tree
+ SplitPanel split = new SplitPanel(SplitPanel.ORIENTATION_HORIZONTAL);
split.setSizeFull();
split.setSplitPosition(0, SplitPanel.UNITS_PIXELS);
- main.addComponent(split);
- main.expand(split);
+ mainExpand.addComponent(split);
+ mainExpand.expand(split);
+
+ // Menu tree, initially hidden
+ Tree tree = createMenuTree();
+ split.addComponent(tree);
+
+ // Main Area
+ split.addComponent(mainArea);
+
+ // List/grid/coverflow
+ Component mode = createModeSwitch();
+ nav.addComponent(mode);
+ nav.setComponentAlignment(mode, ExpandLayout.ALIGNMENT_RIGHT,
+ ExpandLayout.ALIGNMENT_VERTICAL_CENTER);
+
+ }
+
+ /**
+ * Displays a Feature(Set)
+ *
+ * @param f
+ * the Feature(Set) to show
+ */
+ public void setFeature(Feature f) {
+ currentFeature.setValue(f);
+ }
+
+ /**
+ * Displays a Feature(Set) matching the given path, or the main view if
+ * no matching Feature(Set) is found.
+ *
+ * @param path
+ * the path of the Feature(Set) to show
+ */
+ public void setFeature(String path) {
+ Feature f = features.getFeatureByPath(path);
+ setFeature(f);
+ }
+ // Handle REST -style urls
+ public DownloadStream handleURI(URL context, String relativeUri) {
+ Feature f = features.getFeatureByPath(relativeUri);
+ if (f != null) {
+ setFeature(f);
+ open(new ExternalResource(context));
+ }
+ return super.handleURI(context, relativeUri);
+ }
+
+ /*
+ * SamplerWindow helpers
+ */
+
+ private Component createLogo() {
Button logo = new Button("", new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
currentFeature.setValue(null);
}
});
- logo.setDescription("Home");
+ logo.setDescription("↶ Home");
logo.setStyleName(Button.STYLE_LINK);
logo.setIcon(new ThemeResource("sampler/logo.png"));
logo.setWidth("160px");
- nav.addComponent(logo);
- nav.setComponentAlignment(logo, ExpandLayout.ALIGNMENT_LEFT,
- ExpandLayout.ALIGNMENT_VERTICAL_CENTER);
+ return logo;
+ }
+
+ private Button createNextButton() {
+ Button b = new Button("Next sample →", new ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ Object curr = currentFeature.getValue();
+ Object next = allFeatures.nextItemId(curr);
+ while (next != null && next instanceof FeatureSet) {
+ next = allFeatures.nextItemId(next);
+ }
+ currentFeature.setValue(next);
+ }
+ });
+ b.setStyleName(Button.STYLE_LINK);
+ return b;
+ }
- Button b = new Button("< Previous", new ClickListener() {
+ private Button createPrevButton() {
+ Button b = new Button("← Previous sample", new ClickListener() {
public void buttonClick(ClickEvent event) {
Object curr = currentFeature.getValue();
Object prev = allFeatures.prevItemId(curr);
@@ -130,37 +265,35 @@ public class SamplerApplication extends Application {
prev = allFeatures.prevItemId(prev);
}
currentFeature.setValue(prev);
-
}
});
- nav.addComponent(b);
- nav.setComponentAlignment(b, ExpandLayout.ALIGNMENT_LEFT,
- ExpandLayout.ALIGNMENT_VERTICAL_CENTER);
+ b.setStyleName(Button.STYLE_LINK);
+ return b;
+ }
- b = new Button("Next >", new ClickListener() {
- public void buttonClick(ClickEvent event) {
- Object curr = currentFeature.getValue();
- Object next = allFeatures.nextItemId(curr);
- while (next != null && next instanceof FeatureSet) {
- next = allFeatures.nextItemId(next);
+ private Component createModeSwitch() {
+ ModeSwitch m = new ModeSwitch();
+ m.addMode(currentList, "", "View as Icons", new ThemeResource(
+ "sampler/grid.gif"));
+ m.addMode(new FeatureGrid(), "", "View as Icons",
+ new ThemeResource("sampler/flow.gif"));
+ m.addMode(new FeatureTable(), "", "View as List",
+ new ThemeResource("sampler/list.gif"));
+ m.addListener(new ModeSwitch.ModeSwitchListener() {
+ public void componentEvent(Event event) {
+ if (event instanceof ModeSwitchEvent) {
+ updateFeatureList((FeatureList) ((ModeSwitchEvent) event)
+ .getMode());
}
- currentFeature.setValue(next);
-
}
});
- nav.addComponent(b);
- nav.setComponentAlignment(b, ExpandLayout.ALIGNMENT_LEFT,
- ExpandLayout.ALIGNMENT_VERTICAL_CENTER);
-
- b = new Button(":: | \\⊡/ | ≣");
- nav.addComponent(b);
- nav.expand(b);
- nav.setComponentAlignment(b, ExpandLayout.ALIGNMENT_RIGHT,
- ExpandLayout.ALIGNMENT_VERTICAL_CENTER);
+ m.setMode(currentList);
+ return m;
+ }
+ private Tree createMenuTree() {
Tree tree = new Tree();
tree.setImmediate(true);
- split.addComponent(tree);
tree.setContainerDataSource(allFeatures);
tree.setPropertyDataSource(currentFeature);
for (int i = 0; i < features.getFeatures().length; i++) {
@@ -169,42 +302,65 @@ public class SamplerApplication extends Application {
tree.expandItemsRecursively(features);
tree.addListener(new Table.ValueChangeListener() {
public void valueChange(ValueChangeEvent event) {
- Feature val = (Feature) event.getProperty().getValue();
- if (val == null) {
- currentList.setFeatureContainer(features
- .getContainer(true));
- if (currentList.getParent() != split) {
- split.replaceComponent(featureView, currentList);
- }
-
- } else if (val instanceof FeatureSet) {
- currentList.setFeatureContainer(((FeatureSet) val)
- .getContainer(false));
- if (currentList.getParent() != split) {
- split.replaceComponent(featureView, currentList);
- }
- } else {
- if (featureView.getParent() != split) {
- split.replaceComponent(currentList, featureView);
- }
- featureView.setFeature(val);
- }
+ updateFeatureList(currentList);
}
});
+ return tree;
+ }
- FeatureTable tbl = new FeatureTable();
- tbl.setFeatureContainer(allFeatures);
- currentList = tbl;
+ private void updateFeatureList(FeatureList list) {
+ currentList = list;
+ Feature val = (Feature) currentFeature.getValue();
+ if (val == null) {
+ currentList.setFeatureContainer(allFeatures);
+ mainArea.show(currentList);
+ } else if (val instanceof FeatureSet) {
+ currentList.setFeatureContainer(((FeatureSet) val)
+ .getContainer(true));
+ mainArea.show(currentList);
+ } else {
+ mainArea.show(featureView);
+ featureView.setFeature(val);
+ }
+
+ }
- split.addComponent(tbl);
+ }
- featureView = new FeatureView();
+ /**
+ * Main area used to show Feature of FeatureList. In effect a one-component
+ * container, to minimize repaints.
+ */
+ private class MainArea extends CustomComponent {
+ MainArea() {
+ setSizeFull();
+ setCompositionRoot(new Label());
+ }
- Feature f = features.getFeatureByPath("Components/c/DummyFeature");
- tree.setValue(f);
+ public void show(Component c) {
+ if (getCompositionRoot() != c) {
+ setCompositionRoot(c);
+ }
}
}
+ /**
+ * Components capable of listing Features should implement this.
+ *
+ */
+ interface FeatureList extends Component {
+ /**
+ * Shows the given Features
+ *
+ * @param c
+ * Container with Features to show.
+ */
+ public void setFeatureContainer(HierarchicalContainer c);
+ }
+
+ /**
+ * Table -mode FeatureList. Displays the features in a Table.
+ */
private class FeatureTable extends Table implements FeatureList {
FeatureTable() {
alwaysRecalculateColumnWidths = true;
@@ -229,14 +385,14 @@ public class SamplerApplication extends Application {
addGeneratedColumn("", new Table.ColumnGenerator() {
public Component generateCell(Table source, Object itemId,
Object columnId) {
+ final Feature feature = (Feature) itemId;
Button b = new Button(
- itemId instanceof FeatureSet ? "See samples ‣"
+ feature instanceof FeatureSet ? "See samples ‣"
: "See sample ‣");
- b.setData(itemId);
b.addListener(new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
- currentFeature
- .setValue(event.getButton().getData());
+ ((SamplerWindow) getWindow()).setFeature(feature);
+
}
});
b.setStyleName(Button.STYLE_LINK);
@@ -246,7 +402,7 @@ public class SamplerApplication extends Application {
});
}
- public void setFeatureContainer(Container c) {
+ public void setFeatureContainer(HierarchicalContainer c) {
setContainerDataSource(c);
setVisibleColumns(new Object[] { Feature.PROPERTY_ICON,
Feature.PROPERTY_NAME, Feature.PROPERTY_DESCRIPTION, "" });
@@ -257,16 +413,83 @@ public class SamplerApplication extends Application {
}
+ private class FeatureGrid extends GridLayout implements FeatureList {
+
+ FeatureGrid() {
+ super(5, 1);
+ setWidth("100%");
+ }
+
+ private void newRow() {
+ while (getCursorX() > 0) {
+ space();
+ }
+ }
+
+ @Override
+ public void setFeatureContainer(HierarchicalContainer c) {
+ removeAllComponents();
+ Collection features = c.getItemIds();
+ for (Iterator it = features.iterator(); it.hasNext();) {
+ final Feature f = (Feature) it.next();
+ if (f instanceof FeatureSet) {
+ newRow();
+ addComponent(new Label(f.getName()));
+ if (c.isRoot(f)) {
+ newRow();
+ }
+ } else {
+ Button b = new Button();
+ b.setWidth("130px");
+ b.setHeight("130px");
+ b.setSizeFull();
+ b.setStyleName(Button.STYLE_LINK);
+ b.setIcon(new ClassResource(f.getClass(), f.getIconName(),
+ SamplerApplication.this));
+ b.setDescription("<h3>" + f.getName() + "</h3>"
+ + f.getDescription());
+ b.addListener(new Button.ClickListener() {
+ public void buttonClick(ClickEvent event) {
+ ((SamplerWindow) getWindow()).setFeature(f);
+ }
+ });
+ addComponent(b);
+ }
+ }
+ }
+ }
+
+ /**
+ * A set of features.
+ */
static class FeatureSet extends Feature {
- String name;
+ private String pathname;
+
+ private String name;
- Feature[] content;
+ private String desc;
- HierarchicalContainer container = null;
+ private String icon = "FeatureSet.png";
- FeatureSet(String name, Feature[] content) {
+ private Feature[] content;
+
+ private HierarchicalContainer container = null;
+
+ private boolean containerRecursive = false;
+
+ FeatureSet(String pathname, Feature[] content) {
+ this(pathname, pathname, "", content);
+ }
+
+ FeatureSet(String pathname, String name, Feature[] content) {
+ this(pathname, name, "", content);
+ }
+
+ FeatureSet(String pathname, String name, String desc, Feature[] content) {
+ this.pathname = pathname;
this.name = name;
+ this.desc = desc;
this.content = content;
}
@@ -283,7 +506,7 @@ public class SamplerApplication extends Application {
f = null; // break while if no new found
String part = parts.remove(0);
for (int i = 0; i < fs.length; i++) {
- if (fs[i].getName().equals(part)) {
+ if (fs[i].getPathName().equals(part)) {
if (parts.isEmpty()) {
return fs[i];
} else if (fs[i] instanceof FeatureSet) {
@@ -298,8 +521,8 @@ public class SamplerApplication extends Application {
return null;
}
- Container.Hierarchical getContainer(boolean recurse) {
- if (container == null) {
+ HierarchicalContainer getContainer(boolean recurse) {
+ if (container == null || containerRecursive != recurse) {
container = new HierarchicalContainer();
container.addContainerProperty(PROPERTY_NAME, String.class, "");
container.addContainerProperty(PROPERTY_DESCRIPTION,
@@ -310,7 +533,7 @@ public class SamplerApplication extends Application {
return container;
}
- private void addFeatures(FeatureSet f, Container.Hierarchical c,
+ private void addFeatures(FeatureSet f, HierarchicalContainer c,
boolean recurse) {
Feature[] features = f.getFeatures();
for (int i = 0; i < features.length; i++) {
@@ -333,7 +556,12 @@ public class SamplerApplication extends Application {
@Override
public String getDescription() {
- return null;
+ return desc;
+ }
+
+ @Override
+ public String getPathName() {
+ return pathname;
}
@Override
@@ -343,13 +571,9 @@ public class SamplerApplication extends Application {
@Override
public String getIconName() {
- return "FeatureSet.png";
+ return icon;
}
}
- interface FeatureList extends Component {
- public void setFeatureContainer(Container c);
- }
-
}
diff --git a/src/com/itmill/toolkit/demo/sampler/features/DummyFeature.gif b/src/com/itmill/toolkit/demo/sampler/features/DummyFeature.gif
index fa6b38b4c9..ad9b6639a0 100644
--- a/src/com/itmill/toolkit/demo/sampler/features/DummyFeature.gif
+++ b/src/com/itmill/toolkit/demo/sampler/features/DummyFeature.gif
Binary files differ
diff --git a/src/com/itmill/toolkit/demo/sampler/features/DummyFeature.java b/src/com/itmill/toolkit/demo/sampler/features/DummyFeature.java
index 8e7e9e8895..60136709b1 100644
--- a/src/com/itmill/toolkit/demo/sampler/features/DummyFeature.java
+++ b/src/com/itmill/toolkit/demo/sampler/features/DummyFeature.java
@@ -4,6 +4,10 @@ import com.itmill.toolkit.demo.sampler.Feature;
public class DummyFeature extends Feature {
+ public String getName() {
+ return "A placeholder feature";
+ }
+
public String getDescription() {
return "A description";
}