== The Components of Codename One This chapter covers the components of Codename One. Not all components are covered, but it tries to go deeper than the https://www.codenameone.com/javadoc/[JavaDocs]. === Container The Codename One container is a base class for many high level components; a container is a component that can contain other components. Every component has a parent container that can be null if it isn’t within a container at the moment or is a top-level container. A container can have many children. .Component-Container relationship expressed as UML image::img/developer-guide/component-uml.png[Component-Container relationship expressed as UML,scaledwidth=50%] Components are arranged in containers using layout managers which are algorithms that determine the arrangement of components within the container. // HTML_ONLY_START You can read more about layout managers in the https://www.codenameone.com/manual/basics.html#component-container-hierarchy[basics section]. // HTML_ONLY_END //// //PDF_ONLY You can read more about layout managers in the <>. //// ==== Composite Components Codename One components share a very generic hierarchy of inheritance e.g. https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] derives from https://www.codenameone.com/javadoc/com/codename1/ui/Label.html[Label] and thus receives all its abilities. However, some components are composites and derive from the https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container] class. E.g. the https://www.codenameone.com/javadoc/com/codename1/components/MultiButton.html[MultiButton] is a composite button that derives from `Container` but acts/looks like a `Button`. Normally this is pretty seamless for the developer, with a few things to keep in mind. - You should not use the `Container` derived methods on such a composite component (e.g. `add`/`remove` etc.). - You can’t cast it to the type that it relates to e.g. you can’t cast `MultiButton` to `Button`. - Events might be more nuanced. E.g. if you rely on https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionEvent.html#getSource--[ActionEvent.getSource()] or https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionEvent.html#getComponent--[ActionEvent.getComponent()] notice that they might not behave the way you would expect. For a `MultiButton` they will return the underlying `Button`. To workaround this we have https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionEvent.html#getActualComponent--[ActionEvent.getActualComponent()]. [[lead-component-sidebar]] .Lead Component **** Codename One has a rather unique feature for creating composite components: "lead components". This feature effectively allows components like `MultiButton` to act as if they are a single component while really being comprised of multiple components. Lead components work by setting a single component within as the "leader" it determines the style state for all the components in the hierarchy so if we have a `Container` that is lead by a `Button` the button will determine if the selected/pressed state is returned for the entire container hierarchy. This creates a case where a single `Component` has multiple nested `UIID`'s e.g. `MultiButton` has `UIID`'s such as `MultiLine1` that can be customized via API's such as https://www.codenameone.com/javadoc/com/codename1/components/MultiButton.html#setUIIDLine1-java.lang.String-[setUIIDLine1]. The lead component also handles the events from a single source so clicking in one of the other components within the hierarchy will send the event to the leading `Button` resulting in action events that behave "oddly" (hence the need for `getActualComponent`); // HTML_ONLY_START You can learn more about lead components in https://www.codenameone.com/manual/misc-features.html#lead-component-section[here]. // HTML_ONLY_END //// //PDF_ONLY You can learn more about lead components in <>. //// **** === Form https://www.codenameone.com/javadoc/com/codename1/ui/Form.html[Form] is the top-level container of Codename One, `Form` derives from `Container` and is the element we “show”. Only one form can be visible at any given time. We can get the currently visible `Form` using the code: [source,java] ---- Form currentForm = Display.getInstance().getCurrent(); ---- A form is a unique container in the sense that it has a title, a content area and optionally a menu/menu bar area. When invoking methods such as `add`/`remove` on a form, you are in fact invoking something that maps to this: [source,java] ---- myForm.getContentPane().add(...); ---- `Form` is in effect just a https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container] that has a border layout, its north section is occupied by the title area and its south section by the optional menu bar. The center (which stretches) is the content pane. The content pane is where you place all your components. [[form-layout-layers-graphic]] .Form layout graphic image::img/developer-guide/perspective-form-layers.png[Form layout graphic,scaledwidth=20%] You can see that every `Form` has space allocated for the title area. If you don’t set the title it won’t show up (its size will be zero), but it will still be there. The same isn’t always true for the case of the menu bar, which can vary significantly. Effectively, the section that matters is the content pane, so the form tries to do the “right thing” by pretending to be the content pane. However, this isn’t always seamless and sometimes code needs to just invoke `getContentPane()` in order to work directly with the container. TIP: A good example for such a case is with layout animations. Animating the form might not produce the right results. When in doubt its pretty easy to just use `getContentPane` instead of working with the `Form` directly. As you can see from <>, `Form` has two layers that reside on top of the content pane/title. The first is the layered pane which allows you to place "always on top" components. The layered pane added implicitly when you invoke `getLayeredPane()`. NOTE: You still need to place components using layout managers in order to get them to appear in the right place when using the layered pane. The second layer is the glass pane which allows you to draw arbitrary things on top of everything. The order in the image is indeed accurate: 1. `ContentPane` is lowest 2. `LayeredPane` is second 3. `GlassPane` is painted last NOTE: Its important to notice that a layered pane is on top of the `ContentPane` only and doesn't stretch to the title. A `GlassPane` usually stretches all the way but only with a "lightweight" title area e.g. the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar API]. The `GlassPane` allows developers to overlay UI on top of existing UI and paint as they see fit. This is useful for things that provide notification but don’t want to intrude with application functionality. NOTE: In earlier versions of Codename One (pre-3.6), `LayeredPane` & `GlassPane` didn't work with "native" peer components such as media, browser, native maps etc, because peer components were always rendered "in front" of the Codename One UI canvas. However, current versions now allow for proper layering of peer components and light-weight components so that LayeredPane and GlassPane can be used seamlessly with peer components. === Dialog A https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html[Dialog] is a special kind of `Form` that can occupy only a portion of the screen, it also has the additional functionality of the modal `show` method. When showing a dialog we have two basic options: modeless and modal: - Modal dialogs (the default) block the current EDT thread until the dialog is dismissed (to understand how they do it, read about `invokeAndBlock`). + Modal dialogs are an extremely useful way to prompt the user since the code can assume the user responded in the next line of execution. This promotes a linear & intuitive way of writing code. - Modless dialogs return immediately so a call to show such a dialog can't assume anything in the next line of execution. This is useful for features such as progress indicators where we aren't waiting for user input. E.g. a modal dialog can be expressed as such: [source,java] ---- if(Dialog.show("Click Yes Or No", "Select one", "Yes", "No")) { // user clicked yes } else { // user clicked no } ---- Notice that during the `show` call above the execution of the next line was "paused" until we got a response from the user and once the response was returned we could proceed directly. IMPORTANT: All usage of `Dialog` must be within the Event Dispatch Thread (the default thread of Codename One). This is especially true for modal dialogs. The `Dialog` class knows how to "block the EDT" without blocking it. // HTML_ONLY_START To learn more about `invokeAndBlock` which is the workhorse behind the modal dialog functionality check out https://www.codenameone.com/manual/edt.html[the EDT section]. // HTML_ONLY_END //// //PDF_ONLY To learn more about `invokeAndBlock` which is the workhorse behind the modal dialog functionality check out <>. //// The `Dialog` class contains multiple static helper methods to quickly show user notifications, but also allows a developer to create a `Dialog` instance, add information to its content pane and show the dialog. TIP: Dialogs contain a `ContentPane` just like `Form`. When showing a dialog in this way, you can either ask Codename One to position the dialog in a specific general location (taken from the https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BorderLayout.html[BorderLayout] concept for locations) or position it by spacing it (in pixels) from the 4 edges of the screen. E.g. you could do something like this to show a simple modal `Dialog`: [source,java] ---- Dialog d = new Dialog("Title"); d.setLayout(new BorderLayout()); d.add(BorderLayout.CENTER, new SpanLabel("Dialog Body", "DialogBody")); d.showPacked(BorderLayout.SOUTH, true); ---- .Custom modal Dialog in the south position image::img/developer-guide/components-dialog-modal-south.png[Custom Dialog in the south position,scaledwidth=20%] TIP: You can turn the code above to a modless `Dialog` by flipping the boolean `true` argument to `false`. We can position a `Dialog` absolutely by determining the space from the edges e.g. with this code we can occupy the bottom portion of the screen: [source,java] ---- Dialog d = new Dialog("Title"); d.setLayout(new BorderLayout()); d.add(BorderLayout.CENTER, new SpanLabel("Dialog Body", "DialogBody")); d.show(hi.getHeight() / 2, 0, 0, 0); ---- .Custom Dialog positioned absolutely image::img/developer-guide/components-dialog-modal-bottom-half.png[Custom Dialog positioned absolutely,scaledwidth=20%] NOTE: `hi` is the name of the parent `Form` in the sample above. ==== Styling Dialogs It's important to style a `Dialog` using https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html#getDialogStyle--[getDialogStyle()] or https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html#setDialogUIID-java.lang.String-[setDialogUIID] methods rather than styling the dialog object directly. The reason for this is that the `Dialog` is really a `Form` that takes up the whole screen. The `Form` that is visible behind the `Dialog` is rendered as a screenshot. So customizing the actual `UIID` of the `Dialog` won't produce the desired results. ==== Tint and Blurring By default a `Dialog` uses a platform specific tint color when it is showing e.g. notice the background in the image below is tinted: [source,java] ---- Form hi = new Form("Tint Dialog", new BoxLayout(BoxLayout.Y_AXIS)); Button showDialog = new Button("Tint"); showDialog.addActionListener((e) -> Dialog.show("Tint", "Is On....", "OK", null)); hi.add(showDialog); hi.show(); ---- .Dialog with tinted background image::img/developer-guide/components-dialog-tint.png[Dialog with tinted background,scaledwidth=20%] The tint color can be manipulated on the parent form, you can set it to any AARRGGBB value to set any color using the `setTintColor` method. Notice that this is invoked on the parent form and not on the `Dialog`! IMPORTANT: This is an AARRGGBB value and not an RRGGBB value! This means that 0 will be transparent. You can also manipulate this default value globally using the theme constant `tintColor`. The sample below tints the background in green: [source,java] ---- Form hi = new Form("Tint Dialog", new BoxLayout(BoxLayout.Y_AXIS)); hi.setTintColor(0x7700ff00); Button showDialog = new Button("Tint"); showDialog.addActionListener((e) -> Dialog.show("Tint", "Is On....", "OK", null)); hi.add(showDialog); hi.show(); ---- .Dialog with green tinted background image::img/developer-guide/components-dialog-green-tint.png[Dialog with green tinted background,scaledwidth=20%] We can apply Gaussian blur to the background of a dialog to highlight the foreground further and produce a very attractive effect. We can use the `setDefaultBlurBackgroundRadius` to apply this globally, we can use the theme constant `dialogBlurRadiusInt` to do the same or we can do this on a per `Dialog` basis using `setBlurBackgroundRadius`. NOTE: Not all device types support blur you can test if your device supports it using `Display.getInstnace().isGaussianBlurSupported()`. If blur isn't supported the blur setting will be ignored. [source,java] ---- Form hi = new Form("Blur Dialog", new BoxLayout(BoxLayout.Y_AXIS)); Dialog.setDefaultBlurBackgroundRadius(8); Button showDialog = new Button("Blur"); showDialog.addActionListener((e) -> Dialog.show("Blur", "Is On....", "OK", null)); hi.add(showDialog); hi.show(); ---- .The blur effect coupled with the OS default tint image::img/developer-guide/components-dialog-blur.png[The blur effect coupled with the OS default tint,scaledwidth=20%] It might be a bit hard to notice the blur effect with the tinting so here is the same code with tinting disabled: [source,java] ---- hi.setTintColor(0); ---- .The blur effect is more pronounced when the tint is disabled image::img/developer-guide/components-dialog-blur-no-tint.png[The blur effect is more pronounced when the tint is disabled,scaledwidth=20%] ==== Popup Dialog A popup dialog is a common mobile paradigm showing a `Dialog` that points at a specific component. It's just a standard `Dialog` that is shown in a unique way: [source,java] ---- Dialog d = new Dialog("Title"); d.setLayout(new BorderLayout()); d.add(BorderLayout.CENTER, new SpanLabel("Dialog Body", "DialogBody")); d.showPopupDialog(showDialog); ---- .Popup Dialog image::img/developer-guide/components-dialog-popup.png[Popup Dialog,scaledwidth=20%] The popup dialog accepts a https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] or https://www.codenameone.com/javadoc/com/codename1/ui/geom/Rectangle.html[Rectangle] to point at and handles the rest. ===== Styling The Arrow Of The Popup Dialog When Codename One was young we needed a popup arrow implementation but our low level graphics API was pretty basic. As a workaround we created a version of the 9-piece image border that supported pointing arrows at a component. Today Codename One supports pointing an arrow from the `RoundRectBorder` class. This is implicit for the `PopupDialog` UI. This allows for better customization of the border (color etc.) and it looks better on newer displays. It also works on all OSs. Right now only the iOS theme has the old image border approach. NOTE: This will change with a future update where all OS's will align and iOS will use the lightweight popup too TIP: You can make all OS's act the same way by overriding the `PopupDialog` UIID and defining its style to `RoundRectBorder` The new `RoundRectBorder` support works by setting the track component property on border. When that’s done the border implicitly points to the right location. If you still need deeper customization of the arrow you can still use the old 9-piece border functionality illustrated below. ====== Legacy 9-Piece Border Arrow One of the harder aspects of a popup dialog is the construction of the theme elements required for arrow styling. To get that sort of behavior you will need a custom image border and 4 arrows pointing in each direction that will be overlaid with the border. NOTE: The sizes of the arrow images should be similarly proportioned and fit within the image borders whitespace. The block image of the dialog should have empty pixels in the sides to reserve space for the arrow. E.g. if the arrows are all 32x32 pixels then the `PopupDialog` image should have 32 pixels of transparent pixels around it. You will need to define the following theme constants for the arrow to work: [source,java] ---- PopupDialogArrowBool=true PopupDialogArrowTopImage=arrow up image PopupDialogArrowBottomImage=arrow down image PopupDialogArrowLeftImage=arrow left image PopupDialogArrowRightImage=arrow right image ---- Then style the `PopupDialog` UIID with the image for the `Dialog` itself. === InteractionDialog Dialogs in Codename One can be modal or modeless, the former blocks the calling thread and the latter does not. However, there is another definition to those terms: A modal dialog blocks access to the rest of the UI while a modeless dialog "floats" on top of the UI. In that sense, all dialogs in Codename One are modal; they block the parent form since they are effectively just forms that show the "parent" in their background. https://www.codenameone.com/javadoc/com/codename1/components/InteractionDialog.html[InteractionDialog] has an API that is very similar to the https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html[Dialog] API but, unlike dialog, it never blocks anything. Neither the calling thread nor the UI. NOTE: `InteractionDialog` isn't a `Dialog` since it doesn't share the same inheritance hierarchy. However, it acts and "feels" like a `Dialog` despite the fact that it's just a `Container` in the `LayeredPane`. `InteractionDialog` is really just a container that is positioned within the layered pane. Notice that because of that design, you can have only one such dialog at the moment and, if you add something else to the layered pane, you might run into trouble. Using the interaction dialog is pretty trivial and very similar to dialog: [source,java] ---- InteractionDialog dlg = new InteractionDialog("Hello"); dlg.setLayout(new BorderLayout()); dlg.add(BorderLayout.CENTER, new Label("Hello Dialog")); Button close = new Button("Close"); close.addActionListener((ee) -> dlg.dispose()); dlg.addComponent(BorderLayout.SOUTH, close); Dimension pre = dlg.getContentPane().getPreferredSize(); dlg.show(0, 0, Display.getInstance().getDisplayWidth() - (pre.getWidth() + pre.getWidth() / 6), 0); ---- .Interaction Dialog image::img/developer-guide/components-interaction-dialog.png[Interaction Dialog,scaledwidth=20%] This will show the dialog on the right hand side of the screen, which is pretty useful for a floating in place dialog. NOTE: The `InteractionDialog` can only be shown at absolute or popup locations. This is inherent to its use case which is "non-blocking". When using this component you need to be very aware of its location. [[label-section]] === Label https://www.codenameone.com/javadoc/com/codename1/ui/Label.html[Label] represents a text, icon or both. `Label` is also the base class of `Button` which in turn is the base class for `RadioButton` & `CheckBox`. Thus the functionality of the `Label` class extends to all of these components. `Label` text can be positioned in one of 4 locations as such: [source,java] ---- Label left = new Label("Left", icon); left.setTextPosition(Component.LEFT); Label right = new Label("Right", icon); right.setTextPosition(Component.RIGHT); Label bottom = new Label("Bottom", icon); bottom.setTextPosition(Component.BOTTOM); Label top = new Label("Top", icon); top.setTextPosition(Component.TOP); hi.add(left).add(right).add(bottom).add(top); ---- .Label positions image::img/developer-guide/components-label-text-position.png[Label positions,scaledwidth=20%] `Label` allows only a single line of text, line breaking is a very expensive operation on mobile devices footnote:[String width is the real expensive part here, the complexity of font kerning and the recursion required to reflow text is a big performance hurdle] and so the `Label` class doesn't support it. TIP: <> supports multiple lines with a single label, notice that it does carry a performance penalty for this functionality. Labels support tickering and the ability to end with “...” if there isn't enough space to render the label. Developers can determine the placement of the label relatively to its icon in quite a few powerful ways. ==== Label Gap The gap between the label text & the icon defaults to 2 pixels due to legacy settings. The `setGap` method of `Label` accepts a gap size in pixels. Two pixels is low for most cases & it's hard to customize for each `Label`. You can use the theme constant `labelGap` which is a floating point value you can specify in millimeters that will allow you to determine the default gap for a label. You can also customize this manually using the method `Label.setDefaultGap(int)` which determines the default gap in pixels. ==== Autosizing Labels One of the common requests we received over the years is a way to let text "fit" into the allocated space so the font will match almost exactly the width available. In some designs this is very important but it's also very tricky. Measuring the width of a String is a surprisingly expensive operation on some OS's. Unfortunately, there is no other way other than trial & error to find the "best size". Still despite the fact that something is "slow" we might still want to use it for some cases, this isn't something you should use in a renderer, infinite scroll etc. and we recommend minimizing the usage of this feature as much as possible. This feature is only applicable to `Label` and its subclasses (e.g. `Button`), with components such as `TextArea` (e.g. `SpanButton`) the choice between shrinking and line break would require some complex logic. To activate this feature just use `setAutoSizeMode(true)` e.g.: [source,java] ---- Form hi = new Form("AutoSize", BoxLayout.y()); Label a = new Label("Short Text"); a.setAutoSizeMode(true); Label b = new Label("Much Longer Text than the previous line..."); b.setAutoSizeMode(true); Label c = new Label("MUCH MUCH MUCH Much Longer Text than the previous line by a pretty big margin..."); c.setAutoSizeMode(true); Label a1 = new Button("Short Text"); a1.setAutoSizeMode(true); Label b1 = new Button("Much Longer Text than the previous line..."); b1.setAutoSizeMode(true); Label c1 = new Button("MUCH MUCH MUCH Much Longer Text than the previous line by a pretty big margin..."); c1.setAutoSizeMode(true); hi.addAll(a, b, c, a1, b1, c1); hi.show(); ---- .Automatically sizes the fonts of the buttons/labels based on text and available space image::img/developer-guide/autosize.png[Automatically sizes the fonts of the buttons/labels based on text and available space,scaledwidth=20%] === TextField and TextArea The https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html[TextField] class derives from the https://www.codenameone.com/javadoc/com/codename1/ui/TextArea.html[TextArea] class, and both are used for text input in Codename One. `TextArea` defaults to multi-line input and `TextField` defaults to single line input but both can be used in both cases. The main differences between `TextField` and `TextArea` are: - Blinking cursor is rendered on `TextField` only - https://www.codenameone.com/javadoc/com/codename1/ui/events/DataChangedListener.html[DataChangeListener] is only available in `TextField`. This is crucial for character by character input event tracking - https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html#setDoneListener-com.codename1.ui.events.ActionListener-[Done listener] is only available in the `TextField` - Different `UIID` NOTE: The semantic difference between `TextField` & `TextArea` dates back to the ancestor of Codename One: LWUIT. Feature phones don’t have “proper” in-place editing capabilities & thus `TextField` was introduced to allow such input. Because it lacks the blinking cursor capability `TextArea` is often used as a multi-line label and is used internally in `SpanLabel`, `SpanButton` etc. TIP: A common use case is to have an important text component in edit mode immediately as we enter a `Form`. Codename One forms support this exact use case thru the https://www.codenameone.com/javadoc/com/codename1/ui/Form.html#setEditOnShow-com.codename1.ui.TextArea-[Form.setEditOnShow(TextArea)] method. `TextField` & `TextArea` support constraints for various types of input such as `NUMERIC`, `EMAIL`, `URL`, etc. Those usually affect the virtual keyboard used, but might not limit input in some platforms. E.g. on iOS even with `NUMERIC` constraint you would still be able to input characters. TIP: If you need to prevent specific types of input check out the <>. The following sample shows off simple text field usage: [[text-component-sample-code]] [source,java] ---- TableLayout tl; int spanButton = 2; if(Display.getInstance().isTablet()) { tl = new TableLayout(7, 2); } else { tl = new TableLayout(14, 1); spanButton = 1; } tl.setGrowHorizontally(true); hi.setLayout(tl); TextField firstName = new TextField("", "First Name", 20, TextArea.ANY); TextField surname = new TextField("", "Surname", 20, TextArea.ANY); TextField email = new TextField("", "E-Mail", 20, TextArea.EMAILADDR); TextField url = new TextField("", "URL", 20, TextArea.URL); TextField phone = new TextField("", "Phone", 20, TextArea.PHONENUMBER); TextField num1 = new TextField("", "1234", 4, TextArea.NUMERIC); TextField num2 = new TextField("", "1234", 4, TextArea.NUMERIC); TextField num3 = new TextField("", "1234", 4, TextArea.NUMERIC); TextField num4 = new TextField("", "1234", 4, TextArea.NUMERIC); Button submit = new Button("Submit"); TableLayout.Constraint cn = tl.createConstraint(); cn.setHorizontalSpan(spanButton); cn.setHorizontalAlign(Component.RIGHT); hi.add("First Name").add(firstName). add("Surname").add(surname). add("E-Mail").add(email). add("URL").add(url). add("Phone").add(phone). add("Credit Card"). add(GridLayout.encloseIn(4, num1, num2, num3, num4)). add(cn, submit); ---- .Simple text component sample image::img/developer-guide/components-text-component.png[Simple text component sample,scaledwidth=20%] TIP: The <> contains a very elaborate `TextField` search sample with `DataChangeListener` and rather unique styling. ==== Masking A common use case when working with text components is the ability to "mask" input e.g. in the credit card number above we would want 4 digits for each text field and don't want the user to tap #Next# 3 times. Masking allows us to accept partial input in one field and implicitly move to the next, this can be used to all types of complex input thanks to the text component API. E.g with the code above we can mask the credit card input so the cursor jumps to the next field implicitly using this code: [source,java] ---- automoveToNext(num1, num2); automoveToNext(num2, num3); automoveToNext(num3, num4); ---- Then implement the method `automoveToNext` as: [source,java] ---- private void automoveToNext(final TextField current, final TextField next) { current.addDataChangedListener((type, index) -> { if(current.getText().length() == 5) { current.stopEditing(); current.setText(val.substring(0, 4)); next.setText(val.substring(4)); next.startEditingAsync(); } }); } ---- Notice we can invoke `stopEditing(Runnable)` where we receive a callback as editing is stopped. ==== The Virtual Keyboard A common misconception for developers is assuming the virtual keyboard represents "keys". E.g. developers often override the "keyEvent" callbacks which are invoked for physical keyboard typing and expect those to occur with a virtual keyboard. This isn't the case since a virtual keyboard is a very different beast. With a virtual keyboard characters typed might produce a completely different output due to autocorrect. Some keyboards don't even have "keys" in the traditional sense or don't type them in the traditional sense (e.g. swiping). TIP: The constraint property for the `TextField`/`TextArea` is crucial for a virtual keyboard. TIP: When working with a virtual keyboard it's important that the parent `Container` for the `TextField`/`TextArea` is scrollable. Otherwise the component won't be reachable or the UI might be distorted when the keyboard appears. ===== Action Button Client Property By default, the virtual keyboard on Android has a "Done" button, you can customize it to be a search icon, a send icon, or a go icon using a hint such as this: [source,java] ---- searchTextField.putClientProperty("searchField", Boolean.TRUE); sendTextField.putClientProperty("sendButton", Boolean.TRUE); goTextField.putClientProperty("goButton", Boolean.TRUE); ---- This will adapt the icon for the action on the keys. ===== Next and Done on iOS We try to hide a lot of the platform differences in Codename One, input is **very** different between OS's. A common reliance is the ability to send the "Done" event when the user presses the #Done# button. Unfortunately this button doesn't always exist e.g. if there is an #Enter# button (due to multiline input) or if there is a #Next# button in that place. To make the behavior more uniform we slightly customized the iOS keyboard as such: .Next virtual keyboard with toolbar image::img/developer-guide/components-textfield-vkb-next.png[Next virtual keyboard with toolbar,scaledwidth=20%] .Done virtual keyboard without toolbar image::img/developer-guide/components-textfield-vkb-done.png[Done virtual keyboard without toolbar,scaledwidth=20%] NOTE: This works with 3rd party keyboards too... However, this behavior might not be desired so to block that we can do: [source,java] ---- tf.putClientProperty("iosHideToolbar", Boolean.TRUE); ---- This will hide the toolbar for that given field. NOTE: You can customize the color of the #Done# button in the toolbar by setting the `ios.doneButtonColor` display property. E.g. To change the color to red, you could do `Display.getInstance().setProperty("ios.doneButtonColor", String.valueOf(0xff0000))`. `@since 5.0` ==== Clearable Text Field iOS has a convention where an X can be placed after the text field to clear it. Some Android apps have it but there is no native support for that as of this writing. You can wrap a `TextField` with a clearable wrapper to get this effect on all platforms. E.g. replace this: [source,java] ---- cnt.add(myTextField); ---- With this: [source,java] ---- cnt.add(ClearableTextField.wrap(myTextField)); ---- You can also specify the size of the clear icon if you wish. This is technically just a `Container` with the text field style and a button to clear the text at the edge. === TextComponent When building input forms we sometimes want to adapt to the native OS behavior and create a UI that's a bit more distinct to the native OS. `TextField` and `TextArea` are very low level, you can create an Android style UI with such components but it might look out of place in iOS. E.g. this is how most of us would expect the UI to look on iOS and Android respectively: .Text Input on iOS image::img/developer-guide/pixel-perfect-text-field-reasonable-on-ios.png[TextModeLayout on iOS,scaledwidth=30%] .Text Input on Android image::img/developer-guide/pixel-perfect-text-field-android-codenameone-font.png[TextModeLayout on Android with the same code,scaledwidth=30%] Doing this with text fields is possible but would require code that looks a bit different and jumps through hoops. `TextComponent` allows this exact UI without forcing developers to write OS specific code: [source,java] ---- TextModeLayout tl = new TextModeLayout(3, 2); Form f = new Form("Pixel Perfect", tl); TextComponent title = new TextComponent().label("Title"); TextComponent price = new TextComponent().label("Price"); TextComponent location = new TextComponent().label("Location"); TextComponent description = new TextComponent().label("Description").multiline(true); f.add(tl.createConstraint().horizontalSpan(2), title); f.add(tl.createConstraint().widthPercentage(30), price); f.add(tl.createConstraint().widthPercentage(70), location); f.add(tl.createConstraint().horizontalSpan(2), description); f.setEditOnShow(title.getField()); f.show(); ---- TIP: This code uses the `TextModeLayout` which is discussed in the layouts section The text component uses a builder approach to set various values e.g.: [source,java] ---- TextComponent t = new TextComponent(). text("This appears in the text field"). hint("This is the hint"). label("This is the label"). multiline(true); ---- The code is pretty self explanatory and more convenient than typical setters/getters. It automatically handles the floating hint style of animation when running on Android. ==== Error Handling The validator class supports text component and it should "just work". But the cool thing is that it uses the material design convention for error handling! So if we add to the sample above a `Validator`: [source,java] ---- Validator val = new Validator(); val.addConstraint(title, new LengthConstraint(2)); val.addConstraint(price, new NumericConstraint(true)); ---- You would see something that looks like this on Android: .Error handling when the text is blank image::img/developer-guide/pixel-perfect-text-field-error-handling-blank.png[Error handling when the text is blank,scaledwidth=30%] .Error handling when there is some input (notice red title label) image::img/developer-guide/pixel-perfect-text-field-error-handling-text.png[Error handling when there is some input (notice red title label),scaledwidth=30%] .On iOS the situation hasn't changed much yet image::img/developer-guide/pixel-perfect-text-field-error-handling-on-ios.png[On iOS the situation hasn't changed much yet,scaledwidth=30%] The underlying system is the `errorMessage` method which you can chain like the other methods on `TextComponent` as such: [source,java] ---- TextComponent tc = new TextComponent(). label("Input Required"). errorMessage("Input is essential in this field"); ---- ==== InputComponent and PickerComponent To keep the code common and generic we use the `InputComponent` abstract base class and derive the other classes from that. `PickerComponent` is currently the only other option. A picker can work with our existing sample using code like this: [source,java] ---- TextModeLayout tl = new TextModeLayout(3, 2); Form f = new Form("Pixel Perfect", tl); TextComponent title = new TextComponent().label("Title"); TextComponent price = new TextComponent().label("Price"); TextComponent location = new TextComponent().label("Location"); PickerComponent date = PickerComponent.createDate(new Date()).label("Date"); TextComponent description = new TextComponent().label("Description").multiline(true); Validator val = new Validator(); val.addConstraint(title, new LengthConstraint(2)); val.addConstraint(price, new NumericConstraint(true)); f.add(tl.createConstraint().widthPercentage(60), title); f.add(tl.createConstraint().widthPercentage(40), date); f.add(location); f.add(price); f.add(tl.createConstraint().horizontalSpan(2), description); f.setEditOnShow(title.getField()); f.show(); ---- This produces the following which looks pretty standard: .Picker component taking place in iOS image::img/developer-guide/pixel-perfect-text-field-picker-ios.png[Picker component taking place in iOS,scaledwidth=30%] .And in Android image::img/developer-guide/pixel-perfect-text-field-picker-android.png[And in Android,scaledwidth=30%] The one tiny thing you should notice with the `PickerComponent` is that we don't construct the picker component using `new PickerComponent()`. Instead we use create methods such as `PickerComponent.createDate(new Date())`. The reason for that is that we have many types of pickers and it wouldn't make sense to have one constructor. ==== Underlying Theme Constants and UIID's These varying looks are implemented via a combination of layouts, theme constants and UIID's. The most important UIID's are: `TextComponent`, `FloatingHint` & `TextHint`. There are several theme constants related that can manipulate some pieces of this functionality: - `textComponentErrorColor` a hex RGB color which defaults to null in which case this has no effect. When defined this will change the color of the border and label to the given color to match the material design styling. This implements the red border underline in cases of error and the label text color change - `textComponentOnTopBool` toggles the on top mode which makes things look like they do on Android. This defaults to true on Android and false on other OS's. This can also be manipulated via the `onTopMode(boolean)` method in `InputComponent` however the layout will only use the theme constant - `textComponentAnimBool` toggles the animation mode which again can be manipulated by a method in `InputComponent`. If you want to keep the UI static without the floating hint effect set this to false. Notice this defaults to true only on Android - `textComponentFieldUIID` sets the UIID of the text field to something other than `TextField` this is useful for platforms such as iOS where the look of the text field is different within the text component. This allows us to make the background of the text field transparent when it's within the `TextComponent` and make it different from the regular text field [[button-section]] === Button https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] is a subclass of `Label` and as a result it inherits all of its functionality, specifically icon placement, tickering, etc. Button adds to the mix some additional states such as a pressed `UIID` state and pressed icon. NOTE: There are additional icon states in `Button` such as rollover and disabled icon. `Button` also exposes some functionality for subclasses specifically the `setToggle` method call which has no meaning when invoked on a `Button` but has a lot of implications for `CheckBox` & `RadioButton`. `Button` event handling can be performed via an https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionListener.html[ActionListener] or via a https://www.codenameone.com/javadoc/com/codename1/ui/Command.html[Command]. IMPORTANT: Changes in a `Command` won't be reflected into the `Button` after the command was set to the `Button`. Here is a trivial hello world style `Button`: [source,java] ---- Form hi = new Form("Button"); Button b = new Button("My Button"); hi.add(b); b.addActionListener((e) -> Log.p("Clicked")); ---- .Simple button in the iOS styling, notice iOS doesn't have borders on buttons... image::img/developer-guide/components-button.png[Simple button in the iOS styling, notice iOS doesn't have borders on buttons...,scaledwidth=40%] Such a button can be styled to look like a link using code like this or simply by making these settings in the theme and using code such as `btn.setUIID("Hyperlink")`. [source,java] ---- Form hi = new Form("Button"); Button b = new Button("Link Button"); b.getAllStyles().setBorder(Border.createEmpty()); b.getAllStyles().setTextDecoration(Style.TEXT_DECORATION_UNDERLINE); hi.add(b); b.addActionListener((e) -> Log.p("Clicked")); ---- .Button styled to look like a link image::img/developer-guide/components-link-button.png[Button styled to look like a link,scaledwidth=40%] ==== Uppercase Buttons Buttons on Android's material design UI use upper case styling which isn't the case for iOS. To solve this we have the method `setCapsText(boolean)` in `Button` which has the corresponding `isCapsText`, `isCapsTextDefault` & `setCapsTextDefault(boolean)`. This is pretty core to Codename One so to prevent this from impacting everything unless you explicitly invoke `setCapsText(boolean)` the default value of `true` will only apply when the UIID is `Button`, `RaisedButton` or for the builtin `Dialog` buttons. We also have a theme constant: `capsButtonTextBool`. This constant controls caps text behavior from the theme and is set to true in the Android native theme. ==== Raised Button Raised button is a style of button that's available on Android and used to highlight an important action within a form. To confirm with the material design UI guidelines you might want to leverage a raised button UI element on Android but use a regular button everywhere else. First we need to know whether a raised button exists in the theme. So on Android this will return true but on other OS's it will return false. A potential future update might make another platform true based on UI guidelines in other OS's. For this purpose we've got the theme constant `hasRaisedButtonBool` which will return true on Android but will be false elsewhere. You can use it like this: [source,java] ---- if(UIManager.getInstance().isThemeConstant("hasRaisedButtonBool", false)) { // that means we can use a raised button } ---- To enable this we have the `RaisedButton` UIID that derives from `Button` and will act like it except for the places where `hasRaisedButtonBool` is true in which case it will look like this: .Raised and flat button in simulator image::img/developer-guide/raised-flat-buttons.png[Raised and flat button in simulator,scaledwidth=40%] Notice that you can easily customize the colors of these buttons now since the border respects user colors... In this case I just set the background color to purple and the foreground to white: .Purple raised button image::img/developer-guide/raised-flat-buttons-purple.png[Purple raised button,scaledwidth=40%] [source,java] ---- Form f = new Form("Pixel Perfect", BoxLayout.y()); Button b = new Button("Raised Button", "RaisedButton"); Button r = new Button("Flat Button"); f.add(b); f.add(r); f.show(); ---- ==== Ripple Effect The ripple effect in material design highlights the location of the finger and grows as a circle to occupy the full area of the component as the user presses the button. We have the ability to perform a ripple effect by darkening the touched area and growing that in a quick animation. Ripple effect can be applied to any component but we currently only have it turned on for buttons on Android which also applies to things like title commands, side menu elements etc. This might not apply at this moment to lead components like multi-buttons but that might change in the future. `Component` has a property to enable the ripple effect `setRippleEffect(boolean)` and the corresponding `isRippleEffect()`. You can turn it on or off individually in the component level. However, `Button` has static `setButtonRippleEffectDefault(boolean)` and `isButtonRippleEffectDefault()`. These allow us to define the default behavior for all the buttons and that can be configured via the theme constant `buttonRippleBool` which is currently on by default on the native Android theme. === CheckBox/RadioButton https://www.codenameone.com/javadoc/com/codename1/ui/CheckBox.html[CheckBox] & https://www.codenameone.com/javadoc/com/codename1/ui/RadioButton.html[RadioButton] are subclasses of button that allow for either a toggle state or exclusive selection state. Both `CheckBox` & `RadioButton` have a selected state that allows us to determine their selection. TIP: `RadioButton` doesn't allow us to "deselect" it, the only way to "deselect" a `RadioButton` is by selecting another `RadioButton`. The `CheckBox` can be added to a `Container` like any other `Component` but the `RadioButton` must be associated with a `ButtonGroup` otherwise if we have more than one set of `RadioButton's` in the form we might have an issue. Notice in the sample below that we associate all the radio buttons with a group but don't do anything with the group as the radio buttons keep the reference internally. We also show the opposite side functionality and icon behavior: [source,java] ---- CheckBox cb1 = new CheckBox("CheckBox No Icon"); cb1.setSelected(true); CheckBox cb2 = new CheckBox("CheckBox With Icon", icon); CheckBox cb3 = new CheckBox("CheckBox Opposite True", icon); CheckBox cb4 = new CheckBox("CheckBox Opposite False", icon); cb3.setOppositeSide(true); cb4.setOppositeSide(false); RadioButton rb1 = new RadioButton("Radio 1"); RadioButton rb2 = new RadioButton("Radio 2"); RadioButton rb3 = new RadioButton("Radio 3", icon); new ButtonGroup(rb1, rb2, rb3); rb2.setSelected(true); hi.add(cb1).add(cb2).add(cb3).add(cb4).add(rb1).add(rb2).add(rb3); ---- .RadioButton & CheckBox usage image::img/developer-guide/components-radiobutton-checkbox.png[RadioButton & CheckBox usage,scaledwidth=20%] Both of these components can be displayed as toggle buttons (see the toggle button section below), or just use the default check mark/filled circle appearance based on the type/OS. ==== Toggle Button A toggle button is a button that is pressed and stays pressed. When a toggle button is pressed again it's released from the pressed state. Hence the button has a selected state to indicate if it's pressed or not exactly like the `CheckBox`/`RadioButton` components in Codename One. To turn any `CheckBox` or `RadioButton` to a toggle button just use the `setToggle(true)` method. Alternatively you can use the static `createToggle` method on both `CheckBox` and `RadioButton` to create a toggle button directly. IMPORTANT: Invoking `setToggle(true)` implicitly converts the `UIID` to `ToggleButton` unless it was changed by the user from its original default value. We can easily convert the sample above to use toggle buttons as such: [source,java] ---- CheckBox cb1 = CheckBox.createToggle("CheckBox No Icon"); cb1.setSelected(true); CheckBox cb2 = CheckBox.createToggle("CheckBox With Icon", icon); CheckBox cb3 = CheckBox.createToggle("CheckBox Opposite True", icon); CheckBox cb4 = CheckBox.createToggle("CheckBox Opposite False", icon); cb3.setOppositeSide(true); cb4.setOppositeSide(false); ButtonGroup bg = new ButtonGroup(); RadioButton rb1 = RadioButton.createToggle("Radio 1", bg); RadioButton rb2 = RadioButton.createToggle("Radio 2", bg); RadioButton rb3 = RadioButton.createToggle("Radio 3", icon, bg); rb2.setSelected(true); hi.add(cb1).add(cb2).add(cb3).add(cb4).add(rb1).add(rb2).add(rb3); ---- .Toggle button converted sample image::img/developer-guide/components-toggle-buttons.png[Toggle button converted sample,scaledwidth=20%] That's half the story though, to get the full effect of some cool toggle button UI's we can use a https://www.codenameone.com/javadoc/com/codename1/ui/ComponentGroup.html[ComponentGroup]. This allows us to create a button bar effect with the toggle buttons. E.g. lets enclose the `CheckBox` components in a vertical `ComponentGroup` and the `RadioButton's` in a horizontal group. We can do this by changing the last line of the code above as such: [source,java] ---- hi.add(ComponentGroup.enclose(cb1, cb2, cb3, cb4)). add(ComponentGroup.encloseHorizontal(rb1, rb2, rb3)); ---- .Toggle button converted sample wrapped in ComponentGroup image::img/developer-guide/components-toggle-buttons-component-group.png[Toggle button converted sample wrapped in ComponentGroup,scaledwidth=20%] === ComponentGroup https://www.codenameone.com/javadoc/com/codename1/ui/ComponentGroup.html[ComponentGroup] is a special container that can be either horizontal or vertical (https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BoxLayout.html[BoxLayout] `X_AXIS` or `Y_AXIS` respectively). `ComponentGroup` "restyles" the elements within the group to have a `UIID` that allows us to create a "round border" effect that groups elements together. The following code adds 4 component groups to a `Container` to demonstrate the various `UIID` changes: [source,java] ---- hi.add("Three Labels"). add(ComponentGroup.enclose(new Label("GroupElementFirst UIID"), new Label("GroupElement UIID"), new Label("GroupElementLast UIID"))). add("One Label"). add(ComponentGroup.enclose(new Label("GroupElementOnly UIID"))). add("Three Buttons"). add(ComponentGroup.enclose(new Button("ButtonGroupFirst UIID"), new Button("ButtonGroup UIID"), new Button("ButtonGroupLast UIID"))). add("One Button"). add(ComponentGroup.enclose(new Button("ButtonGroupOnly UIID"))); ---- .ComponentGroup adapts the UIID's of the components added so we can style them image::img/developer-guide/components-componentgroup.png[ComponentGroup adapts the UIID's of the components added so we can style them,scaledwidth=15%] Notice the following about the code above and the resulting image: - Buttons have a different UIID than other element types. Their styling is slightly different in such UI's so you need to pay attention to that. - When an element is placed alone within a `ComponentGroup` its a special case `UIID`. IMPORTANT: By default, `ComponentGroup` does *nothing*. You need to explicitly activate it in the theme by setting a theme property to true. Specifically you need to set `ComponentGroupBool` to `true` for `ComponentGroup` to do something otherwise its just a box layout container! The `ComponentGroupBool` flag is true by default in the iOS native theme. When `ComponentGroupBool` is set to true, the component group will modify the styles of all components placed within it to match the element UIID given to it (GroupElement by default) with special caveats to the first/last/only elements. E.g. 1. If I have one element within a component group it will have the UIID: `GroupElementOnly` 2. If I have two elements within a component group they will have the UIID's `GroupElementFirst`, `GroupElementLast` 3. If I have three elements within a component group they will have the UIID's `GroupElementFirst`, `GroupElement`, `GroupElementLast` 4. If I have four elements within a component group they will have the UIID's `GroupElementFirst`, `GroupElement`, `GroupElement`, `GroupElementLast` This allows you to define special styles for the edges. You can customize the `UIID` set by the component group by calling `setElementUIID` in the component group e.g. `setElementUIID("ToggleButton")` for three elements result in the following `UIID's`: `ToggleButtonFirst`, `ToggleButton`, `ToggleButtonLast` === MultiButton https://www.codenameone.com/javadoc/com/codename1/components/MultiButton.html[MultiButton] is a <> that acts like a versatile https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button]. It supports up to 4 lines of text (it doesn’t automatically wrap the text), an emblem (usually navigational arrow, or check box) and an icon. `MultiButton` can be used as a button, a `CheckBox` or a `RadioButton` for creating rich UI’s. NOTE: The `MultiButton` was inspired by the aesthetics of the `UITableView` iOS component. A common source of confusion in the `MultiButton` is the difference between the icon and the emblem, since both may have an icon image associated with them. The icon is an image representing the entry while the emblem is an optional visual representation of the action that will be undertaken when the element is pressed. Both may be used simultaneously or individually of one another. [source,java] ---- MultiButton twoLinesNoIcon = new MultiButton("MultiButton"); twoLinesNoIcon.setTextLine2("Line 2"); MultiButton oneLineIconEmblem = new MultiButton("Icon + Emblem"); oneLineIconEmblem.setIcon(icon); oneLineIconEmblem.setEmblem(emblem); MultiButton twoLinesIconEmblem = new MultiButton("Icon + Emblem"); twoLinesIconEmblem.setIcon(icon); twoLinesIconEmblem.setEmblem(emblem); twoLinesIconEmblem.setTextLine2("Line 2"); MultiButton twoLinesIconEmblemHorizontal = new MultiButton("Icon + Emblem"); twoLinesIconEmblemHorizontal.setIcon(icon); twoLinesIconEmblemHorizontal.setEmblem(emblem); twoLinesIconEmblemHorizontal.setTextLine2("Line 2 Horizontal"); twoLinesIconEmblemHorizontal.setHorizontalLayout(true); MultiButton twoLinesIconCheckBox = new MultiButton("CheckBox"); twoLinesIconCheckBox.setIcon(icon); twoLinesIconCheckBox.setCheckBox(true); twoLinesIconCheckBox.setTextLine2("Line 2"); MultiButton fourLinesIcon = new MultiButton("With Icon"); fourLinesIcon.setIcon(icon); fourLinesIcon.setTextLine2("Line 2"); fourLinesIcon.setTextLine3("Line 3"); fourLinesIcon.setTextLine4("Line 4"); hi.add(oneLineIconEmblem). add(twoLinesNoIcon). add(twoLinesIconEmblem). add(twoLinesIconEmblemHorizontal). add(twoLinesIconCheckBox). add(fourLinesIcon); ---- .Multiple usage scenarios for the MultiButton image::img/developer-guide/components-multibutton.png[Multiple usage scenarios for the MultiButton,scaledwidth=20%] ==== Styling The MultiButton Since the `MultiButton` is a composite component setting its `UIID` will only impact the top level UI. To customize everything you need to customize the UIID's for `MultiLine1`, `MultiLine2`, `MultiLine3`, `MultiLine4` & `Emblem`. You can customize the individual `UIID's` thru the API directly using the `setIconUIID`, `setUIIDLine1`, `setUIIDLine2`, `setUIIDLine3`, `setUIIDLine4` & `setEmblemUIID`. === SpanButton https://www.codenameone.com/javadoc/com/codename1/components/SpanButton.html[SpanButton] is a <> that looks/acts like a `Button` but can break lines rather than crop them when the text is very long. Unlike the `MultiButton` it uses the `TextArea` internally to break lines seamlessly. The `SpanButton` is far simpler than the `MultiButton` and as a result isn't as configurable. [source,java] ---- SpanButton sb = new SpanButton("SpanButton is a composite component (lead component) that looks/acts like a Button but can break lines rather than crop them when the text is very long."); sb.setIcon(icon); hi.add(sb); ---- .The SpanButton Component image::img/developer-guide/components-spanbutton.png[The SpanButton Component,scaledwidth=20%] TIP: `SpanButton` is slower than both `Button` and `MultiButton`. We recommend using it only when there is a genuine need for its functionality. [[SpanLabel]] === SpanLabel https://www.codenameone.com/javadoc/com/codename1/components/SpanLabel.html[SpanLabel] is a <> that looks/acts like a https://www.codenameone.com/javadoc/com/codename1/ui/Label.html[Label] but can break lines rather than crop them when the text is very long. `SpanLabel` uses the `TextArea` internally to break lines seamlessly and so doesn't provide all the elaborate configuration options of `Label`. One of the features of label that moved into `SpanLabel` to some extent is the ability to position the icon. However, unlike a `Label` the icon position is determined by the layout manager of the composite so `setIconPosition` accepts a `BorderLayout` constraint. [source,java] ---- SpanLabel d = new SpanLabel("Default SpanLabel that can seamlessly line break when the text is really long."); d.setIcon(icon); SpanLabel l = new SpanLabel("NORTH Positioned Icon SpanLabel that can seamlessly line break when the text is really long."); l.setIcon(icon); l.setIconPosition(BorderLayout.NORTH); SpanLabel r = new SpanLabel("SOUTH Positioned Icon SpanLabel that can seamlessly line break when the text is really long."); r.setIcon(icon); r.setIconPosition(BorderLayout.SOUTH); SpanLabel c = new SpanLabel("EAST Positioned Icon SpanLabel that can seamlessly line break when the text is really long."); c.setIcon(icon); c.setIconPosition(BorderLayout.EAST); hi.add(d).add(l).add(r).add(c); ---- .The SpanLabel Component image::img/developer-guide/components-spanlabel.png[The SpanLabel Component,scaledwidth=20%] TIP: `SpanLabel` is significantly slower than `Label`. We recommend using it only when there is a genuine need for its functionality. === OnOffSwitch The https://www.codenameone.com/javadoc/com/codename1/components/OnOffSwitch.html[OnOffSwitch] allows you to write an application where the user can swipe a switch between two states (on/off). This is a common UI paradigm in Android and iOS, although it's implemented in a radically different way in both platforms. This is a rather elaborate component because of its very unique design on iOS, but we we're able to accommodate most of the small behaviors of the component into our version, and it seamlessly adapts between the Android style and the iOS style. The image below was generated based on the default use of the `OnOffSwitch`: [source,java] ---- OnOffSwitch onOff = new OnOffSwitch(); hi.add(onOff); ---- .The OnOffSwitch component as it appears on/off on iOS (top) and on Android (bottom) image::img/developer-guide/components-onoffswitch.png[The OnOffSwitch component as it appears on/off on iOS (top) and on Android (bottom),scaledwidth=20%] As you can understand the difference between the way iOS and Android render this component has triggered two very different implementations within a single component. The Android implementation just uses standard buttons and is the default for non-iOS platforms. TIP: You can force the Android or iOS mode by using the theme constant `onOffIOSModeBool`. [[validation-section]] ==== Validation Validation is an inherent part of text input, and the https://www.codenameone.com/javadoc/com/codename1/ui/validation/Validator.html[Validator] class allows just that. You can enable validation thru the `Validator` class to add constraints for a specific component. It's also possible to define components that would be enabled/disabled based on validation state and the way in which validation errors are rendered (change the components `UIID`, paint an emblem on top, etc.). A https://www.codenameone.com/javadoc/com/codename1/ui/validation/Constraint.html[Constraint] is an interface that represents validation requirements. You can define a constraint in Java or use some of the builtin constraints such as https://www.codenameone.com/javadoc/com/codename1/ui/validation/LengthConstraint.html[LengthConstraint], https://www.codenameone.com/javadoc/com/codename1/ui/validation/RegexConstraint.html[RegexConstraint], etc. This sample below continues from the place where the <> stopped by adding validation to that code. [source,java] ---- Validator v = new Validator(); v.addConstraint(firstName, new LengthConstraint(2)). addConstraint(surname, new LengthConstraint(2)). addConstraint(url, RegexConstraint.validURL()). addConstraint(email, RegexConstraint.validEmail()). addConstraint(phone, new RegexConstraint(phoneRegex, "Must be valid phone number")). addConstraint(num1, new LengthConstraint(4)). addConstraint(num2, new LengthConstraint(4)). addConstraint(num3, new LengthConstraint(4)). addConstraint(num4, new LengthConstraint(4)); v.addSubmitButtons(submit); ---- .Validation & Regular Expressions image::img/developer-guide/validation-regex-masking-1.png[Validation and Regular Expressions,scaledwidth=20%] === InfiniteProgress The https://www.codenameone.com/javadoc/com/codename1/components/InfiniteProgress.html[InfiniteProgress] indicator spins an image infinitely to indicate that a background process is still working. TIP: This style of animation is often nicknamed "washing machine" as it spins endlessly. `InfiniteProgress` can be used in one of two ways either by embedding the component into the UI thru something like this: [source,java] ---- myContainer.add(new InfiniteProgress()); ---- `InfiniteProgress` can also appear over the entire screen, thus blocking all input. This tints the background while the infinite progress rotates: [source,java] ---- Dialog ip = new InfiniteProgress().showInifiniteBlocking(); // do some long operation here using invokeAndBlock or do something in a separate thread and callback later // when you are done just call ip.dispose(); ---- .Infinite progress image::img/developer-guide/infinite-progress.png[Infinite progress,scaledwidth=10%] The image used in the `InfiniteProgress` animation is defined by the native theme. You can override that definition either by defining the theme constant `infiniteImage` or by invoking the https://www.codenameone.com/javadoc/com/codename1/components/InfiniteProgress.html#setAnimation-com.codename1.ui.Image-[setAnimation] method. NOTE: Despite the name of the method `setAnimation` expects a static image that will be rotated internally. Don't use an animated image. === InfiniteScrollAdapter and InfiniteContainer https://www.codenameone.com/javadoc/com/codename1/components/InfiniteScrollAdapter.html[InfiniteScrollAdapter] & https://www.codenameone.com/javadoc/com/codename1/ui/InfiniteContainer.html[InfiniteContainer] allow us to create a scrolling effect that "never" ends with the typical `Container`/`Component` paradigm. The motivation behind these classes is simple, say we have a lot of data to fetch from storage or from the internet. We can fetch the data in batches and show progress indication while we do this. Infinite scroll fetches the next batch of data dynamically as we reach the end of the `Container`. `InfiniteScrollAdapter` & `InfiniteContainer` represent two similar ways to accomplish that task relatively easily. Let start by exploring how we can achieve this UI that fetches data from a webservice: .InfiniteScrollAdapter demo code fetching property cross data image::img/developer-guide/components-infinitescrolladapter.png[InfiniteScrollAdapter demo code fetching property cross data,scaledwidth=20%] The first step is creating the webservice call, we won't go into too much detail here as webservices & IO are discussed later in the guide: [source,java] ---- int pageNumber = 1; java.util.List> fetchPropertyData(String text) { try { ConnectionRequest r = new ConnectionRequest(); r.setPost(false); r.setUrl("http://api.nestoria.co.uk/api"); r.addArgument("pretty", "0"); r.addArgument("action", "search_listings"); r.addArgument("encoding", "json"); r.addArgument("listing_type", "buy"); r.addArgument("page", "" + pageNumber); pageNumber++; r.addArgument("country", "uk"); r.addArgument("place_name", text); NetworkManager.getInstance().addToQueueAndWait(r); Map result = new JSONParser().parseJSON(new InputStreamReader(new ByteArrayInputStream(r.getResponseData()), "UTF-8")); Map response = (Map)result.get("response"); return (java.util.List>)response.get("listings"); } catch(Exception err) { Log.e(err); return null; } } ---- IMPORTANT: The demo code here doesn't do any error handling! This is a very bad practice and it is taken here to keep the code short and readable. Proper error handling is used in the Property Cross demo. The `fetchPropertyData` is a very simplistic tool that just fetches the next page of listings for the nestoria webservice. Notice that this method is synchronous and will block the calling thread (legally) until the network operation completes. Now that we have a webservice lets proceed to create the UI. Check out the code annotations below: [source,java] ---- Form hi = new Form("InfiniteScrollAdapter", new BoxLayout(BoxLayout.Y_AXIS)); Style s = UIManager.getInstance().getComponentStyle("MultiLine1"); FontImage p = FontImage.createMaterial(FontImage.MATERIAL_PORTRAIT, s); EncodedImage placeholder = EncodedImage.createFromImage(p.scaled(p.getWidth() * 3, p.getHeight() * 3), false); // <1> InfiniteScrollAdapter.createInfiniteScroll(hi.getContentPane(), () -> { // <2> java.util.List> data = fetchPropertyData("Leeds"); // <3> MultiButton[] cmps = new MultiButton[data.size()]; for(int iter = 0 ; iter < cmps.length ; iter++) { Map currentListing = data.get(iter); if(currentListing == null) { // <4> InfiniteScrollAdapter.addMoreComponents(hi.getContentPane(), new Component[0], false); return; } String thumb_url = (String)currentListing.get("thumb_url"); String guid = (String)currentListing.get("guid"); String summary = (String)currentListing.get("summary"); cmps[iter] = new MultiButton(summary); cmps[iter].setIcon(URLImage.createToStorage(placeholder, guid, thumb_url)); } InfiniteScrollAdapter.addMoreComponents(hi.getContentPane(), cmps, true); // <5> }, true); // <6> ---- <1> Placeholder is essential for the https://www.codenameone.com/javadoc/com/codename1/ui/URLImage.html[URLImage] class which we will discuss at a different place. <2> The `InfiniteScrollAdapter` accepts a runnable which is invoked every time we reach the edge of the scrolling. We used a closure instead of the typical run() method override. <3> This is a blocking call, after the method completes we'll have all the data we need. Notice that this method doesn't block the EDT illegally. <4> If there is no more data we call the `addMoreComponents` method with a false argument. This indicates that there is no additional data to fetch. <5> Here we add the actual components to the end of the form. Notice that we *must not* invoke the `add`/`remove` method of `Container`. Those might conflict with the work of the `InfiniteScrollAdapter`. <6> We pass true to indicate that the data isn't "prefilled" so the method should be invoked immediately when the `Form` is first shown IMPORTANT: Do not violate the EDT in the callback. It is invoked on the event dispatch thread and it is crucial ==== The InfiniteContainer https://www.codenameone.com/javadoc/com/codename1/ui/InfiniteContainer.html[InfiniteContainer] was introduced to simplify and remove some of the boilerplate of the `InfiniteScrollAdapter`. It takes a more traditional approach of inheriting the Container class to provide its functionality. Unlike the `InfiniteScrollAdapter` the `InfiniteContainer` accepts an index and amount to fetch. This is useful for tracking your position but also important since the `InfiniteContainer` also implements #Pull To Refresh# as part of its functionality. Converting the code above to an `InfiniteContainer` is pretty simple we just moved all the code into the callback `fetchComponents` method and returned the array of `Component's` as a response. Unlike the `InfiniteScrollAdapter` we can't use the `ContentPane` directly so we have to use a `BorderLayout` and place the `InfiniteContainer` there: [source,java] ---- Form hi = new Form("InfiniteContainer", new BorderLayout()); Style s = UIManager.getInstance().getComponentStyle("MultiLine1"); FontImage p = FontImage.createMaterial(FontImage.MATERIAL_PORTRAIT, s); EncodedImage placeholder = EncodedImage.createFromImage(p.scaled(p.getWidth() * 3, p.getHeight() * 3), false); InfiniteContainer ic = new InfiniteContainer() { @Override public Component[] fetchComponents(int index, int amount) { java.util.List> data = fetchPropertyData("Leeds"); MultiButton[] cmps = new MultiButton[data.size()]; for(int iter = 0 ; iter < cmps.length ; iter++) { Map currentListing = data.get(iter); if(currentListing == null) { return null; } String thumb_url = (String)currentListing.get("thumb_url"); String guid = (String)currentListing.get("guid"); String summary = (String)currentListing.get("summary"); cmps[iter] = new MultiButton(summary); cmps[iter].setIcon(URLImage.createToStorage(placeholder, guid, thumb_url)); } return cmps; } }; hi.add(BorderLayout.CENTER, ic); ---- === List, MultiList, Renderers & Models ==== InfiniteContainer/InfiniteScrollAdapter vs. List/ContainerList Our recommendation is to always go with `Container`, `InfiniteContainer` or `InfiniteScrollAdapter`. We recommend avoiding `List` or its subclasses/related classes specifically `ContainerList` & `MultiList`. NOTE: We recommend replacing `ComboBox` with `Picker` but that's a completely different discussion. A `Container` with ~5000 nested containers within it can perform on par with a `List` and probably exceed its performance when used correctly. Larger sets of data are rarely manageable on phones or tablets so the benefits for lists are dubious. In terms of API we found that even experienced developers experienced a great deal of pain when wrangling the Swing styled lists and their stateless approach. Since animation, swiping and other capabilities that are so common in mobile are so hard to accomplish with lists we see no actual reason to use them. ==== Why Isn't List Deprecated? We deprecated `ContainerList` which performs really badly and has some inherent complexity issues. `List` has some unique use cases and is still used all over Codename One. `MultiList` is a reasonable version of `List` that is far easier to use without most of the pains related to renderer configuration. There are cases where using `List` or `MultiList` is justified, they are just rarer than usual hence our recommendation. ==== MVC In Lists A Codename One https://www.codenameone.com/javadoc/com/codename1/ui/List.html[List] doesn't contain components, but rather arbitrary data; this seems odd at first but makes sense. If you want a list to contain components, just use a Container. The advantage of using a `List` in this way is that we can display it in many ways (e.g. fixed focus positions, horizontally, etc.), and that we can have more than a million entries without performance overhead. We can also do some pretty nifty things, like filtering the list on the fly or fetching it dynamically from the Internet as the user scrolls down the list. To achieve these things the list uses two interfaces: https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] and ListCellRenderer. https://www.codenameone.com/javadoc/com/codename1/ui/List.html[List] model represents the data; its responsibility is to return the arbitrary object within the list at a given offset. Its second responsibility is to notify the list when the data changes, so the list can refresh. TIP: Think of the model as an array of objects that can notify you when it changes. The list renderer is like a rubber stamp that knows how to draw an object from the model, it's called many times per entry in an animated list and must be very fast. Unlike standard Codename One components, it is only used to draw the entry in the model and is immediately discarded, hence it has no memory overhead, but if it takes too long to process a model value it can be a big bottleneck! TIP: Think of the render as a translation layer that takes the "data" from the model and translates it to a visual representation. This is all very generic, but a bit too much for most, doing a list "properly" requires some understanding. The main source of confusion for developers is the stateless nature of the list and the transfer of state to the model (e.g. a checkbox list needs to listen to action events on the list and update the model, in order for the renderer to display that state). Once you understand that it’s easy. ==== Understanding MVC Let's recap, what is MVC: - #Model# - Represents the data for the component (list), the model can tell us exactly how many items are in it and which item resides at a given offset within the model. This differs from a simple `Vector` (or array), since all access to the model is controlled (the interface is simpler), and unlike a `Vector`/Array, the model can notify us of changes that occur within it. - #View# - The view draws the content of the model. It is a "dumb" layer that has no notion of what is displayed and only knows how to draw. It tracks changes in the model (the model sends events) and redraws itself when it changes. - #Controller# - The controller accepts user input and performs changes to the model, which in turn cause the view to refresh. .Typical MVC Diagram footnote:[Image by RegisFrey - Own work, Public Domain, https://commons.wikimedia.org/w/index.php?curid=10298177] image::img/developer-guide/mvc.png[Image by RegisFrey - Own work Public Domain https://commons.wikimedia.org/w/index.php?curid=10298177,scaledwidth=25%] Codename One's https://www.codenameone.com/javadoc/com/codename1/ui/List.html[List] component uses the MVC paradigm in its implementation. `List` itself is the #Controller# (with a bit of the #View# mixed in). The https://www.codenameone.com/javadoc/com/codename1/ui/list/ListCellRenderer.html[ListCellRenderer] interface is the rest of the #View# and the https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] is (you guessed it by now) the #Model#. When the list is painted, it iterates over the visible elements in the model and asks the model for the data, it then draws them using the renderer. Notice that because of this both the model and the renderer must be REALLY fast and that's hard. ===== Why is this useful? Since the model is a lightweight interface, it can be implemented by you and replaced in runtime if so desired, this allows several use cases: 1. A list can contain thousands of entries but only load the portion visible to the user. Since the model will only be queried for the elements that are visible to the user, it won't need to load the large data set into memory until the user starts scrolling down (at which point other elements may be offloaded from memory). 2. A list can cache efficiently. E.g. a list can mirror data from the server into local RAM without actually downloading all the data. Data can also be mirrored from storage for better performance and discarded for better memory utilization. 3. The is no need for state copying. Since renderers allow us to display any object type, the list model interface can be implemented by the application's data structures (e.g. persistence/network engine), which would return internal application data structures saving you the need of copying application state into a list specific data structure. Note that this advantage only applies with a custom renderer which is pretty difficult to get right. 4. Using the proxy pattern we can layer logic such as filtering, sorting, caching, etc. on top of existing models without changing the model source code. 5. We can reuse generic models for several views, e.g. a model that fetches data from the server can be initialized with different arguments, to fetch different data for different views. View objects in different Forms can display the same model instance in different view instances, thus they would update automatically when we change one global model. Most of these use cases work best for lists that grow to a larger size, or represent complex data, which is what the list object is designed to do. ==== Important - Lists & Layout Managers Usually when working with lists, you want the list to handle the scrolling (otherwise it will perform badly). This means you should place the list in a non-scrollable container (no parent can be scrollable), notice that the content pane is scrollable by default, so you should disable that. It is also recommended to place the list in the `CENTER` location of a https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BorderLayout.html[BorderLayout] to produce the most effective results. e.g.: [source,java] ---- form.setScrollable(false); form.setLayout(new BorderLayout()); form.add(BorderLayout.CENTER, myList); ---- ==== MultiList & DefaultListModel So after this long start lets show the first sample of creating a list using the https://www.codenameone.com/javadoc/com/codename1/ui/list/MultiList.html[MultiList]. The `MultiList` is a preconfigured list that contains a ready made renderer with defaults that make sense for the most common use cases. It still retains most of the power available to the `List` component but reduces the complexity of one of the hardest things to grasp for most developers: rendering. The full power of the `ListModel` is still available and allows you to create a million entry list with just a few lines of code. However the objects that the model returns should always be in the form of `Map` objects and not an arbitrary object like the standard `List` allows. Here is a simple example of a `MultiList` containing a highly popular subject matter: [source,java] ---- Form hi = new Form("MultiList", new BorderLayout()); ArrayList> data = new ArrayList<>(); data.add(createListEntry("A Game of Thrones", "1996")); data.add(createListEntry("A Clash Of Kings", "1998")); data.add(createListEntry("A Storm Of Swords", "2000")); data.add(createListEntry("A Feast For Crows", "2005")); data.add(createListEntry("A Dance With Dragons", "2011")); data.add(createListEntry("The Winds of Winter", "2016 (please, please, please)")); data.add(createListEntry("A Dream of Spring", "Ugh")); DefaultListModel> model = new DefaultListModel<>(data); MultiList ml = new MultiList(model); hi.add(BorderLayout.CENTER, ml); ---- .Basic usage of the MultiList & DefaultListModel] image::img/developer-guide/components-multilist.png[Basic usage of the MultiList and DefaultListModel,scaledwidth=20%] `createListEntry` is relatively trivial: [source,java] ---- private Map createListEntry(String name, String date) { Map entry = new HashMap<>(); entry.put("Line1", name); entry.put("Line2", date); return entry; } ---- There is one major piece missing here and that is the cover images for the books. A simple approach would be to just place the image objects into the entries using the "icon" property as such: [source,java] ---- private Map createListEntry(String name, String date, Image cover) { Map entry = new HashMap<>(); entry.put("Line1", name); entry.put("Line2", date); entry.put("icon", cover); return entry; } ---- .With cover images in place image::img/developer-guide/graphics-urlimage-multilist.png[With cover images in place,scaledwidth=20%] TIP: Since the `MultiList` uses the `GenericListCellRenderer` internally we can use https://www.codenameone.com/javadoc/com/codename1/ui/URLImage.html[URLImage] to dynamically fetch the data. This is discussed in the graphics section of this guide. ===== Going Further With the ListModel Lets assume that http://www.georgerrmartin.com/[GRRM] was really prolific and wrote 1 million books. The default list model won't make much sense in that case but we would still be able to render everything in a list model. We'll fake it a bit but notice that 1M components won't be created even if we somehow scroll all the way down... The https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] interface can be implemented by anyone in this case we just did a really stupid simple implementation: [source,java] ---- class GRMMModel implements ListModel> { @Override public Map getItemAt(int index) { int idx = index % 7; switch(idx) { case 0: return createListEntry("A Game of Thrones " + index, "1996"); case 1: return createListEntry("A Clash Of Kings " + index, "1998"); case 2: return createListEntry("A Storm Of Swords " + index, "2000"); case 3: return createListEntry("A Feast For Crows " + index, "2005"); case 4: return createListEntry("A Dance With Dragons " + index, "2011"); case 5: return createListEntry("The Winds of Winter " + index, "2016 (please, please, please)"); default: return createListEntry("A Dream of Spring " + index, "Ugh"); } } @Override public int getSize() { return 1000000; } @Override public int getSelectedIndex() { return 0; } @Override public void setSelectedIndex(int index) { } @Override public void addDataChangedListener(DataChangedListener l) { } @Override public void removeDataChangedListener(DataChangedListener l) { } @Override public void addSelectionListener(SelectionListener l) { } @Override public void removeSelectionListener(SelectionListener l) { } @Override public void addItem(Map item) { } @Override public void removeItem(int index) { } } ---- We can now replace the existing model by removing all the model related logic and changing the constructor call as such: [source,java] ---- MultiList ml = new MultiList(new GRMMModel()); ---- .It took ages to scroll this far... This goes to a million... image::img/developer-guide/components-millionbooks.png[It took ages to scroll this far... This goes to a million...,scaledwidth=20%] ==== List Cell Renderer The Renderer is a simple interface with 2 methods: [source,java] ---- public interface ListCellRenderer { //This method is called by the List for each item, when the List paints itself. public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected); //This method returns the List animated focus which is animated when list selection changes public Component getListFocusComponent(List list); } ---- The most simple/naive implementation may choose to implement the renderer as follows: [source,java] ---- public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected){ return new Label(value.toString()); } public Component getListFocusComponent(List list){ return null; } ---- This will compile and work, but won't give you much, notice that you won't see the `List` selection move on the List, this is just because the renderer returns a https://www.codenameone.com/javadoc/com/codename1/ui/Label.html[Label] with the same style regardless if it's selected or not. Now Let's try to make it a bit more useful. [source,java] ---- public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected){ Label l = new Label(value.toString()); if (isSelected) { l.setFocus(true); l.getAllStyles().setBgTransparency(100); } else { l.setFocus(false); l.getAllStyles().setBgTransparency(0); } return l; } public Component getListFocusComponent(List list){ return null; } ---- In this renderer we set the `Label.setFocus(true)` if it's selected, calling to this method doesn't really give the focus to the Label, it simply renders the label as selected. Then we invoke `Label.getAllStyles().setBgTransparency(100)` to give the selection semi transparency, and `0` for full transparency if not selected. That is still not very efficient because we create a new `Label` each time the method is invoked. To make the code tighter, keep a reference to the `Component` or extend it as https://www.codenameone.com/javadoc/com/codename1/ui/list/DefaultListCellRenderer.html[DefaultListCellRenderer] does. [source,java] ---- class MyRenderer extends Label implements ListCellRenderer { public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected){ setText(value.toString()); if (isSelected) { setFocus(true); getAllStyles().setBgTransparency(100); } else { setFocus(false); getAllStyles().setBgTransparency(0); } return this; } } } ---- Now Let's have a look at a more advanced Renderer. [source,java] ---- class ContactsRenderer extends Container implements ListCellRenderer { private Label name = new Label(""); private Label email = new Label(""); private Label pic = new Label(""); private Label focus = new Label(""); public ContactsRenderer() { setLayout(new BorderLayout()); addComponent(BorderLayout.WEST, pic); Container cnt = new Container(new BoxLayout(BoxLayout.Y_AXIS)); name.getAllStyles().setBgTransparency(0); name.getAllStyles().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM)); email.getAllStyles().setBgTransparency(0); cnt.addComponent(name); cnt.addComponent(email); addComponent(BorderLayout.CENTER, cnt); focus.getStyle().setBgTransparency(100); } public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) { Contact person = (Contact) value; name.setText(person.getName()); email.setText(person.getEmail()); pic.setIcon(person.getPic()); return this; } public Component getListFocusComponent(List list) { return focus; } } ---- In this renderer we want to render a `Contact` object to the Screen, we build the `Component` in the constructor and in the getListCellRendererComponent we simply update the Labels' texts according to the `Contact` object. Notice that in this renderer we return a focus `Label` with semi transparency, as mentioned before, the focus component can be modified within this method. For example, I can modify the focus `Component` to have an icon. [source,java] ---- focus.getAllStyles().setBgTransparency(100); try { focus.setIcon(Image.createImage("/duke.png")); focus.setAlignment(Component.RIGHT); } catch (IOException ex) { ex.printStackTrace(); } ---- ==== Generic List Cell Renderer As part of the GUI builder work, we needed a way to customize rendering for a List, but the renderer/model approach seemed impossible to adapt to a GUI builder (it seems the Swing GUI builders had a similar issue). Our solution was to introduce the `GenericListCellRenderer`, which while introducing limitations and implementation requirements still manages to make life easier, both in the GUI builder and outside of it. https://www.codenameone.com/javadoc/com/codename1/ui/list/GenericListCellRenderer.html[GenericListCellRenderer] is a renderer designed to be as simple to use as a `Component`-`Container` hierarchy, we effectively crammed most of the common renderer use cases into one class. To enable that, we need to know the content of the objects within the model, so the `GenericListCellRenderer` assumes the model contains only `Map` objects. Since `Maps` can contain arbitrary data the list model is still quite generic and allows storing application specific data. Furthermore a `Map` can still be derived and extended to provide domain specific business logic. The `GenericListCellRenderer` accepts two container instances (more later on why at least two, and not one), which it maps to individual `Map` entries within the model, by finding the appropriate components within the given container hierarchy. Components are mapped to the `Map` entries based on the name property of the component (`getName`/`setName`) and the key/value within the `Map`, e.g.: For a model that contains a `Map` entry like this: ---- "Foo": "Bar" "X": "Y" "Not": "Applicable" "Number": Integer(1) ---- A renderer will loop over the component hierarchy in the container, searching for components whose name matches Foo, X, Not, and Number, and assigning the appropriate value to them. TIP: You can also use image objects as values, and they will be assigned to labels as expected. However, you can't assign both an image and a text to a single label, since the key will be taken. That isn't a big problem, since two labels can be used quite easily in such a renderer. To make matters even more attractive the renderer seamlessly supports list tickering when appropriate, and if a https://www.codenameone.com/javadoc/com/codename1/ui/CheckBox.html[CheckBox] appears within the renderer, it will toggle a boolean flag within the `Map` seamlessly. One issue that crops up with this approach is that, if a value is missing from the `Map`, it is treated as empty and the component is reset. This can pose an issue if we hardcode an image or text within the renderer and we don't want them replaced (e.g. an arrow graphic on a `Label` within the renderer). The solution for this is to name the component with Fixed in the end of the name, e.g. `HardcodedIconFixed`. Naming a component within the renderer with $number will automatically set it as a counter component for the offset of the component within the list. Styling the `GenericListCellRenderer` is slightly different, the renderer uses the `UIID` of the `Container` passed to the generic list cell renderer, and the background focus uses that same `UIID` with the word "Focus" appended to it. It is important to notice that the generic list cell renderer will grant focus to the child components of the selected entry if they are focusable, thus changing the style of said entries. E.g. a https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container] might have a child `Label` that has one style when the parent container is unselected and another when it's selected (focused), this can be easily achieved by defining the label as focusable. Notice that the component will never receive direct focus, since it is still part of a renderer. Last but not least, the generic list cell renderer accepts two or four instances of a Container, rather than the obvious choice of accepting only one instance. This allows the renderer to treat the selected entry differently, which is especially important to tickering, although it's also useful for the fisheye effect footnote:[Fisheye is an effect where the selection stays in place as the list moves around it]. Since it might not be practical to seamlessly clone the `Container` for the renderer's needs, Codename One expects the developer to provide two separate instances, they can be identical in all respects, but they must be separate instances for tickering to work. The renderer also allows for a fisheye effect, where the selected entry is actually different from the unselected entry in its structure, it also allows for a pinstripe effect, where odd/even rows have different styles (this is accomplished by providing 4 instances of the containers selected/unselected for odd/even). The best way to learn about the generic list cell renderer and the `Map` model is by playing with them in the old GUI builder. Notice they can be used in code without any dependency on the GUI builder and can be quite useful at that. Here is a simple example of a list with checkboxes that gets updated automatically: [source,java] ---- com.codename1.ui.List list = new com.codename1.ui.List(createGenericListCellRendererModelData()); list.setRenderer(new GenericListCellRenderer(createGenericRendererContainer(), createGenericRendererContainer())); private Container createGenericRendererContainer() { Label name = new Label(); name.setFocusable(true); name.setName("Name"); Label surname = new Label(); surname.setFocusable(true); surname.setName("Surname"); CheckBox selected = new CheckBox(); selected.setName("Selected"); selected.setFocusable(true); Container c = BorderLayout.center(name). add(BorderLayout.SOUTH, surname). add(BorderLayout.WEST, selected); c.setUIID("ListRenderer"); return c; } private Object[] createGenericListCellRendererModelData() { Map[] data = new HashMap[5]; data[0] = new HashMap<>(); data[0].put("Name", "Shai"); data[0].put("Surname", "Almog"); data[0].put("Selected", Boolean.TRUE); data[1] = new HashMap<>(); data[1].put("Name", "Chen"); data[1].put("Surname", "Fishbein"); data[1].put("Selected", Boolean.TRUE); data[2] = new HashMap<>(); data[2].put("Name", "Ofir"); data[2].put("Surname", "Leitner"); data[3] = new HashMap<>(); data[3].put("Name", "Yaniv"); data[3].put("Surname", "Vakarat"); data[4] = new HashMap<>(); data[4].put("Name", "Meirav"); data[4].put("Surname", "Nachmanovitch"); return data; } ---- .GenericListCellRenderer demo code image::img/developer-guide/components-generic-list-cell-renderer.png[GenericListCellRenderer demo code,scaledwidth=20%] ===== Custom UIID Of Entry in GenenricListCellRenderer/MultiList With https://www.codenameone.com/javadoc/com/codename1/ui/list/MultiList.html[MultiList]/`GenenricListCellRenderer` one of the common issues is making a UI where a specific component within the list renderer has a different UIID style based on data. E.g. this can be helpful to mark a label within the list as red, for instance, in the case of a list of monetary transactions. This can be achieved with a custom renderer, but that is a pretty difficult task. + `GenericListCellRenderer` (`MultiList` uses `GenericListCellRenderer` internally) has another option. Normally, to build the model for a renderer of this type, we use something like: [source,java] ---- map.put("componentName", "Component Value"); ---- What if we want componentName to be red? Just use: [source,java] ---- map.put("componentName_uiid", "red"); ---- This will apply the UIID "red" to the component, which you can then style in the theme. Notice that once you start doing this, you need to define this entry for all entries, e.g.: [source,java] ---- map.put("componentName_uiid", "blue"); ---- Otherwise the component will stay red for the next entry (since the renderer acts like a rubber stamp). ===== Rendering Prototype Because of the rendering architecture of a `List` its pretty hard to calculate the right preferred size for such a component. The default behavior includes querying a few entries from the model then constructing their renderers to get a "sample" of the preferred size value. As you might guess this triggers a performance penalty that is paid with every reflow of the UI. The solution is to use `setRenderingPrototype`. `setRenderingPrototype` accepts a "fake" value that represents a reasonably large amount of data and it will be used to calculate the preferred size. E.g. for a multiList that should render 2 lines of text with 20 characters and a 5mm square icon I can do something like this: [source,java] ---- Map proto = new HashMap<>(); map.put("Line1", "WWWWWWWWWWWWWWWWWWWW"); map.put("Line2", "WWWWWWWWWWWWWWWWWWWW"); int mm5 = Display.getInstance().convertToPixels(5, true); map.put("icon", Image.create(mm5, mm5)); myMultiList.setRenderingPrototype(map); ---- ==== ComboBox The https://www.codenameone.com/javadoc/com/codename1/ui/ComboBox.html[ComboBox] is a specialization of `List` that displays a single selected entry. When clicking that entry a popup is presented allowing the user to pick an entry from the full list of entries. TIP: The `ComboBox` UI paradigm isn't as common on OS's such as iOS where there is no native equivalent to it. We recommend using either the https://www.codenameone.com/javadoc/com/codename1/ui/spinner/Picker.html[Picker] class or the https://www.codenameone.com/javadoc/com/codename1/ui/AutoCompleteTextField.html[AutoCompleteTextField]. `ComboBox` is notoriously hard to style properly as it relies on a complex dynamic of popup renderer and instantly visible renderer. The `UIID` for the `ComboBox` is `ComboBox` however if you set it to something else all the other `UIID's` will also change their prefix. E.g. the `ComboBoxPopup` `UIID` will become `MyNewUIIDPopup`. The combo box defines the following UIID's by default: - `ComboBox` - `ComboBoxItem` - `ComboBoxFocus` - `PopupContentPane` - `PopupItem` - `PopupFocus` The `ComboBox` also defines theme constants that allow some native themes to manipulate its behavior e.g.: - `popupTitleBool` - shows the "label for" value as the title of the popup dialog - `popupCancelBodyBool` - Adds a cancel button into the popup dialog - `centeredPopupBool` - shows the popup dialog in the center of the screen instead of under the popup - `otherPopupRendererBool` - Uses a different list cell render for the popup than the one used for the `ComboBox` itself. When this is `false` `PopupItem` & `PopupFocus` become irrelevant. Notice that the Android native theme defines this to `true`. Since a `ComboBox` is really a `List` you can use everything we learned about a `List` to build a `ComboBox` including models, `GenericListCellRenderer` etc. E.g. the demo below uses the GRRM demo data from above to build a `ComboBox`: [source,java] ---- Form hi = new Form("ComboBox", new BoxLayout(BoxLayout.Y_AXIS)); ComboBox> combo = new ComboBox<> ( createListEntry("A Game of Thrones", "1996"), createListEntry("A Clash Of Kings", "1998"), createListEntry("A Storm Of Swords", "2000"), createListEntry("A Feast For Crows", "2005"), createListEntry("A Dance With Dragons", "2011"), createListEntry("The Winds of Winter", "2016 (please, please, please)"), createListEntry("A Dream of Spring", "Ugh")); combo.setRenderer(new GenericListCellRenderer<>(new MultiButton(), new MultiButton())); ---- .GRRM ComboBox image::img/developer-guide/components-combobox.png[GRRM ComboBox,scaledwidth=25%] === Slider A https://www.codenameone.com/javadoc/com/codename1/ui/Slider.html[Slider] is an empty component that can be filled horizontally or vertically to allow indicating progress, setting volume etc. It can be editable to allow the user to determine its value or none editable to just relay that information to the user. It can have a thumb on top to show its current position. .Slider image::img/developer-guide/slider.png[Slider,scaledwidth=25%] The interesting part about the slider is that it has two separate style `UIID’s`, `Slider` & `SliderFull`. The `Slider` `UIID` is always painted and `SliderFull` is rendered on top based on the amount the `Slider` should be filled. `Slider` is highly customizable e.g. a slider can be used to replicate a 5 star rating widget as such. Notice that this slider will only work when its given its preferred size otherwise additional stars will appear. That's why we place it within a `FlowLayout`: [source,java] ---- Form hi = new Form("Star Slider", new BoxLayout(BoxLayout.Y_AXIS)); hi.add(FlowLayout.encloseCenter(createStarRankSlider())); hi.show(); ---- The slider itself is initialized in the code below. Notice that you can achieve almost the same result using a theme by setting the `Slider` & `SliderFull` UIID's (both in selected & unselected states). In fact doing this in the theme might be superior as you could use one image that contains 5 stars already and that way you won't need the preferred size hack below: [[slider-stars-demo]] [source,java] ---- private void initStarRankStyle(Style s, Image star) { s.setBackgroundType(Style.BACKGROUND_IMAGE_TILE_BOTH); s.setBorder(Border.createEmpty()); s.setBgImage(star); s.setBgTransparency(0); } private Slider createStarRankSlider() { Slider starRank = new Slider(); starRank.setEditable(true); starRank.setMinValue(0); starRank.setMaxValue(10); Font fnt = Font.createTrueTypeFont("native:MainLight", "native:MainLight"). derive(Display.getInstance().convertToPixels(5, true), Font.STYLE_PLAIN); Style s = new Style(0xffff33, 0, fnt, (byte)0); Image fullStar = FontImage.createMaterial(FontImage.MATERIAL_STAR, s).toImage(); s.setOpacity(100); s.setFgColor(0); Image emptyStar = FontImage.createMaterial(FontImage.MATERIAL_STAR, s).toImage(); initStarRankStyle(starRank.getSliderEmptySelectedStyle(), emptyStar); initStarRankStyle(starRank.getSliderEmptyUnselectedStyle(), emptyStar); initStarRankStyle(starRank.getSliderFullSelectedStyle(), fullStar); initStarRankStyle(starRank.getSliderFullUnselectedStyle(), fullStar); starRank.setPreferredSize(new Dimension(fullStar.getWidth() * 5, fullStar.getHeight())); return starRank; } private void showStarPickingForm() { Form hi = new Form("Star Slider", new BoxLayout(BoxLayout.Y_AXIS)); hi.add(FlowLayout.encloseCenter(createStarRankSlider())); hi.show(); } ---- .Star Slider set to 5 (its between 0 - 10) image::img/developer-guide/components-slider.png[Star Slider set to 5 (its between 0 - 10),scaledwidth=25%] TIP: This slider goes all the way to 0 stars which is less common. You can use a `Label` to represent the first star and have the slider work between 0 - 8 values to provide 4 additional stars. [[table-section]] === Table https://www.codenameone.com/javadoc/com/codename1/ui/table/Table.html[Table] is a composite component (but it isn't a <>), this means it is a subclass of https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container]. It's effectively built from multiple components. TIP: `Table` is heavily based on the https://www.codenameone.com/javadoc/com/codename1/ui/table/TableLayout.html[TableLayout] class. It's important to be familiar with that layout manager when working with `Table`. Here is a trivial sample of using the standard table component: [source,java] ---- Form hi = new Form("Table", new BorderLayout()); TableModel model = new DefaultTableModel(new String[] {"Col 1", "Col 2", "Col 3"}, new Object[][] { {"Row 1", "Row A", "Row X"}, {"Row 2", "Row B", "Row Y"}, {"Row 3", "Row C", "Row Z"}, {"Row 4", "Row D", "Row K"}, }) { public boolean isCellEditable(int row, int col) { return col != 0; } }; Table table = new Table(model); hi.add(BorderLayout.CENTER, table); hi.show(); ---- .Simple Table usage image::img/developer-guide/components-table.png[Simple Table usage,scaledwidth=20%] NOTE: In the sample above the title area and first column aren't editable. The other two columns are editable. The more "interesting" capabilities of the `Table` class can be utilized via the `TableLayout`. You can use the layout constraints (also exposed in the table class) to create spanning and elaborate UI's. E.g.: [source,java] ---- Form hi = new Form("Table", new BorderLayout()); TableModel model = new DefaultTableModel(new String[] {"Col 1", "Col 2", "Col 3"}, new Object[][] { {"Row 1", "Row A", "Row X"}, {"Row 2", "Row B can now stretch", null}, {"Row 3", "Row C", "Row Z"}, {"Row 4", "Row D", "Row K"}, }) { public boolean isCellEditable(int row, int col) { return col != 0; } }; Table table = new Table(model) { @Override protected TableLayout.Constraint createCellConstraint(Object value, int row, int column) { TableLayout.Constraint con = super.createCellConstraint(value, row, column); if(row == 1 && column == 1) { con.setHorizontalSpan(2); } con.setWidthPercentage(33); return con; } }; hi.add(BorderLayout.CENTER, table); ---- .Table with spanning & fixed widths to 33% image::img/developer-guide/components-table-with-spanning.png[Table with spanning and fixed widths to 33%,scaledwidth=20%] In order to customize the table cell behavior you can derive the `Table` to create a "renderer like" widget, however unlike the list this component is "kept" and used as is. This means you can bind listeners to this component and work with it as you would with any other component in Codename One. So lets fix the example above to include far more capabilities: [source,java] ---- Table table = new Table(model) { @Override protected Component createCell(Object value, int row, int column, boolean editable) { // <1> Component cell; if(row == 1 && column == 1) { // <2> Picker p = new Picker(); p.setType(Display.PICKER_TYPE_STRINGS); p.setStrings("Row B can now stretch", "This is a good value", "So Is This", "Better than text field"); p.setSelectedString((String)value); // <3> p.setUIID("TableCell"); p.addActionListener((e) -> getModel().setValueAt(row, column, p.getSelectedString())); // <4> cell = p; } else { cell = super.createCell(value, row, column, editable); } if(row > -1 && row % 2 == 0) { // <5> // pinstripe effect cell.getAllStyles().setBgColor(0xeeeeee); cell.getAllStyles().setBgTransparency(255); } return cell; } @Override protected TableLayout.Constraint createCellConstraint(Object value, int row, int column) { TableLayout.Constraint con = super.createCellConstraint(value, row, column); if(row == 1 && column == 1) { con.setHorizontalSpan(2); } con.setWidthPercentage(33); return con; } }; ---- <1> The `createCell` method is invoked once per component but is similar conceptually to the `List` renderer. Notice that it doesn't return a "rubber stamp" though, it returns a full component. <2> We only apply the picker to one cell for simplicities sake. <3> We need to set the value of the component manually, this is crucial since the `Table` doesn't "see" this. <4> We need to track the event and update the model in this case as the `Table` isn't aware of the data change. <5> We set the "pinstripe" effect by coloring even rows. Notice that unlike renderers we only need to apply the coloring once as the `Components` are stateful. .Table with customize cells using the pinstripe effect image::img/developer-guide/components-table-pinstripe.png[Table with customize cells using the pinstripe effect,scaledwidth=20%] .Picker table cell during edit image::img/developer-guide/components-table-pinstripe-edit.png[Picker table cell during edit,scaledwidth=20%] To line wrap table cells we can just override the `createCell` method and return a https://www.codenameone.com/javadoc/com/codename1/ui/TextArea.html[TextArea] instead of a https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html[TextField] since the `TextArea` defaults to the multi-line behavior this should work seamlessly. E.g.: [source,java] ---- Form hi = new Form("Table", new BorderLayout()); TableModel model = new DefaultTableModel(new String[] {"Col 1", "Col 2", "Col 3"}, new Object[][] { {"Row 1", "Row A", "Row X"}, {"Row 2", "Row B can now stretch very long line that should span multiple rows as much as possible", "Row Y"}, {"Row 3", "Row C", "Row Z"}, {"Row 4", "Row D", "Row K"}, }) { public boolean isCellEditable(int row, int col) { return col != 0; } }; Table table = new Table(model) { @Override protected Component createCell(Object value, int row, int column, boolean editable) { TextArea ta = new TextArea((String)value); ta.setUIID("TableCell"); return ta; } @Override protected TableLayout.Constraint createCellConstraint(Object value, int row, int column) { TableLayout.Constraint con = super.createCellConstraint(value, row, column); con.setWidthPercentage(33); return con; } }; hi.add(BorderLayout.CENTER, table); hi.show(); ---- TIP: Notice that we don't really need to do anything else as binding to the `TextArea` is builtin to the `Table`. IMPORTANT: We must set the column width constraint when we want multi-line to work. Otherwise the preferred size of the column might be too wide and the remaining columns might not have space left. .Multiline table cell in portrait mode image::img/developer-guide/components-table-multiline-portrait.png[Multiline table cell in portrait mode,scaledwidth=20%] .Multiline table cell in landscape mode. Notice the cell row count adapts seamlessly image::img/developer-guide/components-table-multiline-landscape.png[Multiline table cell in landscape mode. Notice the cell row count adapts seamlessly,scaledwidth=20%] ==== Sorting Tables Sorting tables by clicking the titles is something that should generally work out of the box by using an API like `setSortSupported(true)`. [source,java] ---- Form hi = new Form("Table", new BorderLayout()); TableModel model = new DefaultTableModel(new String[] {"Col 1", "Col 2", "Col 3"}, new Object[][] { {"Row 1", "Row A", 1}, {"Row 2", "Row B", 4}, {"Row 3", "Row C", 7.5}, {"Row 4", "Row D", 2.24}, }); Table table = new Table(model); table.setSortSupported(true); hi.add(BorderLayout.CENTER, table); hi.add(NORTH, new Button("Button")); hi.show(); ---- Notice this works with numbers, Strings and might work with dates but you can generally support any object type by overriding the method `protected Comparator createColumnSortComparator(int column)` which should return a comparator for your custom object type in the column. [[tree-section]] === Tree https://www.codenameone.com/javadoc/com/codename1/ui/tree/Tree.html[Tree] allows displaying hierarchical data such as folders and files in a collapsible/expandable UI. Like the <> it is a composite component (but it isn't a <>). Like the `Table` it works in consort with a model to construct its user interface on the fly but doesn't use a stateless renderer (as `List` does). The data of the `Tree` arrives from a model model e.g. this: [source,java] ---- class StringArrayTreeModel implements TreeModel { String[][] arr = new String[][] { {"Colors", "Letters", "Numbers"}, {"Red", "Green", "Blue"}, {"A", "B", "C"}, {"1", "2", "3"} }; public Vector getChildren(Object parent) { if(parent == null) { Vector v = new Vector(); for(int iter = 0 ; iter < arr[0].length ; iter++) { v.addElement(arr[0][iter]); } return v; } Vector v = new Vector(); for(int iter = 0 ; iter < arr[0].length ; iter++) { if(parent == arr[0][iter]) { if(arr.length > iter + 1 && arr[iter + 1] != null) { for(int i = 0 ; i < arr[iter + 1].length ; i++) { v.addElement(arr[iter + 1][i]); } } } } return v; } public boolean isLeaf(Object node) { Vector v = getChildren(node); return v == null || v.size() == 0; } } Tree dt = new Tree(new StringArrayTreeModel()); ---- Will result in this: .Tree image::img/developer-guide/tree.png[Tree,scaledwidth=20%] NOTE: Since `Tree` is hierarchy based we can't have a simple model like we have for the `Table` as deep hierarchy is harder to represent with arrays. A more practical "real world" example would be working with XML data. We can use something like this to show an XML `Tree`: [source,java] ---- Form hi = new Form("XML Tree", new BorderLayout()); InputStream is = Display.getInstance().getResourceAsStream(getClass(), "/build.xml"); try(Reader r = new InputStreamReader(is, "UTF-8");) { Element e = new XMLParser().parse(r); Tree xmlTree = new Tree(new XMLTreeModel(e)) { @Override protected String childToDisplayLabel(Object child) { if(child instanceof Element) { return ((Element)child).getTagName(); } return child.toString(); } }; hi.add(BorderLayout.CENTER, xmlTree); } catch(IOException err) { Log.e(err); } ---- NOTE: The `try(Stream)` syntax is a try with resources clogic that implicitly closes the stream. .XML Tree image::img/developer-guide/components-tree-xml.png[XML Tree,scaledwidth=20%] The model for the XML hierarchy is implemented as such: [source,java] ---- class XMLTreeModel implements TreeModel { private Element root; public XMLTreeModel(Element e) { root = e; } public Vector getChildren(Object parent) { if(parent == null) { Vector c = new Vector(); c.addElement(root); return c; } Vector result = new Vector(); Element e = (Element)parent; for(int iter = 0 ; iter < e.getNumChildren() ; iter++) { result.addElement(e.getChildAt(iter)); } return result; } public boolean isLeaf(Object node) { Element e = (Element)node; return e.getNumChildren() == 0; } } ---- [[sharebutton-section]] === ShareButton https://www.codenameone.com/javadoc/com/codename1/components/ShareButton.html[ShareButton] is a button you can add into the UI to let a user share an image or block of text. The `ShareButton` uses a set of predefined share options on the simulator. On Android & iOS the `ShareButton` is mapped to the OS native sharing functionality and can share the image/text with the services configured on the device (e.g. Twitter, Facebook etc.). TIP: Sharing text is trivial but to share an image we need to save it to the https://www.codenameone.com/javadoc/com/codename1/io/FileSystemStorage.html[FileSystemStorage]. Notice that saving to Storage *won't work*! In the sample code below we take a screenshot which is saved to https://www.codenameone.com/javadoc/com/codename1/io/FileSystemStorage.html[FileSystemStorage] for sharing: [source,java] ---- Form hi = new Form("ShareButton"); ShareButton sb = new ShareButton(); sb.setText("Share Screenshot"); hi.add(sb); Image screenshot = Image.createImage(hi.getWidth(), hi.getHeight()); hi.revalidate(); hi.setVisible(true); hi.paintComponent(screenshot.getGraphics(), true); String imageFile = FileSystemStorage.getInstance().getAppHomePath() + "screenshot.png"; try(OutputStream os = FileSystemStorage.getInstance().openOutputStream(imageFile);) { ImageIO.getImageIO().save(screenshot, os, ImageIO.FORMAT_PNG, 1); } catch(IOException err) { Log.e(err); } sb.setImageToShare(imageFile, "image/png"); ---- .The share button running on the simulator image::img/developer-guide/components-sharebutton.png[The share button running on the simulator,scaledwidth=20%] IMPORTANT: `ShareButton` behaves very differently on the device... .The share button running on the Android device and screenshot sent into twitter image::img/developer-guide/components-sharebutton-android.png[The share button running on the Android device and screenshot sent into twitter,scaledwidth=50%] IMPORTANT: The `ShareButton` features some share service classes to allow plugging in additional share services. However, this functionality is only relevant to devices where native sharing isn't supported. So this code isn't used on iOS/Android... === Tabs The https://www.codenameone.com/javadoc/com/codename1/ui/Tabs.html[Tabs] `Container` arranges components into groups within "tabbed" containers. `Tabs` is a container type that allows leafing through its children using labeled toggle buttons. The tabs can be placed in multiple different ways (top, bottom, left or right) with the default being determined by the platform. This class also allows swiping between components to leaf between said tabs (for this purpose the tabs themselves can also be hidden). Since `Tabs` are a `Container` its a common mistake to try and add a `Tab` using the `add` method. That method won't work since a `Tab` can have both an `Image` and text String associated with it. [source,java] ---- Form hi = new Form("Tabs", new BorderLayout()); Tabs t = new Tabs(); Style s = UIManager.getInstance().getComponentStyle("Tab"); FontImage icon1 = FontImage.createMaterial(FontImage.MATERIAL_QUESTION_ANSWER, s); Container container1 = BoxLayout.encloseY(new Label("Label1"), new Label("Label2")); t.addTab("Tab1", icon1, container1); t.addTab("Tab2", new SpanLabel("Some text directly in the tab")); hi.add(BorderLayout.CENTER, t); ---- .Simple usage of Tabs image::img/developer-guide/components-tabs.png[Simple usage of Tabs,scaledwidth=20%] A common usage for `Tabs` is the the swipe to proceed effect which is very common in iOS applications. In the code below we use https://www.codenameone.com/javadoc/com/codename1/ui/RadioButton.html[RadioButton] and https://www.codenameone.com/javadoc/com/codename1/ui/layouts/LayeredLayout.html[LayeredLayout] with hidden tabs to produce that effect: [source,java] ---- Form hi = new Form("Swipe Tabs", new LayeredLayout()); Tabs t = new Tabs(); t.hideTabs(); Style s = UIManager.getInstance().getComponentStyle("Button"); FontImage radioEmptyImage = FontImage.createMaterial(FontImage.MATERIAL_RADIO_BUTTON_UNCHECKED, s); FontImage radioFullImage = FontImage.createMaterial(FontImage.MATERIAL_RADIO_BUTTON_CHECKED, s); ((DefaultLookAndFeel)UIManager.getInstance().getLookAndFeel()).setRadioButtonImages(radioFullImage, radioEmptyImage, radioFullImage, radioEmptyImage); Container container1 = BoxLayout.encloseY(new Label("Swipe the tab to see more"), new Label("You can put anything here")); t.addTab("Tab1", container1); t.addTab("Tab2", new SpanLabel("Some text directly in the tab")); RadioButton firstTab = new RadioButton(""); RadioButton secondTab = new RadioButton(""); firstTab.setUIID("Container"); secondTab.setUIID("Container"); new ButtonGroup(firstTab, secondTab); firstTab.setSelected(true); Container tabsFlow = FlowLayout.encloseCenter(firstTab, secondTab); hi.add(t); hi.add(BorderLayout.south(tabsFlow)); t.addSelectionListener((i1, i2) -> { switch(i2) { case 0: if(!firstTab.isSelected()) { firstTab.setSelected(true); } break; case 1: if(!secondTab.isSelected()) { secondTab.setSelected(true); } break; } }); ---- .Swipeable Tabs with an iOS carousel effect page 1 image::img/developer-guide/components-tabs-swipe1.png[Swipeable Tabs with an iOS carousel effect page 1,scaledwidth=20%] .Swipeable Tabs with an iOS carousel effect page 2 image::img/developer-guide/components-tabs-swipe2.png[Swipeable Tabs with an iOS carousel effect page 2,scaledwidth=20%] NOTE: Notice that we used `setRadioButtonImages` to explicitly set the radio button images to the look we want for the carousel. [[mediamanager-section]] === MediaManager & MediaPlayer // HTML_ONLY_START IMPORTANT: `MediaPlayer` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues https://www.codenameone.com/manual/advanced-topics.html#native-peer-components[here]. // HTML_ONLY_END //// //PDF_ONLY IMPORTANT: `MediaPlayer` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues <>. //// The https://www.codenameone.com/javadoc/com/codename1/components/MediaPlayer.html[MediaPlayer] allows you to control video playback. To use the `MediaPlayer` we need to first load the `Media` object from the https://www.codenameone.com/javadoc/com/codename1/media/MediaManager.html[MediaManager]. The `MediaManager` is the core class responsible for media interaction in Codename One. TIP: You should also check out the https://www.codenameone.com/javadoc/com/codename1/capture/Capture.html[Capture] class for things that aren't covered by the `MediaManager`. In the demo code below we use the gallery functionality to pick a video from the device's video gallery. [source,java] ---- final Form hi = new Form("MediaPlayer", new BorderLayout()); hi.setToolbar(new Toolbar()); Style s = UIManager.getInstance().getComponentStyle("Title"); FontImage icon = FontImage.createMaterial(FontImage.MATERIAL_VIDEO_LIBRARY, s); hi.getToolbar().addCommandToRightBar("", icon, (evt) -> { Display.getInstance().openGallery((e) -> { if(e != null && e.getSource() != null) { String file = (String)e.getSource(); try { Media video = MediaManager.createMedia(file, true); hi.removeAll(); hi.add(BorderLayout.CENTER, new MediaPlayer(video)); hi.revalidate(); } catch(IOException err) { Log.e(err); } } }, Display.GALLERY_VIDEO); }); hi.show(); ---- .Video playback running on the simulator image::img/developer-guide/components-mediaplayer.png[Video playback running on the simulator,scaledwidth=25%] .Video playback running on an Android device. Notice the native playback controls that appear when the video is tapped image::img/developer-guide/components-mediaplayer-android.png[Video playback running on an Android device. Notice the native playback controls that appear when the video is tapped,scaledwidth=25%] IMPORTANT: Video playback in the simulator will only work with JavaFX enabled. This is the default for Java 8 or newer so we recommend using that. === ImageViewer The https://www.codenameone.com/javadoc/com/codename1/components/ImageViewer.html[ImageViewer] allows us to inspect, zoom and pan into an image. It also allows swiping between images if you have a set of images (using an image list model). IMPORTANT: The `ImageViewer` is a complex rich component designed for user interaction. If you just want to display an image use https://www.codenameone.com/javadoc/com/codename1/ui/Label.html[Label] if you want the image to scale seamlessly use https://www.codenameone.com/javadoc/com/codename1/components/ScaleImageLabel.html[ScaleImageLabel]. You can use the `ImageViewer` as a tool to view a single image which allows you to zoom in/out to that image as such: [source,java] ---- Form hi = new Form("ImageViewer", new BorderLayout()); ImageViewer iv = new ImageViewer(duke); hi.add(BorderLayout.CENTER, iv); ---- TIP: You can simulate pinch to zoom on the simulator by dragging the right button away from the top left corner to zoom in and towards the top left corner to zoom out. On Mac touchpads you can drag two fingers to achieve that. .ImageViewer as the demo loads with the image from the default icon image::img/developer-guide/components-imageviewer.png[ImageViewer as the demo loads with the image from the default icon,scaledwidth=20%] .ImageViewer zoomed in image::img/developer-guide/components-imageviewer-zoomed-in.png[ImageViewer zoomed in,scaledwidth=20%] We can work with a list of images to produce a swiping effect for the image viewer where you can swipe from one image to the next and also zoom in/out on a specific image: [source,java] ---- Form hi = new Form("ImageViewer", new BorderLayout()); Image red = Image.createImage(100, 100, 0xffff0000); Image green = Image.createImage(100, 100, 0xff00ff00); Image blue = Image.createImage(100, 100, 0xff0000ff); Image gray = Image.createImage(100, 100, 0xffcccccc); ImageViewer iv = new ImageViewer(red); iv.setImageList(new DefaultListModel<>(red, green, blue, gray)); hi.add(BorderLayout.CENTER, iv); ---- .An ImageViewer with multiple elements is indistinguishable from a single ImageViewer with the exception of swipe image::img/developer-guide/components-imageviewer-multi.png[An ImageViewer with multiple elements is indistinguishable from a single ImageViewer with the exception of swipe,scaledwidth=20%] Notice that we use a https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] to allow swiping between images. TIP: `EncodedImage's` aren't always fully loaded and so when you swipe if the images are really large you might see delays! You can dynamically download images directly into the `ImageViewer` with a custom list model like this: [source,java] ---- Form hi = new Form("ImageViewer", new BorderLayout()); final EncodedImage placeholder = EncodedImage.createFromImage( FontImage.createMaterial(FontImage.MATERIAL_SYNC, s). scaled(300, 300), false); class ImageList implements ListModel { private int selection; private String[] imageURLs = { "http://awoiaf.westeros.org/images/thumb/9/93/AGameOfThrones.jpg/300px-AGameOfThrones.jpg", "http://awoiaf.westeros.org/images/thumb/3/39/AClashOfKings.jpg/300px-AClashOfKings.jpg", "http://awoiaf.westeros.org/images/thumb/2/24/AStormOfSwords.jpg/300px-AStormOfSwords.jpg", "http://awoiaf.westeros.org/images/thumb/a/a3/AFeastForCrows.jpg/300px-AFeastForCrows.jpg", "http://awoiaf.westeros.org/images/7/79/ADanceWithDragons.jpg" }; private Image[] images; private EventDispatcher listeners = new EventDispatcher(); public ImageList() { this.images = new EncodedImage[imageURLs.length]; } public Image getItemAt(final int index) { if(images[index] == null) { images[index] = placeholder; Util.downloadUrlToStorageInBackground(imageURLs[index], "list" + index, (e) -> { try { images[index] = EncodedImage.create(Storage.getInstance().createInputStream("list" + index)); listeners.fireDataChangeEvent(index, DataChangedListener.CHANGED); } catch(IOException err) { err.printStackTrace(); } }); } return images[index]; } public int getSize() { return imageURLs.length; } public int getSelectedIndex() { return selection; } public void setSelectedIndex(int index) { selection = index; } public void addDataChangedListener(DataChangedListener l) { listeners.addListener(l); } public void removeDataChangedListener(DataChangedListener l) { listeners.removeListener(l); } public void addSelectionListener(SelectionListener l) { } public void removeSelectionListener(SelectionListener l) { } public void addItem(Image item) { } public void removeItem(int index) { } }; ImageList imodel = new ImageList(); ImageViewer iv = new ImageViewer(imodel.getItemAt(0)); iv.setImageList(imodel); hi.add(BorderLayout.CENTER, iv); ---- .Dynamically fetching an image URL from the internet footnote:[Image was fetched from http://awoiaf.westeros.org/index.php/Portal:Books] image::img/developer-guide/components-imageviewer-dynamic.png[Dynamically fetching an image URL from the internet,scaledwidth=20%] This fetches the images in the URL asynchronously and fires a data change event when the data arrives to automatically refresh the `ImageViewer` when that happens. === ScaleImageLabel & ScaleImageButton https://www.codenameone.com/javadoc/com/codename1/components/ScaleImageLabel.html[ScaleImageLabel] & https://www.codenameone.com/javadoc/com/codename1/components/ScaleImageButton.html[ScaleImageButton] allow us to position an image that will grow/shrink to fit available space. In that sense they differ from <> & <> which keeps the image at the same size. TIP: The default UIID of `ScaleImageLabel` is "`Label`", however the default UIID of `ScaleImageButton` is "`ScaleImageButton`". The reasoning for the difference is that the "`Button`" UIID includes a border and a lot of legacy. You can use `ScaleImageLabel`/`ScaleImageButton` interchangeably. The only major difference between these components is the buttons ability to handle click events/focus. Here is a simple example that also shows the difference between the `scale to fill` and `scale to fit` modes: [source,java] ---- TableLayout tl = new TableLayout(2, 2); Form hi = new Form("ScaleImageButton/Label", tl); Style s = UIManager.getInstance().getComponentStyle("Button"); Image icon = FontImage.createMaterial(FontImage.MATERIAL_WARNING, s); ScaleImageLabel fillLabel = new ScaleImageLabel(icon); fillLabel.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL); ScaleImageButton fillButton = new ScaleImageButton(icon); fillButton.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL); hi.add(tl.createConstraint().widthPercentage(20), new ScaleImageButton(icon)). add(tl.createConstraint().widthPercentage(80), new ScaleImageLabel(icon)). add(fillLabel). add(fillButton); hi.show(); ---- .ScaleImageLabel/Button, the top row includes scale to fit versions (the default) whereas the bottom row includes the scale to fill versions image::img/developer-guide/components-scaleimage.png[ScaleImageLabel/Button the top row includes scale to fit versions (the default) whereas the bottom row includes the scale to fill versions,scaledwidth=25%] WARNING: When styling these components keep in mind that changing attributes such as background behavior might cause an issue with their functionality or might not work. === Toolbar The https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar] API provides deep customization of the title bar area with more flexibility e.g. placing a https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html[TextField] for search or buttons in arbitrary title area positions. The `Toolbar` API replicates some of the native functionality available on Android/iOS and integrates with features such as the side menu to provide very fine grained control over the title area behavior. The `Toolbar` needs to be installed into the `Form` in order for it to work. You can setup the Toolbar in one of these three ways: . `form.setToolbar(new Toolbar());` - allows you to activate the `Toolbar` to a specific `Form` and not for the entire application . `Toolbar.setGlobalToolbar(true);` - enables the `Toolbar` for all the forms in the app . Theme constant `globalToobarBool` - this is equivalent to `Toolbar.setGlobalToolbar(true);` The basic functionality of the `Toolbar` includes the ability to add a command to the following 4 places: - Left side of the title - `addCommandToLeftBar` - Right side of the title - `addCommandToRightBar` - Side menu bar (the drawer that opens when you click the icon on the top left or swipe the screen from left to right) - `addCommandToSideMenu` - Overflow menu (the menu that opens when you tap the 3 vertical dots in the top right corner) - `addCommandToOverflowMenu` The code below provides a brief overview of these options: [source,java] ---- Toolbar.setGlobalToolbar(true); Form hi = new Form("Toolbar", new BoxLayout(BoxLayout.Y_AXIS)); hi.getToolbar().addCommandToLeftBar("Left", icon, (e) -> Log.p("Clicked")); hi.getToolbar().addCommandToRightBar("Right", icon, (e) -> Log.p("Clicked")); hi.getToolbar().addCommandToOverflowMenu("Overflow", icon, (e) -> Log.p("Clicked")); hi.getToolbar().addCommandToSideMenu("Sidemenu", icon, (e) -> Log.p("Clicked")); hi.show(); ---- .The Toolbar image::img/developer-guide/components-toolbar.png[The Toolbar,scaledwidth=25%] .The default sidemenu of the Toolbar image::img/developer-guide/components-toolbar-sidemenu.png[The default sidemenu of the Toolbar,scaledwidth=30%] .The overflow menu of the Toolbar image::img/developer-guide/components-toolbar-overflow-menu.png[The overflow menu of the Toolbar,scaledwidth=25%] Normally you can just set a title with a `String` but if you would want the component to be a text field or a multi line label you can use `setTitleComponent(Component)` which allows you to install any component into the title area. TIP: The code below demonstrates searching using custom code however the Toolbar also has builtin support for search covered in the next section The customization of the title area allows for some pretty powerful UI effects e.g. the code below allows searching dynamically within a set of entries and uses some very neat tricks: [[Advanced-search-code]] [source,java] ---- Toolbar.setGlobalToolbar(true); Style s = UIManager.getInstance().getComponentStyle("Title"); Form hi = new Form("Toolbar", new BoxLayout(BoxLayout.Y_AXIS)); TextField searchField = new TextField("", "Toolbar Search"); <1> searchField.getHintLabel().setUIID("Title"); searchField.setUIID("Title"); searchField.getAllStyles().setAlignment(Component.LEFT); hi.getToolbar().setTitleComponent(searchField); FontImage searchIcon = FontImage.createMaterial(FontImage.MATERIAL_SEARCH, s); searchField.addDataChangeListener((i1, i2) -> { <2> String t = searchField.getText(); if(t.length() < 1) { for(Component cmp : hi.getContentPane()) { cmp.setHidden(false); cmp.setVisible(true); } } else { t = t.toLowerCase(); for(Component cmp : hi.getContentPane()) { String val = null; if(cmp instanceof Label) { val = ((Label)cmp).getText(); } else { if(cmp instanceof TextArea) { val = ((TextArea)cmp).getText(); } else { val = (String)cmp.getPropertyValue("text"); } } boolean show = val != null && val.toLowerCase().indexOf(t) > -1; cmp.setHidden(!show); <3> cmp.setVisible(show); } } hi.getContentPane().animateLayout(250); }); hi.getToolbar().addCommandToRightBar("", searchIcon, (e) -> { searchField.startEditingAsync(); <4> }); hi.add("A Game of Thrones"). add("A Clash Of Kings"). add("A Storm Of Swords"). add("A Feast For Crows"). add("A Dance With Dragons"). add("The Winds of Winter"). add("A Dream of Spring"); hi.show(); ---- <1> We use a `TextField` the whole time and just style it to make it (and its hint) look like a regular title. An alternative way is to replace the title component dynamically. <2> We use the `DataChangeListener` to update the search results as we type them. <3> Hidden & Visible use the opposite flag values to say similar things (e.g. when hidden is set to false you would want to set visible to true). + Visible indicates whether a component can be seen. It will still occupy the physical space on the screen even when it isn't visible. Hidden will remove the space occupied by the component from the screen, but some code might still try to paint it. Normally, visible is redundant but we use it with hidden for good measure. <4> The search button is totally unnecessary here. We can just click the `TextField`! + However, that isn't intuitive to most users so we added the button to start editing. .Search field within the toolbar image::img/developer-guide/components-toolbar-search.png[Search field within the toolbar,scaledwidth=20%] .Search field after typing a couple of letters image::img/developer-guide/components-toolbar-search-ongoing.png[Search field after typing a couple of letters,scaledwidth=20%] ==== Search Mode While you can implement search manually using the builtin search offers a simpler and more uniform UI. .Builtin toolbar search functionality image::img/developer-guide/toolbar-search-mode.jpg[Builtin toolbar search functionality,scaledwidth=40%] You can customize the appearance of the search bar by using the UIID's: `ToolbarSearch`, `TextFieldSearch` & `TextHintSearch`. In the sample below we fetch all the contacts from the device and enable search thru them, notice it expects and image called `duke.png` which is really just the default Codename One icon renamed and placed in the src folder: [source,java] ---- Image duke = null; try { duke = Image.createImage("/duke.png"); } catch(IOException err) { Log.e(err); } int fiveMM = Display.getInstance().convertToPixels(5); final Image finalDuke = duke.scaledWidth(fiveMM); Toolbar.setGlobalToolbar(true); Form hi = new Form("Search", BoxLayout.y()); hi.add(new InfiniteProgress()); Display.getInstance().scheduleBackgroundTask(()-> { // this will take a while... Contact[] cnts = Display.getInstance().getAllContacts(true, true, true, true, false, false); Display.getInstance().callSerially(() -> { hi.removeAll(); for(Contact c : cnts) { MultiButton m = new MultiButton(); m.setTextLine1(c.getDisplayName()); m.setTextLine2(c.getPrimaryPhoneNumber()); Image pic = c.getPhoto(); if(pic != null) { m.setIcon(fill(pic, finalDuke.getWidth(), finalDuke.getHeight())); } else { m.setIcon(finalDuke); } hi.add(m); } hi.revalidate(); }); }); hi.getToolbar().addSearchCommand(e -> { String text = (String)e.getSource(); if(text == null || text.length() == 0) { // clear search for(Component cmp : hi.getContentPane()) { cmp.setHidden(false); cmp.setVisible(true); } hi.getContentPane().animateLayout(150); } else { text = text.toLowerCase(); for(Component cmp : hi.getContentPane()) { MultiButton mb = (MultiButton)cmp; String line1 = mb.getTextLine1(); String line2 = mb.getTextLine2(); boolean show = line1 != null && line1.toLowerCase().indexOf(text) > -1 || line2 != null && line2.toLowerCase().indexOf(text) > -1; mb.setHidden(!show); mb.setVisible(show); } hi.getContentPane().animateLayout(150); } }, 4); hi.show(); ---- ==== South Component A common feature in side menu bar is the ability to add a component to the "south" part of the side menu. Notice that this feature only works with the on-top and permanent versions of the side menu and not with the legacy versions: [source,java] ---- toolbar.setComponentToSideMenuSouth(myComponent); ---- This places the component below the side menu bar. Notice that this component controls its entire UIID & is separate from the `SideNavigationPanel` UIID so if you set that component you might want to place it within a container that has the `SideNavigationPanel` UIID so it will blend with the rest of the UI. [[title-animations-section]] ==== Title Animations Modern UI's often animate the title upon scrolling to balance the highly functional smaller title advantage with the gorgeous large image based title. This is pretty easy to do with the Toolbar API thru the Title animation API. The code below shows off an attractive title based on a book by GRRM on top of text footnote:[The text below is from A Wiki of Ice & Fire: http://awoiaf.westeros.org/index.php/A_Game_of_Thrones] that is scrollable. As the text is scrolled the title fades out. [source,java] ---- Toolbar.setGlobalToolbar(true); Form hi = new Form("Toolbar", new BoxLayout(BoxLayout.Y_AXIS)); EncodedImage placeholder = EncodedImage.createFromImage(Image.createImage(hi.getWidth(), hi.getWidth() / 5, 0xffff0000), true); URLImage background = URLImage.createToStorage(placeholder, "400px-AGameOfThrones.jpg", "http://awoiaf.westeros.org/images/thumb/9/93/AGameOfThrones.jpg/400px-AGameOfThrones.jpg"); background.fetch(); Style stitle = hi.getToolbar().getTitleComponent().getUnselectedStyle(); stitle.setBgImage(background); stitle.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL); stitle.setPaddingUnit(Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS, Style.UNIT_TYPE_DIPS); stitle.setPaddingTop(15); SpanButton credit = new SpanButton("This excerpt is from A Wiki Of Ice And Fire. Please check it out by clicking here!"); credit.addActionListener((e) -> Display.getInstance().execute("http://awoiaf.westeros.org/index.php/A_Game_of_Thrones")); hi.add(new SpanLabel("A Game of Thrones is the first of seven planned novels in A Song of Ice and Fire, an epic fantasy series by American author George R. R. Martin. It was first published on 6 August 1996. The novel was nominated for the 1998 Nebula Award and the 1997 World Fantasy Award,[1] and won the 1997 Locus Award.[2] The novella Blood of the Dragon, comprising the Daenerys Targaryen chapters from the novel, won the 1997 Hugo Award for Best Novella. ")). add(new Label("Plot introduction", "Heading")). add(new SpanLabel("A Game of Thrones is set in the Seven Kingdoms of Westeros, a land reminiscent of Medieval Europe. In Westeros the seasons last for years, sometimes decades, at a time.\n\n" + "Fifteen years prior to the novel, the Seven Kingdoms were torn apart by a civil war, known alternately as \"Robert's Rebellion\" and the \"War of the Usurper.\" Prince Rhaegar Targaryen kidnapped Lyanna Stark, arousing the ire of her family and of her betrothed, Lord Robert Baratheon (the war's titular rebel). The Mad King, Aerys II Targaryen, had Lyanna's father and eldest brother executed when they demanded her safe return. Her second brother, Eddard, joined his boyhood friend Robert Baratheon and Jon Arryn, with whom they had been fostered as children, in declaring war against the ruling Targaryen dynasty, securing the allegiances of House Tully and House Arryn through a network of dynastic marriages (Lord Eddard to Catelyn Tully and Lord Arryn to Lysa Tully). The powerful House Tyrell continued to support the King, but House Lannister and House Martell both stalled due to insults against their houses by the Targaryens. The civil war climaxed with the Battle of the Trident, when Prince Rhaegar was killed in battle by Robert Baratheon. The Lannisters finally agreed to support King Aerys, but then brutally... ")). add(credit); ComponentAnimation title = hi.getToolbar().getTitleComponent().createStyleAnimation("Title", 200); hi.getAnimationManager().onTitleScrollAnimation(title); hi.show(); ---- .The Toolbar starts with the large URLImage fetched from the web image::img/developer-guide/components-toolbar-animation-1.png[The Toolbar starts with the large URLImage fetched from the web,scaledwidth=20%] .As we scroll down the image fades and the title shrinks in size returning to the default UIID look image::img/developer-guide/components-toolbar-animation-2.png[As we scroll down the image fades and the title shrinks in size returning to the default UIID look,scaledwidth=20%] .As scrolling continues the title reaches standard size image::img/developer-guide/components-toolbar-animation-3.png[As scrolling continues the title reaches standard size,scaledwidth=20%] Almost all of the code above just creates the "look" of the application. The key piece of code above is this: [source,java] ---- ComponentAnimation title = hi.getToolbar().getTitleComponent().createStyleAnimation("Title", 200); hi.getAnimationManager().onTitleScrollAnimation(title); ---- In the first line we create a style animation that will translate the style from the current settings to the destination UIID (the first argument) within 200 pixels of scrolling. We then bind this animation to the title scrolling animation event. === BrowserComponent & WebBrowser // HTML_ONLY_START IMPORTANT: `BrowserComponent` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues https://www.codenameone.com/manual/advanced-topics.html#native-peer-components[here]. // HTML_ONLY_END //// //PDF_ONLY IMPORTANT: `BrowserComponent` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues <>. //// The https://www.codenameone.com/javadoc/com/codename1/components/WebBrowser.html[WebBrowser] component shows the native device web browser when supported by the device and the https://www.codenameone.com/javadoc/com/codename1/ui/html/HTMLComponent.html[HTMLComponent] when the web browser isn’t supported on the given device. If you only intend to target smartphones you should use the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent] directly instead of the `WebBrowser`. The `BrowserComponent` can point at an arbitrary URL to load it: [source,java] ---- Form hi = new Form("Browser", new BorderLayout()); BrowserComponent browser = new BrowserComponent(); browser.setURL("https://www.codenameone.com/"); hi.add(BorderLayout.CENTER, browser); ---- .Browser Component showing the Codename One website on the simulator image::img/developer-guide/components-browsercomponent.png[Browser Component showing the Codename One website on the simulator,scaledwidth=30%] NOTE: The scrollbars only appear in the simulator, device versions of the browser component act differently and support touch scrolling. IMPORTANT: A `BrowserComponent` should be in the center of a BorderLayout. Otherwise its preferred size might be zero before the HTML finishes loading/layout in the native layer and layout might be incorrect as a result. You can use `WebBrowser` and `BrowserComponent` interchangeably for most basic usage. However, if you need access to JavaScript or native browser functionality then there is really no use in going thru the `WebBrowser` abstraction. The `BrowserComponent` has full support for executing local web pages from within the jar. The basic support uses the `jar:///` URL as such: [source,java] ---- BrowserComponent wb = new BrowserComponent(); wb.setURL("jar:///Page.html"); ---- IMPORTANT: On Android a native indicator might show up when the web page is loading. This can be disabled using the `Display.getInstance().setProperty("WebLoadingHidden", "true");` call. You only need to invoke this once. ==== BrowserComponent Hierarchy When Codename One packages applications into native apps it hides a lot of details to make the process simpler. One of the things hidden is the fact that we aren't dealing with a JAR anymore, so `getResource`/`getResourceAsStream` are problematic... Both of these API's support hierarchies and a concept of package relativity both of which might not be supported on all OS's. Codename One has its own getResourceAsSteam in the https://www.codenameone.com/javadoc/com/codename1/ui/Display.html[Display] class and that works just fine, but it requires that all files be in the src root directory. TIP: That's why we recommend that you place files inside res files. A resource file allows you to add arbitrary data files and you can have as many resource files as you need. For web developers this isn't enough since hierarchies are used often to represent the various dependencies, this means that many links & references are relative. To work with such hierarchies just place all of your resources in a hierarchy under the html package in the project source directory (`src/html`). The build server will `tar` the entire content of that package and add an `html.tar` file into the native package. This `tar` is seamlessly extracted on the device when you actually need the resources and only with new application versions (not on every launch). So assuming the resources are under the html root package they can be displayed with code like this: [source,java] ---- try { browserComponent.setURLHierarchy("/htmlFile.html"); } catch(IOException err) { ... } ---- Notice that the path is relative to the html directory and starts with `/` but inside the HTML files you should use relative (not absolute) paths. Also notice that an `IOException` can be thrown due to the process of untarring. Its unlikely to happen but is entirely possible. ==== NavigationCallback At the core of the `BrowserComponent` we have the https://www.codenameone.com/javadoc/com/codename1/ui/events/BrowserNavigationCallback.html[BrowserNavigationCallback]. It might not seem like the most important interface within the browser but it is the "glue" that allows the JavaScript code to communicate back into the Java layer. You can bind a `BrowserNavigationCallback` by invoking `setBrowserNavigationCallback` on the `BrowserComponent`. At that point with every navigation within the browser the callback will get invoked. IMPORTANT: The `shouldNavigate` method from the `BrowserNavigationCallback` is invoked in a native thread and **NOT ON THE EDT**! + It is crucial that this method returns immediately and that it won't do any changes on the UI. The `shouldNavigate` indicates to the native code whether navigation should proceed or not. E.g. if a user clicks a specific link we might choose to do something in the Java code so we can just return false and block the navigation. We can invoke https://www.codenameone.com/javadoc/com/codename1/ui/Display.html#callSerially-java.lang.Runnable-[callSerially] to do the actual task in the Java side. [source,java] ---- Form hi = new Form("BrowserComponent", new BorderLayout()); BrowserComponent bc = new BrowserComponent(); bc.setPage( "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "

Demo

\n" + " \n" + "", null); hi.add(BorderLayout.CENTER, bc); bc.setBrowserNavigationCallback((url) -> { if(url.startsWith("http://click")) { Display.getInstance().callSerially(() -> bc.execute("fnc('

You clicked!

')")); return false; } return true; }); ---- .Before the link is clicked for the "shouldNavigate" call image::img/developer-guide/components-browsercomponent-callback-before.png[Before the link is clicked for the "shouldNavigate" call,scaledwidth=25%] .After the link is clicked for the "shouldNavigate" call image::img/developer-guide/components-browsercomponent-callback-after.png[After the link is clicked for the "shouldNavigate" call,scaledwidth=25%] NOTE: The JavaScript Bridge is implemented on top of the `BrowserNavigationCallback`. ==== JavaScript TIP: The JavaScript bridge is sometimes confused with the JavaScript Port. The JavaScript bridge allows us to communicate with JavaScript from Java (and visa versa). The JavaScript port allows you to compile the Codename One application into a JavaScript application that runs in a standard web browser without code changes (think GWT without source changes and with thread support).+ We discuss the JavaScript port further later in the guide. Codename One 4.0 introduced a new API for interacting with Javascript in Codename One. This API is part of the `BrowserComponent` class, and effectively replaces the https://www.codenameone.com/javadoc/com/codename1/javascript/package-summary.html[com.codename1.javascript package], which is now deprecated. ===== So what was wrong with the old API? The old API provided a synchronous wrapper around an inherently asynchronous process, and made extensive use of `invokeAndBlock()` underneath the covers. This resulted in a very nice API with high-level abstractions that played nicely with a synchronous programming model, but it came with a price-tag in terms of performance, complexity, and predictability. Let’s take a simple example, getting a reference to the “window” object: [source,java] ---- JSObject window = ctx.get("window"); ---- This code looks harmless enough, but this is actually quite expensive. It issues a command to the `BrowserComponent`, and uses `invokeAndBlock()` to wait for the command to go through and send back a response. `invokeAndBlock()` is a magical tool that allows you to “block” without blocking the EDT, but it has its costs, and shouldn’t be overused. Most of the Codename One APIs that use `invokeAndBlock()` indicate this in their name. E.g. `Component.animateLayoutAndWait()`. This gives you the expectation that this call could take some time, and helps to alert you to the underlying cost. The problem with the `ctx.get("window")` call is that it looks the same as a call to `Map.get(key)`. There’s no indication that this call is expensive and could take time. One call like this probably isn’t a big deal, but it doesn’t take long before you have dozens or even hundreds of calls like this littered throughout your codebase, and they can be hard to pick out. ===== The New API The new API fully embraces the asynchronous nature of Javascript. It uses callbacks instead of return values, and provides convenience wrappers with the appropriate “AndWait()” naming convention to allow for synchronous usage. Let’s look at a simple example: NOTE: In all of the sample code below, you can assume that variables named `bc` represent an instance of https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent]. [source,java] ---- bc.execute( "callback.onSuccess(3+4)", res -> Log.p("The result was "+res.getInt()) ); ---- This code should output “The result was 7” to the console. It is fully asynchronous, so you can include this code anywhere without worrying about it “bogging down” your code. The full signature of this form of the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html#execute-java.lang.String-com.codename1.util.SuccessCallback-[execute()] method is: [source,java] ---- public void execute(String js, SuccessCallback callback) ---- The first parameter is just a javascript expression. This javascript *MUST* call either `callback.onSuccess(result)` or `callback.onError(message, errCode)` at some point in order for your callback to be called. The second parameter is your callback that is executed from the javascript side, when `callback.onSuccess(res)` is called. The callback takes a single parameter of type https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.JSRef.html[JSRef] which is a generic wrapper around a javascript variable. JSRef has accessors to retrieve the value as some of the primitive types. E.g. `getBoolean()`, `getDouble()`, `getInt()`, `toString()`, and it provides some introspection via the `getType()` method. NOTE: It is worth noting that the callback method can only take a single parameter. If you need to pass multiple parameters, you may consider including them in a single string which you parse in your callback. ===== Synchronous Wrappers As mentioned before, the new API also provides an `executeAndWait()` wrapper for `execute()` that will work synchronously. It, as its name suggests, uses `invokeAndBlock` under the hood so as not to block the EDT while it is waiting. E.g. [source, java] ---- JSRef res = bc.executeAndWait("callback.onSuccess(3+4)"); Log.p("The result was "+res.Int()); ---- Prints “The result was 7”. IMPORTANT: When using the `andWait()` variant, it is *extremely* important that your Javascript calls your callback method at some point - otherwise it will block *indefinitely*. We provide variants of executeAndWait() that include a timeout in case you want to hedge against this possibility. ===== Multi-use Callbacks The callbacks you pass to `execute()` and `executeAndWait()` are single-use callbacks. You can’t, for example, store the `callback` variable on the javascript side for later use (e.g. to respond to a button click event). If you need a “multi-use” callback, you should use the `addJSCallback()` method instead. Its usage looks identical to `execute()`, the only difference is that the callback will life on after its first use. E.g. Consider the following code: [source,java] ---- bc.execute( "$('#somebutton').click(function(){callback.onSuccess('Button was clicked')})", res -> Log.p(res.toString()) ); ---- The above example, assumes that jQuery is loaded in the webpage that we are interacting with, and we are adding a click handler to a button with ID “somebutton”. The click handler calls our callback. If you run this example, the first time the button is clicked, you’ll see “Button was clicked” printed to the console as expected. However, the 2nd time, you’ll just get an exception. This is because the callback passed to `execute()` is only single-use. We need to modify this code to use the `addJSCallback()` method as follows: [source,java] ---- bc.addJSCallback( "$('#somebutton').click(function(){callback.onSuccess('Button was clicked')})", res -> Log.p(res.toString()) ); ---- Now it will work no matter how many times the button is clicked. ===== Passing Parameters to Javascript In many cases, the javascript expressions that you execute will include parameters from your java code. Properly escaping these parameters is tricky at worst, and annoying at best. E.g. If you’re passing a string, you need to make sure that it escapes quotes and new lines properly or it will cause the javascript to have a syntax error. Luckily we provide variants of `execute()` and https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html#addJSCallback-java.lang.String-com.codename1.util.SuccessCallback-[addJSCallback()] that allow you to pass your parameters and have them automatically escaped. For example, suppose we want to pass a string with text to set in a textarea within the webpage. We can do something like: [source,java] ---- bc.execute( "jQuery('#bio').text(${0}); jQuery('#age').text(${1})", new Object[]{ "A multi-line\n string with \"quotes\"", 27 } ); ---- The gist is that you embed placeholders in the javascript expression that are replaced by the corresponding entry in an array of parameters. The `${0}` placeholder is replaced by the first item in the parameters array, the `${1}` placeholder is replaced by the 2nd, and so on. ===== Proxy Objects The new API also includes a https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.JSProxy.html[JSProxy] class that encapsulates a Javascript object simplify the getting and setting of properties on Javascript objects - and the calling of their methods. It provides essentially three core methods, along with several variants of each to allow for async or synchronous usages, parameters, and timeouts. E.g. We might want to create a proxy for the https://developer.mozilla.org/en-US/docs/Web/API/Window/location[window.location] object so that we can access its properties more easily from Java. [source,java] ---- JSProxy location = bc.createJSProxy("window.location"); ---- Then we can retrieve its properties using the `get()` method: [source,java] ---- location.get("href", res -> Log.p("location.href="+res)); ---- Or synchronously: [source,java] ---- JSRef href = location.getAndWait("href"); Log.p("location.href="+href); ---- We can also set its properties: [source,java] ---- location.set("href", "http://www.google.com"); ---- And call its methods: [source,java] ---- location.call("replace", new Object[]{"http://www.google.com"}, res -> Log.p("Return value was "+res) ); ---- ===== Legacy JSObject Support This section describes the now deprecated `JSObject` approach. It's here for reference by developers working with older code. We suggest using the new API when starting a new project. `BrowserComponent` can communicate with the HTML code using JavaScript calls. E.g. we can create HTML like this: [source,java] ---- Form hi = new Form("BrowserComponent", new BorderLayout()); BrowserComponent bc = new BrowserComponent(); bc.setPage( "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "

Demo

\n" + " \n" + "", null); TextField tf = new TextField(); hi.add(BorderLayout.CENTER, bc). add(BorderLayout.SOUTH, tf); bc.addWebEventListener("onLoad", (e) -> bc.execute("fnc('

Hello World

')")); tf.addActionListener((e) -> bc.execute("fnc('

" + tf.getText() +"

')")); hi.show(); ---- .JavaScript code was invoked to append text into the browser image above image::img/developer-guide/components-browsercomponent-javascript.png[JavaScript code was invoked to append text into the browser image above,scaledwidth=20%] NOTE: Notice that opening an alert in an embedded native browser might not work We use the `execute` method above to execute custom JavaScript code. We also have an `executeAndReturnString` method that allows us to receive a response value from the JavaScript side. Coupled with `shouldNavigate` we can effectively do everything which is exactly what the JavaScript Bridge tries to do. ===== The JavaScript Bridge While it's possible to just build everything on top of `execute` and `shouldNavigate`, both of these methods have their limits. That is why we introduced the javascript package, it allows you to communicate with JavaScript using intuitive code/syntax. The https://www.codenameone.com/javadoc/com/codename1/javascript/JavascriptContext.html[JavascriptContext] class lays the foundation by enabling you to call JavaScript code directly from Java. It provides automatic type conversion between Java and JavaScript types as follows: .Java to JavaScript [cols="2*",options="header"] |==== | Java Type | Javascript Type | `String` | `String` | `Double/Integer/Float/Long` | `Number` | `Boolean` | `Boolean` | `JSObject` | `Object` | `null` | `null` | `Other` | Not Allowed |==== .JavaScript to Java [cols="2*",options="header"] |==== | Javascript Type | Java Type | `String` | `String` | `Number` | `Double` | `Boolean` | `Boolean` | `Object` | `JSObject` | `Function` | `JSObject` | `Array` | `JSObject` | `null` | `null` | `undefined` | `null` |==== NOTE: This conversion table is more verbose than necessary, since JavaScript functions and arrays are, in fact Objects themselves, so those rows are redundant. All JavaScript objects are converted to https://www.codenameone.com/javadoc/com/codename1/javascript/JSObject.html[JSObject]. We can access JavaScript variables easily from the context by using code like this: [source,java] ---- Form hi = new Form("BrowserComponent", new BorderLayout()); BrowserComponent bc = new BrowserComponent(); bc.setPage( "\n" + " \n" + " \n" + " \n" + " \n" + "

This will appear twice...

\n" + " \n" + "", null); hi.add(BorderLayout.CENTER, bc); bc.addWebEventListener("onLoad", (e) -> { // Create a Javascript context for this BrowserComponent JavascriptContext ctx = new JavascriptContext(bc); String pageContent = (String)ctx.get("document.body.innerHTML"); hi.add(BorderLayout.SOUTH, pageContent); hi.revalidate(); }); hi.show(); ---- .The contents was copied from the DOM and placed in the south position of the form image::img/developer-guide/components-browsercomponent-context.png[The contents was copied from the DOM and placed in the south position of the form,scaledwidth=20%] Notice that when you work with numeric values or anything related to the types mentioned above your code must be aware of the typing. E.g. in this case the type is `Double` and not `String`: [source,java] ---- Double outerWidth = (Double)ctx.get("window.outerWidth"); ---- You can also query the context for objects and modify their value e.g. [source,java] ---- Form hi = new Form("BrowserComponent", new BorderLayout()); BrowserComponent bc = new BrowserComponent(); bc.setPage( "\n" + " \n" + " \n" + " \n" + " \n" + "

Please Wait...

\n" + " \n" + "", null); hi.add(BorderLayout.CENTER, bc); bc.addWebEventListener("onLoad", (e) -> { // Create a Javascript context for this BrowserComponent JavascriptContext ctx = new JavascriptContext(bc); JSObject jo = (JSObject)ctx.get("window"); jo.set("location", "https://www.codenameone.com/"); }); ---- This code effectively navigates to the Codename One home page by fetching the DOM's window object and setting its `location` property to https://www.codenameone.com/[https://www.codenameone.com/]. ==== Cordova/PhoneGap Integration PhoneGap was one of the first web app packager tools in the market. It's a tool that is effectively a browser component within a native wrapper coupled with native access API's. Cordova is the open source extension of this popular project. Codename One supports embedding PhoneGap/Cordova applications directly into Codename One applications. This is relatively easy to do with the `BrowserComponent` and JavaScript integration. The main aspect that this integration requires is support for Cordova plugins & its JavaScript API's. The effort to integrate Cordova/PhoneGap support into Codename One is handled within an open source github project https://github.com/codenameone/CN1Cordova[here]. The chief benefits of picking Codename One rather than using Cordova directly are: - Build Cloud - Better Native Code Support - Better Protection Of IP - IDE Integration Java - JavaScript - HTML - Easy, Doesn't Require A Mac, Automates Certificates/Signing - Migration To Java This is discussed further in the https://www.codenameone.com/blog/phonegap-cordova-compatibility-for-codename-one.html[original announcement]. === AutoCompleteTextField The https://www.codenameone.com/javadoc/com/codename1/ui/AutoCompleteTextField.html[AutoCompleteTextField] allows us to write text into a text field and select a completion entry from the list in a similar way to a search engine. This is really easy to incorporate into your code, just replace your usage of https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html[TextField] with `AutoCompleteTextField` and define the data that the autocomplete should work from. There is a default implementation that accepts a `String` array or a https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] for completion strings, this can work well for a "small" set of thousands (or tens of thousands) of entries. E.g. This is a trivial use case that can work well for smaller sample sizes: [source,java] ---- Form hi = new Form("Auto Complete", new BoxLayout(BoxLayout.Y_AXIS)); AutoCompleteTextField ac = new AutoCompleteTextField("Short", "Shock", "Sholder", "Shrek"); ac.setMinimumElementsShownInPopup(5); hi.add(ac); ---- .Autocomplete Text Field image::img/developer-guide/components-autocomplete.png[Autocomplete Text Field,scaledwidth=30%] However, if you wish to query a database or a web service you will need to derive the class and perform more advanced filtering by overriding the `filter` method: [source,java] ---- public void showForm() { final DefaultListModel options = new DefaultListModel<>(); AutoCompleteTextField ac = new AutoCompleteTextField(options) { @Override protected boolean filter(String text) { if(text.length() == 0) { return false; } String[] l = searchLocations(text); if(l == null || l.length == 0) { return false; } options.removeAll(); for(String s : l) { options.addItem(s); } return true; } }; ac.setMinimumElementsShownInPopup(5); hi.add(ac); hi.add(new SpanLabel("This demo requires a valid google API key to be set below " + "you can get this key for the webservice (not the native key) by following the instructions here: " + "https://developers.google.com/places/web-service/get-api-key")); hi.add(apiKey); hi.getToolbar().addCommandToRightBar("Get Key", null, e -> Display.getInstance().execute("https://developers.google.com/places/web-service/get-api-key")); hi.show(); } TextField apiKey = new TextField(); String[] searchLocations(String text) { try { if(text.length() > 0) { ConnectionRequest r = new ConnectionRequest(); r.setPost(false); r.setUrl("https://maps.googleapis.com/maps/api/place/autocomplete/json"); r.addArgument("key", apiKey.getText()); r.addArgument("input", text); NetworkManager.getInstance().addToQueueAndWait(r); Map result = new JSONParser().parseJSON(new InputStreamReader(new ByteArrayInputStream(r.getResponseData()), "UTF-8")); String[] res = Result.fromContent(result).getAsStringArray("//description"); return res; } } catch(Exception err) { Log.e(err); } return null; } ---- .Autocomplete Text Field with a webservice image::img/developer-guide/dynamic-autocomplete.png[Autocomplete Text Field with a webservice,scaledwidth=20%] ==== Using Images In AutoCompleteTextField One question I got a few times is "How do you customize the results of the auto complete field"? This sounds difficult to most people as we can only work with Strings so how do we represent additional data or format the date correctly? The answer is actually pretty simple, we still need to work with Strings because auto-complete is first and foremost a text field. However, that doesn't preclude our custom renderer from fetching data that might be placed in a different location and associated with the result. The following source code presents an auto-complete text field with images in the completion popup and two lines for every entry: [source,java] ---- final String[] characters = { "Tyrion Lannister", "Jaime Lannister", "Cersei Lannister", "Daenerys Targaryen", "Jon Snow", "Petyr Baelish", "Jorah Mormont", "Sansa Stark", "Arya Stark", "Theon Greyjoy" // snipped the rest for clarity }; Form current = new Form("AutoComplete", BoxLayout.y()); AutoCompleteTextField ac = new AutoCompleteTextField(characters); final int size = Display.getInstance().convertToPixels(7); final EncodedImage placeholder = EncodedImage.createFromImage(Image.createImage(size, size, 0xffcccccc), true); final String[] actors = { "Peter Dinklage", "Nikolaj Coster-Waldau", "Lena Headey"}; // <1> final Image[] pictures = { URLImage.createToStorage(placeholder, "tyrion","http://i.lv3.hbo.com/assets/images/series/game-of-thrones/character/s5/tyrion-lannister-512x512.jpg"), URLImage.createToStorage(placeholder, "jaime","http://i.lv3.hbo.com/assets/images/series/game-of-thrones/character/s5/jamie-lannister-512x512.jpg"), URLImage.createToStorage(placeholder, "cersei","http://i.lv3.hbo.com/assets/images/series/game-of-thrones/character/s5/cersei-lannister-512x512.jpg") }; ac.setCompletionRenderer(new ListCellRenderer() { private final Label focus = new Label(); // <2> private final Label line1 = new Label(characters[0]); private final Label line2 = new Label(actors[0]); private final Label icon = new Label(pictures[0]); private final Container selection = BorderLayout.center( BoxLayout.encloseY(line1, line2)).add(BorderLayout.EAST, icon); @Override public Component getListCellRendererComponent(com.codename1.ui.List list, Object value, int index, boolean isSelected) { for(int iter = 0 ; iter < characters.length ; iter++) { if(characters[iter].equals(value)) { line1.setText(characters[iter]); if(actors.length > iter) { line2.setText(actors[iter]); icon.setIcon(pictures[iter]); } else { line2.setText(""); // <3> icon.setIcon(placeholder); } break; } } return selection; } @Override public Component getListFocusComponent(com.codename1.ui.List list) { return focus; } }); current.add(ac); current.show(); ---- <1> We have duplicate arrays that are only partial for clarity. This is a separate list of data element but you can fetch the additional data from anywhere <2> We create the renderer UI instantly in the fields with the helper methods for wrapping elements which is pretty cool & terse <3> In a renderer it's important to always set the value especially if you don't have a value in place .Auto complete with images image::img/developer-guide/auto-complete-with-pictures.png[Auto complete with images,scaledwidth=20%] [[picker-section]] === Picker https://www.codenameone.com/javadoc/com/codename1/ui/spinner/Picker.html[Picker] occupies the limbo between native widget and lightweight widget. Picker is more like `TextField`/`TextArea` in the sense that it's a Codename One widget that calls the native code only during editing. The reasoning for this is the highly native UX and functionality related to this widget type which should be quite obvious from the screenshots below. At this time there are 4 types of pickers: - Time - Date & Time - Date - Strings If a platform doesn't support native pickers an internal fallback implementation is used. This is the implementation we always use in the simulator so assume different behavior when building for the device. TIP: While Android supports Date, Time native pickers it doesn't support the Date & Time native picker UX and will fallback in that case. The sample below includes al picker types: [source,java] ---- Form hi = new Form("Picker", new BoxLayout(BoxLayout.Y_AXIS)); Picker datePicker = new Picker(); datePicker.setType(Display.PICKER_TYPE_DATE); Picker dateTimePicker = new Picker(); dateTimePicker.setType(Display.PICKER_TYPE_DATE_AND_TIME); Picker timePicker = new Picker(); timePicker.setType(Display.PICKER_TYPE_TIME); Picker stringPicker = new Picker(); stringPicker.setType(Display.PICKER_TYPE_STRINGS); Picker durationPicker = new Picker(); durationPicker.setType(Display.PICKER_TYPE_DURATION); Picker minuteDurationPicker = new Picker(); minuteDurationPicker.setType(Display.PICKER_TYPE_DURATION_MINUTES); Picker hourDurationPicker = new Picker(); hourDurationPicker.setType(Display.PICKER_TYPE_DURATION_HOURS); datePicker.setDate(new Date()); dateTimePicker.setDate(new Date()); timePicker.setTime(10 * 60); // 10:00AM = Minutes since midnight stringPicker.setStrings("A Game of Thrones", "A Clash Of Kings", "A Storm Of Swords", "A Feast For Crows", "A Dance With Dragons", "The Winds of Winter", "A Dream of Spring"); stringPicker.setSelectedString("A Game of Thrones"); hi.add(datePicker).add(dateTimePicker).add(timePicker) .add(stringPicker).add(durationPicker) .add(minuteDurationPicker).add(hourDurationPicker); hi.show(); ---- .The various picker components image::img/developer-guide/components-picker.png[The various picker components,scaledwidth=30%] .The date & time picker on the simulator image::img/developer-guide/components-picker-date-time-on-simulator.png[The date & time picker on the simulator,scaledwidth=30%] .The date picker component on the Android device image::img/developer-guide/components-picker-date-android.png[The date picker component on the Android device,scaledwidth=15%] .Date & time picker on Android. Notice it didn't use a builtin widget since there is none image::img/developer-guide/components-picker-date-time-android.png[Date and time picker on Android. Notice it didn't use a builtin widget since there is none,scaledwidth=20%] .String picker on the native Android device image::img/developer-guide/components-picker-strings-android.png[String picker on the native Android device,scaledwidth=20%] .Time picker on the Android device image::img/developer-guide/components-picker-time-android.png[Time picker on the Android device,scaledwidth=20%] .Duration picker on Android device image::img/developer-guide/components-picker-duration-android.png[Duration picker on Android device,scaledwidth=20%] .Hours Duration picker on Android device image::img/developer-guide/components-picker-duration-hours-android.png[Hours Duration picker on Android device,scaledwidth=20%] .Minutes Duration picker on Android device image::img/developer-guide/components-picker-duration-minutes-android.png[Minutes Duration picker on Android device,scaledwidth=20%] The text displayed by the picker on selection is generated automatically by the `updateValue()` method. You can override it to display a custom formatted value and call `setText(String)` with the correct display string. A common use case is to format date values based on a specific appearance and `Picker` has builtin support for a custom display formatter. Just use the `setFormatter(SimpleDateFormat)` method and set the appearance for the field. === SwipeableContainer The https://www.codenameone.com/javadoc/com/codename1/ui/SwipeableContainer.html[SwipeableContainer] allows us to place a component such as a https://www.codenameone.com/javadoc/com/codename1/components/MultiButton.html[MultiButton] on top of additional "options" that can be exposed by swiping the component to the side. This swipe gesture is commonly used in touch interfaces to expose features such as delete, edit etc. It's trivial to use this component by just determining the components placed on top and bottom (the revealed component). [source,java] ---- SwipeableContainer swip = new SwipeableContainer(bottom, top); ---- We can combine some of the demos above including the <> to rank GRRM's books in an interactive way: [source,java] ---- Form hi = new Form("Swipe", new BoxLayout(BoxLayout.Y_AXIS)); hi.add(createRankWidget("A Game of Thrones", "1996")). add(createRankWidget("A Clash Of Kings", "1998")). add(createRankWidget("A Storm Of Swords", "2000")). add(createRankWidget("A Feast For Crows", "2005")). add(createRankWidget("A Dance With Dragons", "2011")). add(createRankWidget("The Winds of Winter", "TBD")). add(createRankWidget("A Dream of Spring", "TBD")); hi.show(); public SwipeableContainer createRankWidget(String title, String year) { MultiButton button = new MultiButton(title); button.setTextLine2(year); return new SwipeableContainer(FlowLayout.encloseCenterMiddle(createStarRankSlider()), button); } ---- .SwipableContainer showing a common use case of ranking on swipe image::img/developer-guide/components-swipablecontainer.png[SwipableContainer showing a common use case of ranking on swipe,scaledwidth=20%] === EmbeddedContainer https://www.codenameone.com/javadoc/com/codename1/ui/util/EmbeddedContainer.html[EmbeddedContainer] solves a problem that exists only within the GUI builder and the class makes no sense outside of the context of the GUI builder. The necessity for `EmbeddedContainer` came about due to iPhone inspired designs that relied on tabs (iPhone style tabs at the bottom of the screen) where different features of the application are within a different tab. This didn't mesh well with the GUI builder navigation logic and so we needed to rethink some of it. We wanted to reuse GUI as much as possible while still enjoying the advantage of navigation being completely managed for me. Android does this with Activities and the iPhone itself has a view controller, both approaches are problematic for Codename One. The problem is that you have what is effectively two incompatible hierarchies to mix and match. The Component/Container hierarchy is powerful enough to represent such a UI but we needed a "marker" to indicate to the https://www.codenameone.com/javadoc/com/codename1/ui/util/UIBuilder.html[UIBuilder] where a "root" component exists so navigation occurs only within the given "root". Here `EmbeddedContainer` comes into play, its a simple container that can only contain another GUI from the GUI builder. Nothing else. So we can place it in any form of UI and effectively have the UI change appropriately and navigation would default to "sensible values". Navigation replaces the content of the embedded container; it finds the embedded container based on the component that broadcast the event. If you want to navigate manually just use the showContainer() method which accepts a component, you can give any component that is under the `EmbeddedContainer` you want to replace and Codename One will be smart enough to replace only that component. The nice part about using the `EmbeddedContainer` is that the resulting UI can be very easily refactored to provide a more traditional form based UI without duplicating effort and can be easily adapted to a more tablet oriented UI (with a side bar) again without much effort. === MapComponent IMPORTANT: The MapComponent uses a somewhat outdated tiling API which is not as rich as modern native maps. We recommend using the https://github.com/codenameone/codenameone-google-maps/[GoogleMap's Native cn1lib] to integrate native mapping functionality into the Codename One app. The https://www.codenameone.com/javadoc/com/codename1/maps/MapComponent.html[MapComponent] uses the OpenStreetMap webservice by default to display a navigatable map. The code was contributed by Roman Kamyk and was originally used for a LWUIT application. .Map Component image::img/developer-guide/mapcomponent.png[Map Component,scaledwidth=30%] The screenshot above was produced using the following code: [source,java] ---- Form map = new Form("Map"); map.setLayout(new BorderLayout()); map.setScrollable(false); final MapComponent mc = new MapComponent(); try { //get the current location from the Location API Location loc = LocationManager.getLocationManager().getCurrentLocation(); Coord lastLocation = new Coord(loc.getLatitude(), loc.getLongtitude()); Image i = Image.createImage("/blue_pin.png"); PointsLayer pl = new PointsLayer(); pl.setPointIcon(i); PointLayer p = new PointLayer(lastLocation, "You Are Here", i); p.setDisplayName(true); pl.addPoint(p); mc.addLayer(pl); } catch (IOException ex) { ex.printStackTrace(); } mc.zoomToLayers(); map.addComponent(BorderLayout.CENTER, mc); map.addCommand(new BackCommand()); map.setBackCommand(new BackCommand()); map.show(); ---- The example below shows how to integrate the https://www.codenameone.com/javadoc/com/codename1/maps/MapComponent.html[MapComponent] with the Google https://www.codenameone.com/javadoc/com/codename1/location/Location.html[Location] API. make sure to obtain your secret api key from the Google https://www.codenameone.com/javadoc/com/codename1/location/Location.html[Location] data API at: https://developers.google.com/maps/documentation/places/ .MapComponent with Google Location API image::img/developer-guide/map-loc.png[MapComponent with Google Location API,scaledwidth=25%] [source,java] ---- final Form map = new Form("Map"); map.setLayout(new BorderLayout()); map.setScrollable(false); final MapComponent mc = new MapComponent(); Location loc = LocationManager.getLocationManager().getCurrentLocation(); //use the code from above to show you on the map putMeOnMap(mc); map.addComponent(BorderLayout.CENTER, mc); map.addCommand(new BackCommand()); map.setBackCommand(new BackCommand()); ConnectionRequest req = new ConnectionRequest() { protected void readResponse(InputStream input) throws IOException { JSONParser p = new JSONParser(); Hashtable h = p.parse(new InputStreamReader(input)); // "status" : "REQUEST_DENIED" String response = (String)h.get("status"); if(response.equals("REQUEST_DENIED")){ System.out.println("make sure to obtain a key from " + "https://developers.google.com/maps/documentation/places/"); progress.dispose(); Dialog.show("Info", "make sure to obtain an application key from " + "google places api's" , "Ok", null); return; } final Vector v = (Vector) h.get("results"); Image im = Image.createImage("/red_pin.png"); PointsLayer pl = new PointsLayer(); pl.setPointIcon(im); pl.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { PointLayer p = (PointLayer) evt.getSource(); System.out.println("pressed " + p); Dialog.show("Details", "" + p.getName(), "Ok", null); } }); for (int i = 0; i < v.size(); i++) { Hashtable entry = (Hashtable) v.elementAt(i); Hashtable geo = (Hashtable) entry.get("geometry"); Hashtable loc = (Hashtable) geo.get("location"); Double lat = (Double) loc.get("lat"); Double lng = (Double) loc.get("lng"); PointLayer point = new PointLayer(new Coord(lat.doubleValue(), lng.doubleValue()), (String) entry.get("name"), null); pl.addPoint(point); } progress.dispose(); mc.addLayer(pl); map.show(); mc.zoomToLayers(); } }; req.setUrl("https://maps.googleapis.com/maps/api/place/search/json"); req.setPost(false); req.addArgument("location", "" + loc.getLatitude() + "," + loc.getLongtitude()); req.addArgument("radius", "500"); req.addArgument("types", "food"); req.addArgument("sensor", "false"); //get your own key from https://developers.google.com/maps/documentation/places/ //and replace it here. String key = "yourAPIKey"; req.addArgument("key", key); NetworkManager.getInstance().addToQueue(req); } catch (IOException ex) { ex.printStackTrace(); } } ---- === Chart Component The `charts` package enables Codename One developers to add charts and visualizations to their apps without having to include external libraries or embedding web views. We also wanted to harness the new features in the graphics pipeline to maximize performance. ==== Device Support Since the charts package makes use of 2D transformations and shapes, it requires some of the graphics features that are not yet available on all platforms. Currently the following platforms are supported: 1. Simulator 2. Android 3. iOS ==== Features 1. **Built-in support for many common types of charts** including bar charts, line charts, stacked charts, scatter charts, pie charts and more. 2. **Pinch Zoom** - The https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent] class includes optional pinch zoom support. 3. **Panning Support** - The https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent] class includes optional support for panning. ==== Chart Types The `com.codename1.charts` package includes models and renderers for many different types of charts. It is also extensible so that you can add your own chart types if required. The following screen shots demonstrate a small sampling of the types of charts that can be created. .Line Charts [float="left"] image::img/developer-guide/line_chart.png[Line Charts,scaledwidth=15%] .Cubic Line Charts [float="right"] image::img/developer-guide/line_chart_cubic_multi.png[Cubic Line Charts,scaledwidth=15%] .Bar Charts [float="left"] image::img/developer-guide/bar_chart.png[Bar Charts,scaledwidth=15%] .Stacked Bar Charts [float="right"] image::img/developer-guide/bar_chart_stacked.png[Stacked Bar Charts,scaledwidth=15%] .Range Bar Charts [float="left"] image::img/developer-guide/range_bar_chart.png[Range Bar Charts,scaledwidth=15%] .Pie Charts [float="right",scaledwidth=15%] image::img/developer-guide/pie_chart.png[Pie Charts,scaledwidth=15%] .Doughnut Charts [float="left"] image::img/developer-guide/doughnut_chart.png[Doughnut Charts,scaledwidth=15%] .Scatter Charts [float="right",scaledwidth=15%] image::img/developer-guide/scatter_chart.png[Scatter Charts,scaledwidth=15%] .Dial Charts [float="left",scaledwidth=15%] image::img/developer-guide/dial_chart.png[Dial Charts,scaledwidth=15%] .Combined Charts [float="right"] image::img/developer-guide/combined.png[Combined Charts,scaledwidth=15%] .Bubble Charts [float="left",scaledwidth=15%] image::img/developer-guide/bubble_chart.png[Bubble Charts,scaledwidth=15%] .Time Charts [float="right",scaledwidth=15%] image::img/developer-guide/time_chart.png[Time Charts,scaledwidth=15%] NOTE: The above screenshots were taken from the https://github.com/codenameone/codenameone-demos/tree/master/ChartsDemo[ChartsDemo app]. You can start playing with this app by checking it out from our git repository. ==== How to Create A Chart Adding a chart to your app involves four steps: 1. **Build the model**. You can construct a model (aka data set) for the chart using one of the existing model classes in the `com.codename1.charts.models` package. Essentially, this is just where you add the data that you want to display. 2. **Set up a renderer**. You can create a renderer for your chart using one of the existing renderer classes in the `com.codename1.charts.renderers` package. The renderer allows you to specify how the chart should look. E.g. the colors, fonts, styles, to use. 3. **Create the Chart View**. Use one of the existing _view_ classes in the `com.codename1.charts.views` package. 4. **Create a https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent]**. In order to add your chart to the UI, you need to wrap it in a https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent] object. You can check out the https://github.com/codenameone/codenameone-demos/tree/master/ChartsDemo[ChartsDemo] app for specific examples, but here is a high level view of some code that creates a Pie Chart. [source,java] ---- /** * Creates a renderer for the specified colors. */ private DefaultRenderer buildCategoryRenderer(int[] colors) { DefaultRenderer renderer = new DefaultRenderer(); renderer.setLabelsTextSize(15); renderer.setLegendTextSize(15); renderer.setMargins(new int[]{20, 30, 15, 0}); for (int color : colors) { SimpleSeriesRenderer r = new SimpleSeriesRenderer(); r.setColor(color); renderer.addSeriesRenderer(r); } return renderer; } /** * Builds a category series using the provided values. * * @param titles the series titles * @param values the values * @return the category series */ protected CategorySeries buildCategoryDataset(String title, double[] values) { CategorySeries series = new CategorySeries(title); int k = 0; for (double value : values) { series.add("Project " + ++k, value); } return series; } public Form createPieChartForm() { // Generate the values double[] values = new double[]{12, 14, 11, 10, 19}; // Set up the renderer int[] colors = new int[]{ColorUtil.BLUE, ColorUtil.GREEN, ColorUtil.MAGENTA, ColorUtil.YELLOW, ColorUtil.CYAN}; DefaultRenderer renderer = buildCategoryRenderer(colors); renderer.setZoomButtonsVisible(true); renderer.setZoomEnabled(true); renderer.setChartTitleTextSize(20); renderer.setDisplayValues(true); renderer.setShowLabels(true); SimpleSeriesRenderer r = renderer.getSeriesRendererAt(0); r.setGradientEnabled(true); r.setGradientStart(0, ColorUtil.BLUE); r.setGradientStop(0, ColorUtil.GREEN); r.setHighlighted(true); // Create the chart ... pass the values and renderer to the chart object. PieChart chart = new PieChart(buildCategoryDataset("Project budget", values), renderer); // Wrap the chart in a Component so we can add it to a form ChartComponent c = new ChartComponent(chart); // Create a form and show it. Form f = new Form("Budget"); f.setLayout(new BorderLayout()); f.addComponent(BorderLayout.CENTER, c); return f; } ---- === Calendar The https://www.codenameone.com/javadoc/com/codename1/ui/Calendar.html[Calendar] class allows us to display a traditional calendar picker and optionally highlight days in various ways. NOTE: We normally recommend developers use the <> rather than use the calendar to pick a date. It looks better on the devices. Simple usage of the `Calendar` class looks something like this: [source,java] ---- Form hi = new Form("Calendar", new BorderLayout()); Calendar cld = new Calendar(); cld.addActionListener((e) -> Log.p("You picked: " + new Date(cld.getSelectedDay()))); hi.add(BorderLayout.CENTER, cld); ---- .The Calendar component image::img/developer-guide/components-calendar.png[The Calendar component,scaledwidth=20%] === ToastBar The https://www.codenameone.com/javadoc/com/codename1/components/ToastBar.html[ToastBar] class allows us to display none-obtrusive status messages to the user at the bottom of the screen. This is useful for such things as informing the user of a long-running task (like downloading a file in the background), or popping up an error message that doesn't require a response from the user. Simple usage of the `ToastBar` class looks something like this: [source,java] ---- Status status = ToastBar.getInstance().createStatus(); status.setMessage("Downloading your file..."); status.show(); // ... Later on when download completes status.clear(); ---- .ToastBar with message image::img/developer-guide/components-statusbar-message.png[ToastBar with message,scaledwidth=20%] We can show a progress indicator in the ToastBar like this: [source,java] ---- Status status = ToastBar.getInstance().createStatus(); status.setMessage("Hello world"); status.setShowProgressIndicator(true); status.show(); ---- .Status Message with Progress Bar image::img/developer-guide/components-statusbar.png[Status Message with Progress Bar,scaledwidth=20%] We can automatically clear a status message/progress after a timeout using the `setExpires` method as such: [source,java] ---- Status status = ToastBar.getInstance().createStatus(); status.setMessage("Hello world"); status.setExpires(3000); // only show the status for 3 seconds, then have it automatically clear status.show(); ---- We can also delay the showing of the status message using `showDelayed` as such: [source,java] ---- Status status = ToastBar.getInstance().createStatus(); status.setMessage("Hello world"); status.showDelayed(300); // Wait 300 ms to show the status // ... Some time later, clear the status... this may be before it shows at all status.clear(); ---- .ToastBar with a multiline message image::img/developer-guide/components-statusbar-multiline.png[ToastBar with a multiline message,scaledwidth=20%] ==== Actions In ToastBar Probably the best usage example for actions in toast is in the gmail style undo. If you are not a gmail user then the gmail app essentially never prompts for confirmation! It just does whatever you ask and pops a "toast message" with an option to undo. So if you clicked by mistake you have 3-4 seconds to take that back. This simple example shows you how you can undo any addition to the UI in a similar way to gmail: [source,java] ---- Form hi = new Form("Undo", BoxLayout.y()); Button add = new Button("Add"); add.addActionListener(e -> { Label l = new Label("Added this"); hi.add(l); hi.revalidate(); ToastBar.showMessage("Added, click here to undo...", FontImage.MATERIAL_UNDO, ee -> { l.remove(); hi.revalidate(); }); }); hi.add(add); hi.show(); ---- === SignatureComponent The https://www.codenameone.com/javadoc/com/codename1/components/SignatureComponent.html[SignatureComponent] provides a widget that allows users to draw their signature in the app. Simple usage of the `SignatureComponent` class looks like: [source,java] ---- Form hi = new Form("Signature Component"); hi.setLayout(new BoxLayout(BoxLayout.Y_AXIS)); hi.add("Enter Your Name:"); hi.add(new TextField()); hi.add("Signature:"); SignatureComponent sig = new SignatureComponent(); sig.addActionListener((evt)-> { System.out.println("The signature was changed"); Image img = sig.getSignatureImage(); // Now we can do whatever we want with the image of this signature. }); hi.addComponent(sig); hi.show(); ---- .The signature Component image::img/developer-guide/components-signature2.png[The Signature Component,scaledwidth=25%] === Accordion The https://www.codenameone.com/javadoc/com/codename1/components/Accordion.html[Accordion] displays collapsible content panels. Simple usage of the `Accordion` class looks like: [source,java] ---- Form f = new Form("Accordion", new BoxLayout(BoxLayout.Y_AXIS)); f.setScrollableY(true); Accordion accr = new Accordion(); accr.addContent("Item1", new SpanLabel("The quick brown fox jumps over the lazy dog\n" + "The quick brown fox jumps over the lazy dog")); accr.addContent("Item2", new SpanLabel("The quick brown fox jumps over the lazy dog\n" + "The quick brown fox jumps over the lazy dog\n " + "The quick brown fox jumps over the lazy dog\n " + "The quick brown fox jumps over the lazy dog\n " + "")); accr.addContent("Item3", BoxLayout.encloseY(new Label("Label"), new TextField(), new Button("Button"), new CheckBox("CheckBox"))); f.add(accr); f.show(); ---- .The Accordion Component image::img/developer-guide/components-accordion.png[The Accordion Component,scaledwidth=30%] === Floating Hint https://www.codenameone.com/javadoc/com/codename1/components/FloatingHint.html[FloatingHint] wraps a text component with a special container that can animate the hint label into a title label when the text component is edited or has content within it. [source,java] ---- Form hi = new Form("Floating Hint", BoxLayout.y()); TextField first = new TextField("", "First Field"); TextField second = new TextField("", "Second Field"); hi.add(new FloatingHint(first)). add(new FloatingHint(second)). add(new Button("Go")); hi.show(); ---- .The FloatingHint component with one component that contains text and another that doesn't image::img/developer-guide/components-floatinghint.png[The FloatingHint component with one component that contains text and another that doesn't,scaledwidth=30%] === Floating Button The material design floating action button is a powerful tool for promoting an action within your application. https://www.codenameone.com/javadoc/com/codename1/components/FloatingActionButton.html[FloatingActionButton] is a round button that resides on top of the UI typically in the bottom right hand side. + It has a drop shadow to distinguish it from the UI underneath and it can hide two or more additional actions under the surface. E.g. we can create a simple single click button such as this: [source,java] ---- FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_ADD); fab.addActionListener(e -> ToastBar.showErrorMessage("Not implemented yet...")); fab.bindFabToContainer(form.getContentPane()); ---- Which will place a `+` sign button that will perform the action. Alternatively we can create a nested action where a click on the button will produce a submenu for users to pick from e.g.: [source,java] ---- FloatingActionButton fab = FloatingActionButton.createFAB(FontImage.MATERIAL_ADD); fab.createSubFAB(FontImage.MATERIAL_PEOPLE, ""); fab.createSubFAB(FontImage.MATERIAL_IMPORT_CONTACTS, ""); fab.bindFabToContainer(form.getContentPane()); ---- .FloatingActionButton with submenu expanded image::img/developer-guide/floating-action.png[FloatingActionButton with submenu expanded,scaledwidth=20%] Those familiar with this widget know that there are many nuances to this UI that we might implement/expose in the future. At the moment we chose to keep the API simple and minimal for the common use cases and refine it based on feedback. ==== Using Floating Button as a Badge Floating buttons can also be used to badge an arbitrary component in the style popularized by iOS/Mac OS. A badge appears at the top right corner and includes special numeric details such as "unread count".. The code below adds a simple badge to an icon button: [source,java] ---- Form hi = new Form("Badge"); Button chat = new Button(""); FontImage.setMaterialIcon(chat, FontImage.MATERIAL_CHAT, 7); FloatingActionButton badge = FloatingActionButton.createBadge("33"); hi.add(badge.bindFabToContainer(chat, Component.RIGHT, Component.TOP)); TextField changeBadgeValue = new TextField("33"); changeBadgeValue.addDataChangedListener((i, ii) -> { badge.setText(changeBadgeValue.getText()); badge.getParent().revalidate(); }); hi.add(changeBadgeValue); hi.show(); ---- The code above results in this, notice you can type into the text field to change the badge value: .Badge floating button in action image::img/developer-guide/badge-floating-button.png[Badge floating button in action,scaledwidth=20%] === SplitPane The split pane component is a bit desktop specific but works reasonably well on devices. To get the image below we changed `SalesDemo.java` in the kitchen sink by changing this: [source,java] ---- private Container encloseInMaximizableGrid(Component cmp1, Component cmp2) { GridLayout gl = new GridLayout(2, 1); Container grid = new Container(gl); gl.setHideZeroSized(true); grid.add(encloseInMaximize(grid, cmp1)). add(encloseInMaximize(grid, cmp2)); return grid; } ---- To: [source,java] ---- private Container encloseInMaximizableGrid(Component cmp1, Component cmp2) { return new SplitPane(SplitPane.VERTICAL_SPLIT, cmp1, cmp2, "25%", "50%", "75%"); } ---- .Split Pane in the Kitchen Sink Demo image::img/developer-guide/splitpane.png[Split Pane in the Kitchen Sink Demo,scaledwidth=30%] This is mostly self explanatory but only "mostly". We have 5 arguments the first 3 make sense: - Split orientation - Components to split The last 3 arguments seem weird but they also make sense once you understand them, they are: - The minimum position of the split - 1/4 of available space - The default position of the split - middle of the screen - The maximum position of the split - 3/4 of available space The units don't have to be percentages they can be mm (millimeters) or px (pixels).