com.codename1.ui.UIFragment Maven / Gradle / Ivy
/*
* 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