From 53bf328f9cd53ff40faa99eb383e6ef0c4117498 Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Fri, 28 Aug 2009 13:38:54 +0000 Subject: [PATCH] initial version of CssLayout (aka FlowLayout from FastLayouts) + sampler example svn changeset:8579/svn branch:6.1 --- src/com/vaadin/demo/sampler/FeatureSet.java | 2 + .../sampler/features/layouts/CssLayouts.java | 49 ++++ .../features/layouts/CssLayoutsExample.java | 91 +++++++ .../terminal/gwt/client/DefaultWidgetSet.java | 5 + .../terminal/gwt/client/ui/VCssLayout.java | 239 ++++++++++++++++++ src/com/vaadin/ui/CssLayout.java | 225 +++++++++++++++++ 6 files changed, 611 insertions(+) create mode 100644 src/com/vaadin/demo/sampler/features/layouts/CssLayouts.java create mode 100644 src/com/vaadin/demo/sampler/features/layouts/CssLayoutsExample.java create mode 100644 src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java create mode 100644 src/com/vaadin/ui/CssLayout.java diff --git a/src/com/vaadin/demo/sampler/FeatureSet.java b/src/com/vaadin/demo/sampler/FeatureSet.java index a20bbaba92..8936d450dc 100644 --- a/src/com/vaadin/demo/sampler/FeatureSet.java +++ b/src/com/vaadin/demo/sampler/FeatureSet.java @@ -22,6 +22,7 @@ import com.vaadin.demo.sampler.features.dates.DatePopup; import com.vaadin.demo.sampler.features.dates.DateResolution; import com.vaadin.demo.sampler.features.form.FormBasic; import com.vaadin.demo.sampler.features.layouts.ApplicationLayout; +import com.vaadin.demo.sampler.features.layouts.CssLayouts; import com.vaadin.demo.sampler.features.layouts.CustomLayouts; import com.vaadin.demo.sampler.features.layouts.ExpandingComponent; import com.vaadin.demo.sampler.features.layouts.GridLayoutBasic; @@ -247,6 +248,7 @@ public class FeatureSet extends Feature { new ApplicationLayout(), // new WebLayout(), // new CustomLayouts(), // + new CssLayouts(),// }); } } diff --git a/src/com/vaadin/demo/sampler/features/layouts/CssLayouts.java b/src/com/vaadin/demo/sampler/features/layouts/CssLayouts.java new file mode 100644 index 0000000000..04b2181b83 --- /dev/null +++ b/src/com/vaadin/demo/sampler/features/layouts/CssLayouts.java @@ -0,0 +1,49 @@ +package com.vaadin.demo.sampler.features.layouts; + +import com.vaadin.demo.sampler.APIResource; +import com.vaadin.demo.sampler.Feature; +import com.vaadin.demo.sampler.NamedExternalResource; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.VerticalLayout; + +@SuppressWarnings("serial") +public class CssLayouts extends Feature { + + @Override + public String getName() { + return "Css layout"; + } + + @Override + public String getDescription() { + // TODO + return "Most commonly developers usign Vaadin don't want to think " + + "of the browser environment at all. With the flexible " + + "layout API found from grid, horizontal and vertical " + + "layout developers can build almost anything with plain " + + "Java. But sometimes experienced web developers miss " + + "flexibility of CSS and HTML. CssLayout is a simple " + + "layout that puts contained componets into div element. " + + "It has a simple DOM structure and it leaves all the power " + + "to CSS designer hands. Having a very narrow feature set" + + ", CssLayout is also the fastest layout to render in " + + "Vaadin."; + } + + @Override + public APIResource[] getRelatedAPI() { + return new APIResource[] { new APIResource(HorizontalLayout.class), + new APIResource(VerticalLayout.class) }; + } + + @SuppressWarnings("unchecked") + @Override + public Class[] getRelatedFeatures() { + return new Class[] { ApplicationLayout.class, CustomLayouts.class }; + } + + @Override + public NamedExternalResource[] getRelatedResources() { + return null; + } +} diff --git a/src/com/vaadin/demo/sampler/features/layouts/CssLayoutsExample.java b/src/com/vaadin/demo/sampler/features/layouts/CssLayoutsExample.java new file mode 100644 index 0000000000..08077e4888 --- /dev/null +++ b/src/com/vaadin/demo/sampler/features/layouts/CssLayoutsExample.java @@ -0,0 +1,91 @@ +package com.vaadin.demo.sampler.features.layouts; + +import com.vaadin.ui.Component; +import com.vaadin.ui.CssLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.Panel; +import com.vaadin.ui.VerticalLayout; + +@SuppressWarnings("serial") +public class CssLayoutsExample extends VerticalLayout { + + public CssLayoutsExample() { + setMargin(true); + + /* + * Note that adding inline style like this is a very bad programming + * habit in common. The correct place for css is in theme. We do it here + * to keep css in java code for demonstration purposes. + */ + Label demostyle = new Label( + "", Label.CONTENT_XHTML); + + addComponent(demostyle); + + final Panel panel = new Panel("Panel"); + panel.setStyleName("floatedpanel"); + panel.setWidth("30%"); + panel.setHeight("370px"); + panel.addComponent(new Label("This panel is 30% width " + + "and 370px height(defined on server side) " + + "and floated right (with custom css). " + + "Try resizesing browser window to see " + + "how black boxes (floated to left) " + + "behave. Every third of them has red " + + "color to demonstrate dynamic css injection. ")); + + final Label bottomCenter = new Label( + "I'm 3 inches wide footer at the bottom " + + "of the layout (and centered unless I'm in IE6)"); + bottomCenter.setSizeUndefined(); // disable 100% default width + bottomCenter.setStyleName("footer"); + // bottomCenter.setWidth("50px"); + + CssLayout cssLayout = new CssLayout() { + int brickCounter = 0; + + @Override + protected String getCss(Component c) { + // colorize every third rendered brick + if (c instanceof Brick) { + brickCounter++; + if (brickCounter % 3 == 0) { + // make every third brick red and bold + return "color: #ff6611; font-weight:bold;"; + } + } + return null; + } + }; + + cssLayout.setWidth("100%"); + + cssLayout.addComponent(panel); + for (int i = 0; i < 15; i++) { + // add black labels that float left + cssLayout.addComponent(new Brick()); + } + cssLayout.addComponent(bottomCenter); + + addComponent(cssLayout); + } + + /** + * A simple label containing text "Brick" and themed black square. + */ + static class Brick extends Label { + public Brick() { + super("Brick"); + // disable 100% that label has by default + setSizeUndefined(); + setStyleName("brick"); + } + } + +} \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/DefaultWidgetSet.java b/src/com/vaadin/terminal/gwt/client/DefaultWidgetSet.java index f72859dbfb..4c3f9c83d1 100644 --- a/src/com/vaadin/terminal/gwt/client/DefaultWidgetSet.java +++ b/src/com/vaadin/terminal/gwt/client/DefaultWidgetSet.java @@ -14,6 +14,7 @@ import com.vaadin.terminal.gwt.client.ui.VCustomLayout; import com.vaadin.terminal.gwt.client.ui.VDateFieldCalendar; import com.vaadin.terminal.gwt.client.ui.VEmbedded; import com.vaadin.terminal.gwt.client.ui.VFilterSelect; +import com.vaadin.terminal.gwt.client.ui.VCssLayout; import com.vaadin.terminal.gwt.client.ui.VForm; import com.vaadin.terminal.gwt.client.ui.VFormLayout; import com.vaadin.terminal.gwt.client.ui.VGridLayout; @@ -145,6 +146,8 @@ public class DefaultWidgetSet implements WidgetSet { return new VUriFragmentUtility(); } else if (VAbsoluteLayout.class == classType) { return new VAbsoluteLayout(); + } else if (VCssLayout.class == classType) { + return new VCssLayout(); } return new VUnknownComponent(); @@ -259,6 +262,8 @@ public class DefaultWidgetSet implements WidgetSet { return VUriFragmentUtility.class; } else if (VAbsoluteLayout.TAGNAME.equals(tag)) { return VAbsoluteLayout.class; + } else if (VCssLayout.TAGNAME.equals(tag)) { + return VCssLayout.class; } return VUnknownComponent.class; diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java new file mode 100644 index 0000000000..875d562a5b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java @@ -0,0 +1,239 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; + +import com.google.gwt.dom.client.Style; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Container; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.RenderSpace; +import com.vaadin.terminal.gwt.client.StyleConstants; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ValueMap; + +public class VCssLayout extends SimplePanel implements Paintable, Container { + public static final String TAGNAME = "csslayout"; + public static final String CLASSNAME = "v-" + TAGNAME; + + private FlowPane panel = new FlowPane(); + + private Element margin = DOM.createDiv(); + + private boolean hasHeight; + private boolean hasWidth; + + public VCssLayout() { + super(); + DOM.appendChild(getElement(), margin); + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + setStyleName(CLASSNAME); + setWidget(panel); + } + + @Override + protected Element getContainerElement() { + return margin; + } + + @Override + public void setWidth(String width) { + super.setWidth(width); + panel.setWidth(width); + hasWidth = width != null && !width.equals(""); + } + + @Override + public void setHeight(String height) { + super.setHeight(height); + panel.setHeight(height); + hasHeight = height != null && !height.equals(""); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + if (client.updateComponent(this, uidl, true)) { + return; + } + + final VMarginInfo margins = new VMarginInfo(uidl + .getIntAttribute("margins")); + + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP, + margins.hasTop()); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT, + margins.hasRight()); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM, + margins.hasBottom()); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT, + margins.hasLeft()); + + setStyleName(margin, CLASSNAME + "-" + "spacing", uidl + .hasAttribute("spacing")); + panel.updateFromUIDL(uidl, client); + } + + public boolean hasChildComponent(Widget component) { + return panel.hasChildComponent(component); + } + + public void replaceChildComponent(Widget oldComponent, Widget newComponent) { + panel.replaceChildComponent(oldComponent, newComponent); + } + + public void updateCaption(Paintable component, UIDL uidl) { + panel.updateCaption(component, uidl); + } + + public class FlowPane extends FlowPanel { + + private final HashMap widgetToCaption = new HashMap(); + private ApplicationConnection client; + + public FlowPane() { + super(); + setStyleName(CLASSNAME + "-grid"); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + // for later requests + this.client = client; + + final ArrayList oldWidgets = new ArrayList(); + for (final Iterator iterator = iterator(); iterator + .hasNext();) { + oldWidgets.add(iterator.next()); + } + clear(); + + ValueMap mapAttribute = null; + if (uidl.hasAttribute("css")) { + mapAttribute = uidl.getMapAttribute("css"); + } + + for (final Iterator i = uidl.getChildIterator(); i + .hasNext();) { + final UIDL r = (UIDL) i.next(); + final Paintable child = client.getPaintable(r); + if (oldWidgets.contains(child)) { + oldWidgets.remove(child); + } + + add((Widget) child); + if (mapAttribute != null && mapAttribute.containsKey(r.getId())) { + String css = null; + try { + Style style = ((Widget) child).getElement().getStyle(); + css = mapAttribute.getString(r.getId()); + String[] cssRules = css.split(";"); + for (int j = 0; j < cssRules.length; j++) { + String[] rule = cssRules[j].split(":"); + if (rule.length == 0) { + continue; + } else { + style.setProperty( + makeCamelCase(rule[0].trim()), rule[1] + .trim()); + } + } + } catch (Exception e) { + ApplicationConnection.getConsole().log( + "CssLayout encounterd invalid css string: " + + css); + } + } + + if (!r.getBooleanAttribute("cached")) { + child.updateFromUIDL(r, client); + } + } + + // loop oldWidgetWrappers that where not re-attached and unregister + // them + for (final Iterator it = oldWidgets.iterator(); it + .hasNext();) { + final Paintable w = (Paintable) it.next(); + client.unregisterPaintable(w); + widgetToCaption.remove(w); + } + } + + public boolean hasChildComponent(Widget component) { + return component.getParent() == this; + } + + public void replaceChildComponent(Widget oldComponent, + Widget newComponent) { + VCaption caption = widgetToCaption.get(oldComponent); + if (caption != null) { + remove(caption); + widgetToCaption.remove(oldComponent); + } + int index = getWidgetIndex(oldComponent); + if (index >= 0) { + remove(oldComponent); + insert(newComponent, index); + } + } + + public void updateCaption(Paintable component, UIDL uidl) { + VCaption caption = widgetToCaption.get(component); + if (VCaption.isNeeded(uidl)) { + Widget widget = (Widget) component; + if (caption == null) { + caption = new VCaption(component, client); + widgetToCaption.put(widget, caption); + insert(caption, getWidgetIndex(widget)); + } else if (!caption.isAttached()) { + insert(caption, getWidgetIndex(widget)); + } + caption.updateCaption(uidl); + } else if (caption != null) { + remove(caption); + widgetToCaption.remove(component); + } + } + } + + public RenderSpace getAllocatedSpace(Widget child) { + com.google.gwt.dom.client.Element div = child.getElement() + .getParentElement(); + return new RenderSpace(div.getOffsetWidth(), div.getOffsetHeight()); + } + + public boolean requestLayout(Set children) { + if (hasSize()) { + return false; + } else { + return true; + } + } + + private boolean hasSize() { + return hasWidth || hasHeight; + } + + private static final String makeCamelCase(String cssProperty) { + // TODO this might be cleaner to implement with regexp + while (cssProperty.contains("-")) { + int indexOf = cssProperty.indexOf("-"); + cssProperty = cssProperty.substring(0, indexOf) + + String.valueOf(cssProperty.charAt(indexOf + 1)) + .toUpperCase() + cssProperty.substring(indexOf + 2); + } + return cssProperty; + } +} diff --git a/src/com/vaadin/ui/CssLayout.java b/src/com/vaadin/ui/CssLayout.java new file mode 100644 index 0000000000..65cfa36b28 --- /dev/null +++ b/src/com/vaadin/ui/CssLayout.java @@ -0,0 +1,225 @@ +package com.vaadin.ui; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; + +import com.vaadin.terminal.PaintException; +import com.vaadin.terminal.PaintTarget; +import com.vaadin.terminal.Paintable; +import com.vaadin.terminal.gwt.client.ui.VCssLayout; + +/** + * CssLayout is a layout component that can be used in browser environment only. + * It simply renders components and their captions into a same div element. + * Component layout can then be adjusted with css. + *

+ * In comparison to {@link HorizontalLayout} and {@link VerticalLayout} + *

    + *
  • rather similar server side api + *
  • no spacing, alignment or expand ratios + *
  • much simpler DOM that can be styled by skilled web developer + *
  • no abstraction of browser differences (developer must ensure that the + * result works properly on each browser) + *
  • different kind of handling for relative sizes (that are set from server + * side) (*) + *
  • noticeable faster rendering time in some situations as we rely more on + * browsers rendering engine. + *
+ *

+ * With {@link CustomLayout} one can often achieve similar results (good looking + * layouts with web technologies), but with CustomLayout developer needs to work + * with fixed templates. + *

+ * By extending CssLayout one can also inject some css rules straight to child + * components using {@link #getCss(Component)}. + * + *

+ * (*) Relative sizes (set from server side) are treated bit differently than in + * other layouts in Vaadin. In cssLayout the size is calculated relatively to + * CSS layouts content area which is pretty much as in html and css. In other + * layouts the size of component is calculated relatively to the "slot" given by + * layout. + *

+ * Also note that client side framework in Vaadin modifies inline style + * properties width and height. This happens on each update to component. If one + * wants to set component sizes with CSS, component must have undefined size on + * server side (which is not the default for all components) and the size must + * be defined with class styles - not by directly injecting width and height. + * + * @since 6.1 brought in from "FastLayouts" incubator project + * + */ +public class CssLayout extends AbstractLayout { + + private static final long serialVersionUID = -6408703812053460073L; + + @Override + public String getTag() { + return VCssLayout.TAGNAME; + } + + /** + * Custom layout slots containing the components. + */ + protected LinkedList components = new LinkedList(); + + /** + * Add a component into this container. The component is added to the right + * or under the previous component. + * + * @param c + * the component to be added. + */ + @Override + public void addComponent(Component c) { + super.addComponent(c); + components.add(c); + requestRepaint(); + } + + /** + * Adds a component into this container. The component is added to the left + * or on top of the other components. + * + * @param c + * the component to be added. + */ + public void addComponentAsFirst(Component c) { + super.addComponent(c); + components.addFirst(c); + requestRepaint(); + } + + /** + * Adds a component into indexed position in this container. + * + * @param c + * the component to be added. + * @param index + * the Index of the component position. The components currently + * in and after the position are shifted forwards. + */ + public void addComponent(Component c, int index) { + super.addComponent(c); + components.add(index, c); + requestRepaint(); + } + + /** + * Removes the component from this container. + * + * @param c + * the component to be removed. + */ + @Override + public void removeComponent(Component c) { + super.removeComponent(c); + components.remove(c); + requestRepaint(); + } + + /** + * Gets the component container iterator for going trough all the components + * in the container. + * + * @return the Iterator of the components inside the container. + */ + public Iterator getComponentIterator() { + return components.iterator(); + } + + /** + * Paints the content of this component. + * + * @param target + * the Paint Event. + * @throws PaintException + * if the paint operation failed. + */ + @Override + public void paintContent(PaintTarget target) throws PaintException { + super.paintContent(target); + HashMap componentCss = null; + // Adds all items in all the locations + for (Component c : components) { + // Paint child component UIDL + c.paint(target); + String componentCssString = getCss(c); + if (componentCssString != null) { + if (componentCss == null) { + componentCss = new HashMap(); + } + componentCss.put(c, componentCssString); + } + } + if (componentCss != null) { + target.addAttribute("css", componentCss); + } + } + + /** + * Returns styles to be applied to given component. Override this method to + * inject custom style rules to components. + * + *

+ * Note that styles are injected over previous styles before actual child + * rendering. Previous styles are not cleared, but overridden. + * + *

+ * Note that one most often achieves better code style, by separating + * styling to theme (with custom theme and {@link #addStyleName(String)}. + * With own custom styles it is also very easy to break browser + * compatibility. + * + * @param c + * the component + * @return css rules to be applied to component + */ + protected String getCss(Component c) { + return null; + } + + /* Documented in superclass */ + public void replaceComponent(Component oldComponent, Component newComponent) { + + // Gets the locations + int oldLocation = -1; + int newLocation = -1; + int location = 0; + for (final Iterator i = components.iterator(); i.hasNext();) { + final Component component = (Component) i.next(); + + if (component == oldComponent) { + oldLocation = location; + } + if (component == newComponent) { + newLocation = location; + } + + location++; + } + + if (oldLocation == -1) { + addComponent(newComponent); + } else if (newLocation == -1) { + removeComponent(oldComponent); + addComponent(newComponent, oldLocation); + } else { + if (oldLocation > newLocation) { + components.remove(oldComponent); + components.add(newLocation, oldComponent); + components.remove(newComponent); + components.add(oldLocation, newComponent); + } else { + components.remove(newComponent); + components.add(oldLocation, newComponent); + components.remove(oldComponent); + components.add(newLocation, oldComponent); + } + + requestRepaint(); + } + } + +} -- 2.39.5