SlideShare a Scribd company logo
Adapting to Tablets & Desktops - Part II
In this part we dig deeper into the UIAbstraction class
private FormDelegate formDelegate;
private Container cnt;
private Container decoratedContainer;
private static UIAbstraction currentUI;
public UIAbstraction(String title, Layout l) {
initUI(title, l);
}
protected void initUI(String title, Layout l) {
if(Display.getInstance().isTablet()) {
cnt = new Container(l);
if(!(l instanceof BorderLayout)) {
cnt.setScrollableY(true);
}
cnt.putClientProperty("uiabstraction", this);
decoratedContainer = cnt;
if(hasOKBack()) {
decoratedContainer = BorderLayout.center(cnt);
okButton = new Button("OK", "GreenButton");
Button cancelButton = new Button("Cancel", "GreenButton");
FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK);
FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL);
UIAbstraction
Moving on we reach the fields of the class and the start of the UI. Lets step over those…
private FormDelegate formDelegate;
private Container cnt;
private Container decoratedContainer;
private static UIAbstraction currentUI;
public UIAbstraction(String title, Layout l) {
initUI(title, l);
}
protected void initUI(String title, Layout l) {
if(Display.getInstance().isTablet()) {
cnt = new Container(l);
if(!(l instanceof BorderLayout)) {
cnt.setScrollableY(true);
}
cnt.putClientProperty("uiabstraction", this);
decoratedContainer = cnt;
if(hasOKBack()) {
decoratedContainer = BorderLayout.center(cnt);
okButton = new Button("OK", "GreenButton");
Button cancelButton = new Button("Cancel", "GreenButton");
FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK);
FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL);
UIAbstraction
First we have the form delegate instance, this is only relevant if we are in a phone mode and not in the tablet mode where we have a different class in place
private FormDelegate formDelegate;
private Container cnt;
private Container decoratedContainer;
private static UIAbstraction currentUI;
public UIAbstraction(String title, Layout l) {
initUI(title, l);
}
protected void initUI(String title, Layout l) {
if(Display.getInstance().isTablet()) {
cnt = new Container(l);
if(!(l instanceof BorderLayout)) {
cnt.setScrollableY(true);
}
cnt.putClientProperty("uiabstraction", this);
decoratedContainer = cnt;
if(hasOKBack()) {
decoratedContainer = BorderLayout.center(cnt);
okButton = new Button("OK", "GreenButton");
Button cancelButton = new Button("Cancel", "GreenButton");
FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK);
FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL);
UIAbstraction
The container is the current UI. Since Form is a subclass of Container this will work for both the form and the tablet UI mode
private FormDelegate formDelegate;
private Container cnt;
private Container decoratedContainer;
private static UIAbstraction currentUI;
public UIAbstraction(String title, Layout l) {
initUI(title, l);
}
protected void initUI(String title, Layout l) {
if(Display.getInstance().isTablet()) {
cnt = new Container(l);
if(!(l instanceof BorderLayout)) {
cnt.setScrollableY(true);
}
cnt.putClientProperty("uiabstraction", this);
decoratedContainer = cnt;
if(hasOKBack()) {
decoratedContainer = BorderLayout.center(cnt);
okButton = new Button("OK", "GreenButton");
Button cancelButton = new Button("Cancel", "GreenButton");
FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK);
FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL);
UIAbstraction
Elements such as the OK/Cancel buttons are added to a Container and then the main container is “wrapped up” to include these additional buttons. This allows us to
keep the code mostly seamless but we sometimes need access to the actual decorated container for example when we need to replace it. 

Don’t worry if you don’t understand this, it will be clearer as I go thru the actual code.
private FormDelegate formDelegate;
private Container cnt;
private Container decoratedContainer;
private static UIAbstraction currentUI;
public UIAbstraction(String title, Layout l) {
initUI(title, l);
}
protected void initUI(String title, Layout l) {
if(Display.getInstance().isTablet()) {
cnt = new Container(l);
if(!(l instanceof BorderLayout)) {
cnt.setScrollableY(true);
}
cnt.putClientProperty("uiabstraction", this);
decoratedContainer = cnt;
if(hasOKBack()) {
decoratedContainer = BorderLayout.center(cnt);
okButton = new Button("OK", "GreenButton");
Button cancelButton = new Button("Cancel", "GreenButton");
FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK);
FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL);
UIAbstraction
This is essentially the equivalent of getCurrentForm() only in this case we get the current UIAbstraction which will work correctly.
private FormDelegate formDelegate;
private Container cnt;
private Container decoratedContainer;
private static UIAbstraction currentUI;
public UIAbstraction(String title, Layout l) {
initUI(title, l);
}
protected void initUI(String title, Layout l) {
if(Display.getInstance().isTablet()) {
cnt = new Container(l);
if(!(l instanceof BorderLayout)) {
cnt.setScrollableY(true);
}
cnt.putClientProperty("uiabstraction", this);
decoratedContainer = cnt;
if(hasOKBack()) {
decoratedContainer = BorderLayout.center(cnt);
okButton = new Button("OK", "GreenButton");
Button cancelButton = new Button("Cancel", "GreenButton");
FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK);
FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL);
UIAbstraction
Lets move on to the creation code and specifically the code that occurs when working with a tablet. Notice that isTablet will return true for desktops as well so there isn’t
as much of a need for isDesktop unless you need it to be truly distinctive from tablet
private FormDelegate formDelegate;
private Container cnt;
private Container decoratedContainer;
private static UIAbstraction currentUI;
public UIAbstraction(String title, Layout l) {
initUI(title, l);
}
protected void initUI(String title, Layout l) {
if(Display.getInstance().isTablet()) {
cnt = new Container(l);
if(!(l instanceof BorderLayout)) {
cnt.setScrollableY(true);
}
cnt.putClientProperty("uiabstraction", this);
decoratedContainer = cnt;
if(hasOKBack()) {
decoratedContainer = BorderLayout.center(cnt);
okButton = new Button("OK", "GreenButton");
Button cancelButton = new Button("Cancel", "GreenButton");
FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK);
FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL);
UIAbstraction
By default in Codename One the content pane of a form is scrollable on the Y axis. So I tried to replicate this behavior here so the code will feel seamless.
private FormDelegate formDelegate;
private Container cnt;
private Container decoratedContainer;
private static UIAbstraction currentUI;
public UIAbstraction(String title, Layout l) {
initUI(title, l);
}
protected void initUI(String title, Layout l) {
if(Display.getInstance().isTablet()) {
cnt = new Container(l);
if(!(l instanceof BorderLayout)) {
cnt.setScrollableY(true);
}
cnt.putClientProperty("uiabstraction", this);
decoratedContainer = cnt;
if(hasOKBack()) {
decoratedContainer = BorderLayout.center(cnt);
okButton = new Button("OK", "GreenButton");
Button cancelButton = new Button("Cancel", "GreenButton");
FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK);
FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL);
UIAbstraction
The uiabstraction client property allows us to get access to the UIAbstraction class from the component. This is useful for methods that need access to navigation logic
but run from the UI code e.g. a button in the UI that triggers form navigation.

The decorated container defaults to this container…
private FormDelegate formDelegate;
private Container cnt;
private Container decoratedContainer;
private static UIAbstraction currentUI;
public UIAbstraction(String title, Layout l) {
initUI(title, l);
}
protected void initUI(String title, Layout l) {
if(Display.getInstance().isTablet()) {
cnt = new Container(l);
if(!(l instanceof BorderLayout)) {
cnt.setScrollableY(true);
}
cnt.putClientProperty("uiabstraction", this);
decoratedContainer = cnt;
if(hasOKBack()) {
decoratedContainer = BorderLayout.center(cnt);
okButton = new Button("OK", "GreenButton");
Button cancelButton = new Button("Cancel", "GreenButton");
FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK);
FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL);
UIAbstraction
Unless we have a back/ok button pair in which case we create a new wrapper of the container and use the decorated container for that. We just create ok and cancel
buttons
decoratedContainer.add(BorderLayout.SOUTH,
GridLayout.encloseIn(2, cancelButton, okButton));
decoratedContainer.putClientProperty("uiabstraction", this);
UIAbstraction previous = currentUI;
okButton.addActionListener(e -> {
onOK();
previous.showBack();
});
cancelButton.addActionListener(e -> {
onBack();
previous.showBack();
});
}
} else {
formDelegate = new FormDelegate(title, l);
formDelegate.putClientProperty("uiabstraction", this);
cnt = formDelegate;
}
}
UIAbstraction
Which we add into the south of the container using a grid layout. Notice the usage of grid layout ensures both buttons will have exactly the same size which contributes
to nice aesthetics.
decoratedContainer.add(BorderLayout.SOUTH,
GridLayout.encloseIn(2, cancelButton, okButton));
decoratedContainer.putClientProperty("uiabstraction", this);
UIAbstraction previous = currentUI;
okButton.addActionListener(e -> {
onOK();
previous.showBack();
});
cancelButton.addActionListener(e -> {
onBack();
previous.showBack();
});
}
} else {
formDelegate = new FormDelegate(title, l);
formDelegate.putClientProperty("uiabstraction", this);
cnt = formDelegate;
}
}
UIAbstraction
If you recall the previous code for OK & cancel we had in the Form delegate this will seem almost identical. But then you might notice the current UI is really the
UIAbstraction class and we use showBack() from there and not from the Form!
decoratedContainer.add(BorderLayout.SOUTH,
GridLayout.encloseIn(2, cancelButton, okButton));
decoratedContainer.putClientProperty("uiabstraction", this);
UIAbstraction previous = currentUI;
okButton.addActionListener(e -> {
onOK();
previous.showBack();
});
cancelButton.addActionListener(e -> {
onBack();
previous.showBack();
});
}
} else {
formDelegate = new FormDelegate(title, l);
formDelegate.putClientProperty("uiabstraction", this);
cnt = formDelegate;
}
}
UIAbstraction
This is the else statement that runs in case of regular forms or in phone mode. It just defers everything to the form delegate.
public void show() {
if(formDelegate != null) {
formDelegate.show();
} else {
TabletUI.getInstance().showContainer(decoratedContainer, true);
currentUI = this;
}
}
public void showBack() {
if(formDelegate != null) {
formDelegate.showBack();
} else {
TabletUI.getInstance().showContainer(decoratedContainer, true);
currentUI = this;
}
}
public Container add(Object con, Component cmp) {
return cnt.add(con, cmp);
}
public Container add(Component cmp) {
return cnt.add(cmp);
}
UIAbstraction
Moving on we can see the common methods for show are implemented normally as one would expect by calling show or showBack respectively
public void show() {
if(formDelegate != null) {
formDelegate.show();
} else {
TabletUI.getInstance().showContainer(decoratedContainer, true);
currentUI = this;
}
}
public void showBack() {
if(formDelegate != null) {
formDelegate.showBack();
} else {
TabletUI.getInstance().showContainer(decoratedContainer, false);
currentUI = this;
}
}
public Container add(Object con, Component cmp) {
return cnt.add(con, cmp);
}
public Container add(Component cmp) {
return cnt.add(cmp);
}
UIAbstraction
But there is something interesting here… Tablet UI which we use to implement the show logic. TabletUI is the equivalent for the form delegate for tablets but unlike that
class it’s a single form that never changes. I’ll go to that class soon
public void show() {
if(formDelegate != null) {
formDelegate.show();
} else {
TabletUI.getInstance().showContainer(decoratedContainer, true);
currentUI = this;
}
}
public void showBack() {
if(formDelegate != null) {
formDelegate.showBack();
} else {
TabletUI.getInstance().showContainer(decoratedContainer, false);
currentUI = this;
}
}
public Container add(Object con, Component cmp) {
return cnt.add(con, cmp);
}
public Container add(Component cmp) {
return cnt.add(cmp);
}
UIAbstraction
Other delegates work seamlessly with the container… Notice that add on a form will work well and so will add on a container. So in this case we can just seamlessly
delegate.
protected boolean shouldPaintStatusBar() {
return UIManager.getInstance().isThemeConstant("paintsTitleBarBool", false);
}
public Container getContainer() {
return cnt;
}
public void bindFab(FloatingActionButton fab) {
if(formDelegate != null) {
fab.bindFabToContainer(formDelegate.getContentPane());
} else {
decoratedContainer = fab.bindFabToContainer(decoratedContainer);
decoratedContainer.putClientProperty("uiabstraction", this);
}
}
public void bindFabTopRight(FloatingActionButton fab) {
if(formDelegate != null) {
fab.bindFabToContainer(formDelegate.getContentPane(), RIGHT, TOP);
} else {
decoratedContainer = fab.bindFabToContainer(decoratedContainer, RIGHT,
TOP);
decoratedContainer.putClientProperty("uiabstraction", this);
}
}
UIAbstraction
Bind fab works exactly as we would expect for a form but with the tablet we need to re-wrap the decorated container and use that as the new decorated container. To
understand why you need to understand that fab wraps the content of a Container and adds a layered layout where it resides. If the parent is a form already then we have
a layered pane and it just uses that… But if it’s not then we need to use the container that the fab returns which is where the delegate container comes in really handy…
public void setLayout(Layout newLayout) {
cnt.setLayout(newLayout);
}
public static UIAbstraction getUIAbstraction(Container parent) {
return (UIAbstraction)parent.getClientProperty("uiabstraction");
}
protected void initToolbar(Toolbar tb) {
}
protected void onOK() {
}
protected void onBack() {
}
protected boolean hasOKBack() {
return false;
}
protected void setScrollableY(boolean b) {
cnt.setScrollableY(b);
}
protected void addSubmitButton(Validator val) {
if(okButton != null) {
val.addSubmitButtons(okButton);
}
}
UIAbstraction
Finally all of these methods are just callbacks that we invoke in the code
public void setLayout(Layout newLayout) {
cnt.setLayout(newLayout);
}
public static UIAbstraction getUIAbstraction(Container parent) {
return (UIAbstraction)parent.getClientProperty("uiabstraction");
}
protected void initToolbar(Toolbar tb) {
}
protected void onOK() {
}
protected void onBack() {
}
protected boolean hasOKBack() {
return false;
}
protected void setScrollableY(boolean b) {
cnt.setScrollableY(b);
}
protected void addSubmitButton(Validator val) {
if(okButton != null) {
val.addSubmitButtons(okButton);
}
}
UIAbstraction
The submit button validation is handled here so it will work with the internal OK button without exposing a button component that might be fragile.

More Related Content

PDF
Adapting to Tablets and Desktops - Part 2.pdf
PDF
Adapting to Tablets and Desktops - Part 1.pdf
PDF
Adapting to Tablets and Desktops - Part 1 - Transcript.pdf
PDF
Adapting to Tablets and Desktops - Part 3 - Transcript.pdf
PDF
Initial UI Mockup - Part 2.pdf
PDF
Creating a Facebook Clone - Part IV - Transcript.pdf
PDF
Creating an Uber Clone - Part IV - Transcript.pdf
PDF
Creating an Uber Clone - Part VIII - Transcript.pdf
Adapting to Tablets and Desktops - Part 2.pdf
Adapting to Tablets and Desktops - Part 1.pdf
Adapting to Tablets and Desktops - Part 1 - Transcript.pdf
Adapting to Tablets and Desktops - Part 3 - Transcript.pdf
Initial UI Mockup - Part 2.pdf
Creating a Facebook Clone - Part IV - Transcript.pdf
Creating an Uber Clone - Part IV - Transcript.pdf
Creating an Uber Clone - Part VIII - Transcript.pdf

More from ShaiAlmog1 (20)

PDF
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
PDF
create-netflix-clone-06-client-ui.pdf
PDF
create-netflix-clone-01-introduction_transcript.pdf
PDF
create-netflix-clone-02-server_transcript.pdf
PDF
create-netflix-clone-04-server-continued_transcript.pdf
PDF
create-netflix-clone-01-introduction.pdf
PDF
create-netflix-clone-06-client-ui_transcript.pdf
PDF
create-netflix-clone-03-server.pdf
PDF
create-netflix-clone-04-server-continued.pdf
PDF
create-netflix-clone-05-client-model_transcript.pdf
PDF
create-netflix-clone-03-server_transcript.pdf
PDF
create-netflix-clone-02-server.pdf
PDF
create-netflix-clone-05-client-model.pdf
PDF
Creating a Whatsapp Clone - Part II.pdf
PDF
Creating a Whatsapp Clone - Part IX - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part II - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part V - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV.pdf
PDF
Creating a Whatsapp Clone - Part I - Transcript.pdf
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
create-netflix-clone-06-client-ui.pdf
create-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-02-server_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-01-introduction.pdf
create-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-03-server.pdf
create-netflix-clone-04-server-continued.pdf
create-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-03-server_transcript.pdf
create-netflix-clone-02-server.pdf
create-netflix-clone-05-client-model.pdf
Creating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdf

Recently uploaded (20)

PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPTX
sap open course for s4hana steps from ECC to s4
PPTX
Spectroscopy.pptx food analysis technology
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
Approach and Philosophy of On baking technology
PPTX
Programs and apps: productivity, graphics, security and other tools
PPT
Teaching material agriculture food technology
PDF
KodekX | Application Modernization Development
PDF
Machine learning based COVID-19 study performance prediction
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Electronic commerce courselecture one. Pdf
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
MIND Revenue Release Quarter 2 2025 Press Release
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Advanced methodologies resolving dimensionality complications for autism neur...
sap open course for s4hana steps from ECC to s4
Spectroscopy.pptx food analysis technology
“AI and Expert System Decision Support & Business Intelligence Systems”
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
Digital-Transformation-Roadmap-for-Companies.pptx
20250228 LYD VKU AI Blended-Learning.pptx
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Approach and Philosophy of On baking technology
Programs and apps: productivity, graphics, security and other tools
Teaching material agriculture food technology
KodekX | Application Modernization Development
Machine learning based COVID-19 study performance prediction
Dropbox Q2 2025 Financial Results & Investor Presentation
Electronic commerce courselecture one. Pdf
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
MIND Revenue Release Quarter 2 2025 Press Release
The AUB Centre for AI in Media Proposal.docx
Build a system with the filesystem maintained by OSTree @ COSCUP 2025

Adapting to Tablets and Desktops - Part 2 - Transcript.pdf

  • 1. Adapting to Tablets & Desktops - Part II In this part we dig deeper into the UIAbstraction class
  • 2. private FormDelegate formDelegate; private Container cnt; private Container decoratedContainer; private static UIAbstraction currentUI; public UIAbstraction(String title, Layout l) { initUI(title, l); } protected void initUI(String title, Layout l) { if(Display.getInstance().isTablet()) { cnt = new Container(l); if(!(l instanceof BorderLayout)) { cnt.setScrollableY(true); } cnt.putClientProperty("uiabstraction", this); decoratedContainer = cnt; if(hasOKBack()) { decoratedContainer = BorderLayout.center(cnt); okButton = new Button("OK", "GreenButton"); Button cancelButton = new Button("Cancel", "GreenButton"); FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK); FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL); UIAbstraction Moving on we reach the fields of the class and the start of the UI. Lets step over those…
  • 3. private FormDelegate formDelegate; private Container cnt; private Container decoratedContainer; private static UIAbstraction currentUI; public UIAbstraction(String title, Layout l) { initUI(title, l); } protected void initUI(String title, Layout l) { if(Display.getInstance().isTablet()) { cnt = new Container(l); if(!(l instanceof BorderLayout)) { cnt.setScrollableY(true); } cnt.putClientProperty("uiabstraction", this); decoratedContainer = cnt; if(hasOKBack()) { decoratedContainer = BorderLayout.center(cnt); okButton = new Button("OK", "GreenButton"); Button cancelButton = new Button("Cancel", "GreenButton"); FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK); FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL); UIAbstraction First we have the form delegate instance, this is only relevant if we are in a phone mode and not in the tablet mode where we have a different class in place
  • 4. private FormDelegate formDelegate; private Container cnt; private Container decoratedContainer; private static UIAbstraction currentUI; public UIAbstraction(String title, Layout l) { initUI(title, l); } protected void initUI(String title, Layout l) { if(Display.getInstance().isTablet()) { cnt = new Container(l); if(!(l instanceof BorderLayout)) { cnt.setScrollableY(true); } cnt.putClientProperty("uiabstraction", this); decoratedContainer = cnt; if(hasOKBack()) { decoratedContainer = BorderLayout.center(cnt); okButton = new Button("OK", "GreenButton"); Button cancelButton = new Button("Cancel", "GreenButton"); FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK); FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL); UIAbstraction The container is the current UI. Since Form is a subclass of Container this will work for both the form and the tablet UI mode
  • 5. private FormDelegate formDelegate; private Container cnt; private Container decoratedContainer; private static UIAbstraction currentUI; public UIAbstraction(String title, Layout l) { initUI(title, l); } protected void initUI(String title, Layout l) { if(Display.getInstance().isTablet()) { cnt = new Container(l); if(!(l instanceof BorderLayout)) { cnt.setScrollableY(true); } cnt.putClientProperty("uiabstraction", this); decoratedContainer = cnt; if(hasOKBack()) { decoratedContainer = BorderLayout.center(cnt); okButton = new Button("OK", "GreenButton"); Button cancelButton = new Button("Cancel", "GreenButton"); FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK); FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL); UIAbstraction Elements such as the OK/Cancel buttons are added to a Container and then the main container is “wrapped up” to include these additional buttons. This allows us to keep the code mostly seamless but we sometimes need access to the actual decorated container for example when we need to replace it. Don’t worry if you don’t understand this, it will be clearer as I go thru the actual code.
  • 6. private FormDelegate formDelegate; private Container cnt; private Container decoratedContainer; private static UIAbstraction currentUI; public UIAbstraction(String title, Layout l) { initUI(title, l); } protected void initUI(String title, Layout l) { if(Display.getInstance().isTablet()) { cnt = new Container(l); if(!(l instanceof BorderLayout)) { cnt.setScrollableY(true); } cnt.putClientProperty("uiabstraction", this); decoratedContainer = cnt; if(hasOKBack()) { decoratedContainer = BorderLayout.center(cnt); okButton = new Button("OK", "GreenButton"); Button cancelButton = new Button("Cancel", "GreenButton"); FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK); FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL); UIAbstraction This is essentially the equivalent of getCurrentForm() only in this case we get the current UIAbstraction which will work correctly.
  • 7. private FormDelegate formDelegate; private Container cnt; private Container decoratedContainer; private static UIAbstraction currentUI; public UIAbstraction(String title, Layout l) { initUI(title, l); } protected void initUI(String title, Layout l) { if(Display.getInstance().isTablet()) { cnt = new Container(l); if(!(l instanceof BorderLayout)) { cnt.setScrollableY(true); } cnt.putClientProperty("uiabstraction", this); decoratedContainer = cnt; if(hasOKBack()) { decoratedContainer = BorderLayout.center(cnt); okButton = new Button("OK", "GreenButton"); Button cancelButton = new Button("Cancel", "GreenButton"); FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK); FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL); UIAbstraction Lets move on to the creation code and specifically the code that occurs when working with a tablet. Notice that isTablet will return true for desktops as well so there isn’t as much of a need for isDesktop unless you need it to be truly distinctive from tablet
  • 8. private FormDelegate formDelegate; private Container cnt; private Container decoratedContainer; private static UIAbstraction currentUI; public UIAbstraction(String title, Layout l) { initUI(title, l); } protected void initUI(String title, Layout l) { if(Display.getInstance().isTablet()) { cnt = new Container(l); if(!(l instanceof BorderLayout)) { cnt.setScrollableY(true); } cnt.putClientProperty("uiabstraction", this); decoratedContainer = cnt; if(hasOKBack()) { decoratedContainer = BorderLayout.center(cnt); okButton = new Button("OK", "GreenButton"); Button cancelButton = new Button("Cancel", "GreenButton"); FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK); FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL); UIAbstraction By default in Codename One the content pane of a form is scrollable on the Y axis. So I tried to replicate this behavior here so the code will feel seamless.
  • 9. private FormDelegate formDelegate; private Container cnt; private Container decoratedContainer; private static UIAbstraction currentUI; public UIAbstraction(String title, Layout l) { initUI(title, l); } protected void initUI(String title, Layout l) { if(Display.getInstance().isTablet()) { cnt = new Container(l); if(!(l instanceof BorderLayout)) { cnt.setScrollableY(true); } cnt.putClientProperty("uiabstraction", this); decoratedContainer = cnt; if(hasOKBack()) { decoratedContainer = BorderLayout.center(cnt); okButton = new Button("OK", "GreenButton"); Button cancelButton = new Button("Cancel", "GreenButton"); FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK); FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL); UIAbstraction The uiabstraction client property allows us to get access to the UIAbstraction class from the component. This is useful for methods that need access to navigation logic but run from the UI code e.g. a button in the UI that triggers form navigation. The decorated container defaults to this container…
  • 10. private FormDelegate formDelegate; private Container cnt; private Container decoratedContainer; private static UIAbstraction currentUI; public UIAbstraction(String title, Layout l) { initUI(title, l); } protected void initUI(String title, Layout l) { if(Display.getInstance().isTablet()) { cnt = new Container(l); if(!(l instanceof BorderLayout)) { cnt.setScrollableY(true); } cnt.putClientProperty("uiabstraction", this); decoratedContainer = cnt; if(hasOKBack()) { decoratedContainer = BorderLayout.center(cnt); okButton = new Button("OK", "GreenButton"); Button cancelButton = new Button("Cancel", "GreenButton"); FontImage.setMaterialIcon(okButton, FontImage.MATERIAL_CHECK); FontImage.setMaterialIcon(cancelButton, FontImage.MATERIAL_CANCEL); UIAbstraction Unless we have a back/ok button pair in which case we create a new wrapper of the container and use the decorated container for that. We just create ok and cancel buttons
  • 11. decoratedContainer.add(BorderLayout.SOUTH, GridLayout.encloseIn(2, cancelButton, okButton)); decoratedContainer.putClientProperty("uiabstraction", this); UIAbstraction previous = currentUI; okButton.addActionListener(e -> { onOK(); previous.showBack(); }); cancelButton.addActionListener(e -> { onBack(); previous.showBack(); }); } } else { formDelegate = new FormDelegate(title, l); formDelegate.putClientProperty("uiabstraction", this); cnt = formDelegate; } } UIAbstraction Which we add into the south of the container using a grid layout. Notice the usage of grid layout ensures both buttons will have exactly the same size which contributes to nice aesthetics.
  • 12. decoratedContainer.add(BorderLayout.SOUTH, GridLayout.encloseIn(2, cancelButton, okButton)); decoratedContainer.putClientProperty("uiabstraction", this); UIAbstraction previous = currentUI; okButton.addActionListener(e -> { onOK(); previous.showBack(); }); cancelButton.addActionListener(e -> { onBack(); previous.showBack(); }); } } else { formDelegate = new FormDelegate(title, l); formDelegate.putClientProperty("uiabstraction", this); cnt = formDelegate; } } UIAbstraction If you recall the previous code for OK & cancel we had in the Form delegate this will seem almost identical. But then you might notice the current UI is really the UIAbstraction class and we use showBack() from there and not from the Form!
  • 13. decoratedContainer.add(BorderLayout.SOUTH, GridLayout.encloseIn(2, cancelButton, okButton)); decoratedContainer.putClientProperty("uiabstraction", this); UIAbstraction previous = currentUI; okButton.addActionListener(e -> { onOK(); previous.showBack(); }); cancelButton.addActionListener(e -> { onBack(); previous.showBack(); }); } } else { formDelegate = new FormDelegate(title, l); formDelegate.putClientProperty("uiabstraction", this); cnt = formDelegate; } } UIAbstraction This is the else statement that runs in case of regular forms or in phone mode. It just defers everything to the form delegate.
  • 14. public void show() { if(formDelegate != null) { formDelegate.show(); } else { TabletUI.getInstance().showContainer(decoratedContainer, true); currentUI = this; } } public void showBack() { if(formDelegate != null) { formDelegate.showBack(); } else { TabletUI.getInstance().showContainer(decoratedContainer, true); currentUI = this; } } public Container add(Object con, Component cmp) { return cnt.add(con, cmp); } public Container add(Component cmp) { return cnt.add(cmp); } UIAbstraction Moving on we can see the common methods for show are implemented normally as one would expect by calling show or showBack respectively
  • 15. public void show() { if(formDelegate != null) { formDelegate.show(); } else { TabletUI.getInstance().showContainer(decoratedContainer, true); currentUI = this; } } public void showBack() { if(formDelegate != null) { formDelegate.showBack(); } else { TabletUI.getInstance().showContainer(decoratedContainer, false); currentUI = this; } } public Container add(Object con, Component cmp) { return cnt.add(con, cmp); } public Container add(Component cmp) { return cnt.add(cmp); } UIAbstraction But there is something interesting here… Tablet UI which we use to implement the show logic. TabletUI is the equivalent for the form delegate for tablets but unlike that class it’s a single form that never changes. I’ll go to that class soon
  • 16. public void show() { if(formDelegate != null) { formDelegate.show(); } else { TabletUI.getInstance().showContainer(decoratedContainer, true); currentUI = this; } } public void showBack() { if(formDelegate != null) { formDelegate.showBack(); } else { TabletUI.getInstance().showContainer(decoratedContainer, false); currentUI = this; } } public Container add(Object con, Component cmp) { return cnt.add(con, cmp); } public Container add(Component cmp) { return cnt.add(cmp); } UIAbstraction Other delegates work seamlessly with the container… Notice that add on a form will work well and so will add on a container. So in this case we can just seamlessly delegate.
  • 17. protected boolean shouldPaintStatusBar() { return UIManager.getInstance().isThemeConstant("paintsTitleBarBool", false); } public Container getContainer() { return cnt; } public void bindFab(FloatingActionButton fab) { if(formDelegate != null) { fab.bindFabToContainer(formDelegate.getContentPane()); } else { decoratedContainer = fab.bindFabToContainer(decoratedContainer); decoratedContainer.putClientProperty("uiabstraction", this); } } public void bindFabTopRight(FloatingActionButton fab) { if(formDelegate != null) { fab.bindFabToContainer(formDelegate.getContentPane(), RIGHT, TOP); } else { decoratedContainer = fab.bindFabToContainer(decoratedContainer, RIGHT, TOP); decoratedContainer.putClientProperty("uiabstraction", this); } } UIAbstraction Bind fab works exactly as we would expect for a form but with the tablet we need to re-wrap the decorated container and use that as the new decorated container. To understand why you need to understand that fab wraps the content of a Container and adds a layered layout where it resides. If the parent is a form already then we have a layered pane and it just uses that… But if it’s not then we need to use the container that the fab returns which is where the delegate container comes in really handy…
  • 18. public void setLayout(Layout newLayout) { cnt.setLayout(newLayout); } public static UIAbstraction getUIAbstraction(Container parent) { return (UIAbstraction)parent.getClientProperty("uiabstraction"); } protected void initToolbar(Toolbar tb) { } protected void onOK() { } protected void onBack() { } protected boolean hasOKBack() { return false; } protected void setScrollableY(boolean b) { cnt.setScrollableY(b); } protected void addSubmitButton(Validator val) { if(okButton != null) { val.addSubmitButtons(okButton); } } UIAbstraction Finally all of these methods are just callbacks that we invoke in the code
  • 19. public void setLayout(Layout newLayout) { cnt.setLayout(newLayout); } public static UIAbstraction getUIAbstraction(Container parent) { return (UIAbstraction)parent.getClientProperty("uiabstraction"); } protected void initToolbar(Toolbar tb) { } protected void onOK() { } protected void onBack() { } protected boolean hasOKBack() { return false; } protected void setScrollableY(boolean b) { cnt.setScrollableY(b); } protected void addSubmitButton(Validator val) { if(okButton != null) { val.addSubmitButtons(okButton); } } UIAbstraction The submit button validation is handled here so it will work with the internal OK button without exposing a button component that might be fragile.