All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.ui.UIFragment Maven / Gradle / Ivy

There is a newer version: 7.0.167
Show newest version
/*
 * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Codename One designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *  
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */
package com.codename1.ui;

import com.codename1.io.CharArrayReader;
import com.codename1.io.JSONParser;
import com.codename1.io.Log;

import com.codename1.io.Util;
import static com.codename1.ui.ComponentSelector.$;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.layouts.FlowLayout;
import com.codename1.ui.layouts.GridLayout;
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.layouts.Layout;
import com.codename1.ui.table.TableLayout;
import com.codename1.xml.Element;
import com.codename1.xml.XMLParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Vector;

/**
 * Encapsulates an XML or JSON template for a UI component hierarchy.  The UI can be defined
 * using XML.  The XML is compiled into a view at runtime.  Custom components may be
 * injected into the template using special placeholder tags (i.e. tags where the tag
 * name begins with '$'.
 * 
 * 

Supported Tags

* *
    *
  • border - A container with layout=BorderLayout
  • *
  • y - A container with layout=BoxLayout Y
  • *
  • x - A container with layout=BoxLayout X
  • *
  • flow - A container with layout=FlowLayout
  • *
  • layered - A container with layout=LayeredLayout
  • *
  • grid - A container with layout=GridLayout. Accepted attributes {@literal rows} and {@literal cols}
  • *
  • table - A container with layout=TableLayout. Accepted attributes {@literal rows} and {@literal cols}. May have zero or more nested {@literal } tags. *
  • label - A Label
  • *
  • button - A button
  • *
* *

Layout Variant Tags

* *

BorderLayout and BoxLayout include some variant tags to customize their behaviour also:

* *
    *
  • borderAbs, borderAbsolute - BorderLayout with center absolute behaviour. This is the same as <border behavior='absolute'/>
  • *
  • borderTotalBelow - BorderLayout with Total Below center behaviour. This is the same as <border behavior='totalBelow'/>
  • *
  • yBottomLast, ybl - BoxLayout with Y_BOTTOM_LAST setting.
  • *
  • xNoGrow, xng - BoxLayout X with No Grow option. This is the same as <x noGrow='true'/>
  • *
* *

Supported Attributes

* *
    *
  • uiid - The UIID of the component. I.e. {@link Component#getUIID() }
  • *
  • id - The ID of the component so that it can be retrieved using {@link #findById(java.lang.String) }
  • *
  • name - The name of the component (i.e. {@link Component#getName() }
  • *
  • constraint - The layout constraint used for adding to * the parent. Supports north, south, east, west, center, when parent is * border
  • *
  • rows - Used by grid only. Represents number of rows in * grid or table.
  • *
  • cols - Used by grid only. Represents number of columns * in grid or table.
  • *
  • behavior, behaviour - Used by Border Layout only. Specifies the center behaviour. Accepts values "scale", "absolute", and "totalBelow".
  • *
  • noGrow - Used by <x> only. Specifies that BoxLayout should be X_AXIS_NO_GROW. Accepts values "true" and "false".
  • *
  • bottomLast - Used by <y> only. Specifies that BoxLayout should use Y_AXIS_BOTTOM_LAST option. Accepts values "true" and "false"
  • *
* *

Example XML Notation

*
{@code
 * Form f = new Form("Test", new BorderLayout());
 * String tpl = ""
 *     + "<$button constraint='east'/>"
 *     + "<$search constraint='south'/>"
 *     + "";
 *
 * f.setFormBottomPaddingEditingMode(true);
 * TextField searchField = new TextField();
 * searchField.addActionListener(e->{
 *    Log.p("Search field action performed");
 * });
 * Button submit = new Button("Submit");
 * submit.addActionListener(e->{
 *     Log.p("Button action performed");
 * });
 *
 * UIFragment template = UIFragment.parseXML(tpl)
 *     .set("button", submit)
 *     .set("search", searchField);
 * f.add(BorderLayout.CENTER, template.getView());
 * f.show();
 * }
* *

JSON Notation

* *

When trying to express a UI structure inside a Java String, the XML notation may be * a little bit verbose and cumbersome. For this reason, there is an alternative JSON-based * notation that will allow you to express a UI structure more succinctly.

* *

A JSON object (i.e. curly braces) denotes a Container. If this object includes properties * corresponding to the constraints of {@link BorderLayout} (e.g. center, east, west, north, south, or overlay), then * the container will use a {@link BorderLayout}, and the associated properties will represent its children * with the appropriate constraint.

* *

E.g.:

*
{@code {center:'Center Label', south:'South Content'}}
*

Will create a Container with labels in its {@link BorderLayout#CENTER} and {@link BorderLayout#SOUTH} positions.

*

To make things even more succinct, it supports single-character property keys for the BorderLayout constraint values. E.g. The following * is equivalent to the previous example:

*
{@code {c:'Center Label', s:'South Content'}}
 * 
 * 

Other Layouts:

*
    *
  • Flow Layout - {@code {flow:[...]}}
  • *
  • Grid Layout - {@code {grid:[...], cols:3, rows:2}}
  • *
  • Box Layout X - {@code {x:[...]}}
  • *
  • Box Layout Y - {@code {y:[...]}}
  • *
  • Layered Layout - {@code {layered:[...]}}
  • *
  • Table Layout - {@code {table:[['A1', 'B1', 'C1'], ['A2', 'B2', 'C2'], ...]}}
  • *
* *

Layout Variants

* *

BoxLayout and BorderLayout include variant shorthands to customize their behaviour.

* *
    *
  • xNoGrow, xng - Same as {@code {x:[...], noGrow:true}}
  • *
  • yBottomLast, ybl - Same as {@code {y:[...], bottomLast:true}}
  • *
  • centerAbsolute, centerAbs, ca - Same as {@code {center:[...], behavior:absolute}}
  • *
  • centerTotalBelow, ctb - Same as {@code {center:[...], behavior:totalBelow}}
  • *
* *

Embedding Placeholders/Parameters

*

The notation for embedding placeholder components (which must be injected via {@link UIFragment#set(java.lang.String, com.codename1.ui.Component) }), * is similar to the XML equivalent. Just place the parmeter name, prefixed with '$'. E.g.

*
{@code $submitButton}
* * *

Example JSON Notation

* * *
{@code
 * Component view = UIFragment.parseJSON("{n:['Hello', 'World', $checkbox], c:[y, {class:'MyTable', table:[['Username', $username], ['Password', $password]]}, {flow:['Some text'], align:center}], s:$submit}")
 *     .set("username", new TextField())
 *     .set("password", new TextField())
 *     .set("submit", new Button("Submit"))
 *     .set("checkbox", new CheckBox("Check Me"))
 *     .getView();
 * }
* * @author shannah * @since 7.0 */ public class UIFragment { // The root element of this template private Element root; // The root view of the template private Container view; // The factory that creates components from XML elements private ComponentFactory factory = new DefaultComponentFactory(); // Index that stores any component which included an "id" attribute private Map index = new HashMap(); // Parameters for the template. Any tag with tag name startign with $ will // be replaced by a corresponding parameter private Map parameters = new HashMap(); /** * A factory for converting XML elements to Components. * @see #setFactory(com.codename1.ui.UIFragment.ComponentFactory) * @see #getFactory() */ public static interface ComponentFactory { /** * Creates a new component given its XML description. * @param el The XML element * @return A Component. */ public Component newComponent(Element el); /** * Creates a layout constraint for adding a child component to a parent component. * @param parent The parent component. * @param parentEl The XML element for the parent component. * @param child The child component. * @param childEl The XML element for the child component. * @return Layout constraint for adding to the parent component. */ public Object newConstraint(Container parent, Element parentEl, Component child, Element childEl); } /** * Default component factory that is used in templates. Supports the following tags: *
    *
  • y
  • *
  • x
  • *
  • flow
  • *
  • layered
  • *
  • border
  • *
  • table
  • *
  • label
  • *
  • button
  • *
*/ public static class DefaultComponentFactory implements ComponentFactory { private static int centerBehaviour(String behaviour) { if ("scale".equalsIgnoreCase(behaviour)) { return BorderLayout.CENTER_BEHAVIOR_SCALE; } if ("absolute".equalsIgnoreCase(behaviour)) { return BorderLayout.CENTER_BEHAVIOR_CENTER_ABSOLUTE; } if ("center".equalsIgnoreCase(behaviour)) { return BorderLayout.CENTER_BEHAVIOR_CENTER; } if ("totalBelow".equalsIgnoreCase(behaviour)) { return BorderLayout.CENTER_BEHAVIOR_TOTAL_BELOW; } return BorderLayout.CENTER_BEHAVIOR_SCALE; } private static boolean grow(String grow) { if ("true".equalsIgnoreCase(grow)) { return true; } else { return false; } } private static int align(String align) { if ("left".equals(align)) { return Component.LEFT; } if ("right".equals(align)){ return Component.RIGHT; } if ("center".equals(align)) { return Component.CENTER; } return 0; } private static int valign(String valign) { if ("top".equals(valign)) { return Component.TOP; } if ("center".equals(valign)) { return Component.CENTER; } if ("bottom".equals(valign)) { return Component.BOTTOM; } return 0; } private static boolean empty(Element el, String attName) { String val = el.getAttribute(attName); return val == null || val.length() == 0; } @Override public Component newComponent(Element el) { String name = el.getTagName().toLowerCase(); if (name.startsWith("border")) { BorderLayout l = new BorderLayout(); if (name.startsWith("borderabs")) { l.setCenterBehavior(BorderLayout.CENTER_BEHAVIOR_CENTER_ABSOLUTE); } else if (name.startsWith("borderscale")) { l.setCenterBehavior(BorderLayout.CENTER_BEHAVIOR_SCALE); } else if (name.startsWith("bordertotalbelow")) { l.setCenterBehavior(BorderLayout.CENTER_BEHAVIOR_TOTAL_BELOW); } else { if (!empty(el, "behaviour")) { l.setCenterBehavior(centerBehaviour(el.getAttribute("behaviour"))); } else if (!empty(el, "behavior")) { l.setCenterBehavior(centerBehaviour(el.getAttribute("behavior"))); } } return new Container(l); } if (name.startsWith("y")) { BoxLayout l; if (name.startsWith("ybottom") || name.equals("ybl")) { l = new BoxLayout(BoxLayout.Y_AXIS_BOTTOM_LAST); } else { if ("true".equalsIgnoreCase(el.getAttribute("bottomLast"))) { l = new BoxLayout(BoxLayout.Y_AXIS_BOTTOM_LAST); } else { l = new BoxLayout(BoxLayout.Y_AXIS); } } return new Container(l); } if (name.startsWith("x")) { BoxLayout l; if (name.startsWith("xnogrow") || name.equals("xng")) { l = new BoxLayout(BoxLayout.X_AXIS_NO_GROW); } else { if ("true".equalsIgnoreCase(el.getAttribute("noGrow")) || "false".equalsIgnoreCase(el.getAttribute("grow"))) { l = new BoxLayout(BoxLayout.X_AXIS_NO_GROW); } else { l = new BoxLayout(BoxLayout.X_AXIS); } } return new Container(l); } if (name.equals("flow")) { FlowLayout fl = new FlowLayout(); String align = el.getAttribute("align"); String valign = el.getAttribute("valign"); if (align != null && align.length() > 0) { fl.setAlign(align(align)); } if (valign != null && valign.length()> 0) { fl.setValign(valign(valign)); } return new Container(fl); } if (name.equals("grid")) { String colsStr = el.getAttribute("cols"); int cols; try { cols = Integer.parseInt(colsStr); } catch (Throwable t) { throw new RuntimeException("grid requires cols attribute."); } String rowsStr = el.getAttribute("rows"); int rows=-1; try { rows = Integer.parseInt(rowsStr); } catch (Throwable t){ rows = (int)Math.ceil(el.getNumChildren()/(double)cols); if (rows == 0) { rows = 1; } } return new Container(new GridLayout(rows, cols)); } if (name.equals("table")) { String colsStr = el.getAttribute("cols"); String rowsStr = el.getAttribute("rows"); int rows = -1; int cols = -1; if (colsStr != null && colsStr.length() > 0) { cols = Integer.parseInt(colsStr); } if (rowsStr != null && rowsStr.length() > 0) { rows = Integer.parseInt(rowsStr); } Vector children = el.getChildrenByTagName("tr"); if (rows < 0) { rows = children.size(); } if (cols < 0) { if (children.size() > 0) { Element firstRow = (Element)children.get(0); Vector firstRowCols = firstRow.getChildrenByTagName("td"); cols = firstRowCols.size(); } } rows = Math.max(1, rows); cols = Math.max(1, cols); TableLayout tl = new TableLayout(rows, cols); return new Container(tl); } if (name.equals("layered")) { return new Container(new LayeredLayout()); } if (name.equals("label")) { Element textEl = el.getNumChildren() > 0 ? el.getChildAt(0) : null; String text = textEl != null ? textEl.getText() : ""; return new Label(text); } if (name.equals("button")) { Element textEl = el.getNumChildren() > 0 ? el.getChildAt(0) : null; String text = textEl != null ? textEl.getText() : ""; return new Button(text); } throw new IllegalArgumentException("Unsupported element "+name); } @Override public Object newConstraint(Container parent, Element parentEl, Component child, Element childEl) { Layout l = parent.getLayout(); if (l instanceof BorderLayout) { String cnst = childEl.getAttribute("constraint"); if (cnst == null) { return BorderLayout.CENTER; } cnst = cnst.toLowerCase(); if ("north".equals(cnst)) { return BorderLayout.NORTH; } if ("south".equals(cnst)) { return BorderLayout.SOUTH; } if ("east".equals(cnst)) { return BorderLayout.EAST; } if ("west".equals(cnst)) { return BorderLayout.WEST; } if ("center".equals(cnst)) { return BorderLayout.CENTER; } if ("overlay".equals(cnst)) { return BorderLayout.OVERLAY; } throw new IllegalArgumentException("Unsupported constraint "+cnst); } return null; } } /** * Parses input stream of XML into a Template * @param input InputStream with XML content to parse * @return The corresponding template, or a RuntimeException if parsing failed. */ public static UIFragment parseXML(InputStream input) { try { XMLParser p = new XMLParser(); Element el = p.parse(new InputStreamReader(input)); return new UIFragment(el); } catch (Exception ex) { Log.e(ex); throw new RuntimeException(ex.getMessage()); } } /** * Parses XML string into a Template * @param xml XML string describing a UI. * @return The corresponding template, or a RuntimeException if parsing failed. */ public static UIFragment parseXML(String xml) { try { XMLParser p = new XMLParser(); p.setCaseSensitive(true); Element el = p.parse(new CharArrayReader(xml.toCharArray())); return new UIFragment(el); } catch (Exception ex) { Log.e(ex); throw new RuntimeException(ex.getMessage()); } } /** * Parses a JSON string into a template. * @param json A JSON string representing a UI hierarchy. * @return * @throws IOException */ public static UIFragment parseJSON(String json) { try { Element el = UINotationParser.parseJSONNotation(json); return new UIFragment(el); } catch (Exception ex) { Log.e(ex); throw new RuntimeException(ex.getMessage()); } } private UIFragment(Element el) { this.root = el; } /** * Gets the view that is generated by this template. * @return */ public Container getView() { if (view == null) { view = (Container)getFactory().newComponent(root); decorate(root, view); build(root, view); } return view; } private void decorate(Element el, Component cmp) { String classAttr = el.getAttribute("class"); if (classAttr != null && classAttr.length() > 0) { String[] tags = Util.split(classAttr, " "); $(cmp).addTags(tags); } String uiid = el.getAttribute("uiid"); if (uiid != null && uiid.length() > 0){ cmp.setUIID(uiid); } String id = el.getAttribute("id"); if (id != null && id.length() > 0) { index.put(id, cmp); } String name = el.getAttribute("name"); if (name != null && name.length() > 0) { cmp.setName(name); } String flags = el.getAttribute("flags"); if (flags != null && flags.indexOf("safeArea") >= 0 && (cmp instanceof Container)) { ((Container)cmp).setSafeArea(true); } } private List getChildren(Element el) { String tagName = el.getTagName(); if ("table".equals(tagName)) { List out = new ArrayList(); for (Object row : el.getChildrenByTagName("tr")) { Element erow = (Element)row; for (Object cell : erow.getChildrenByTagName("td")) { Element ecell = (Element)cell; if (ecell.getNumChildren() > 0) { out.add(ecell.getChildAt(0)); } } } return out; } else { List out = new ArrayList(); int len = el.getNumChildren(); for (int i=0; i children = getChildren(model); int len = children.size(); for (int i=0; i 0) { Object first = l.get(0); if (first instanceof String) { String s = (String)first; if ("x".equals(first)) { el = new Element("x"); } else if ("y".equals(first)) { el = new Element("y"); } else if ("grid".equals(first)) { el = new Element("grid"); } else if ("layered".equals(first)) { el = new Element("layered"); } else { el = new Element("flow"); } if (!inArray(s, new String[]{"x", "y", "grid", "flow", "layered"})) { el.addChild(buildXMLFromJSONNotation(first)); } } else { el.addChild(buildXMLFromJSONNotation(first)); } for (int i=1; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy