Programming an app
To program an app, you need a development environment. Use the same environment you would use for other Magnolia development tasks.
See Development environment for instructions on how to set up your development environment. |
Components
The term component is a developer term that here refers to Java objects, not templating components.
A component provides a service such as keeping a registry of templates. Components expose their functionality to other components through an interface or an API. A typical component depends on a number of other components and benefits from dependency injection as Magnolia will connect components to satisfy their dependencies. Components are either singletons, which means that they are around for the lifetime of the server, or they are tied to shorter durations such as the duration of an HTTP request or a user session. The duration is known as scope.
Apps and components
Each app has its own component provider that uses components in the
app-<app name>
group. Each subapp has a component provider that uses
components described for it. Component providers have the app component
provider as their parent:
Defining app components in a module descriptor
Define your components in the module descriptor. Here is a minimal module descriptor that doesn’t declare any components yet. It just declares the module name and version.
<!DOCTYPE module SYSTEM "module.dtd">
<module>
<name>ui-helloworld-app</name>
<displayName>Hello World App</displayName>
<description>The Famous Hello World App</description>
<version>1.0</version>
</module>
Defining an app or subapp as a component
Best practice
Defining something as a component allows other developers quickly change the implementation. For this reason, you should define anything that is likely to be changed or customized later as a component. For example, define your apps and subapps as components. Views are also common customization points so define the views returned by subapps as components. Any components you define are available for injection. This is also true for components further up the hierarchy in parent components.
In the module descriptor file, add a section for each component. Name the components using the following convention:
-
app-<app name>
-
app-<app name>-<subapp name>
The <app name>
is the name configured in the app configuration in
AdminCentral. For example:
-
app-helloworld
-
app-helloworld-main
-
app-helloworld-greeter
<components>
<id>app-helloworld</id>
</components>
<components>
<id>app-helloworld-main</id>
<component>
<type>info.magnolia.ui.app.helloworld.main.HelloWorldMainSubAppView</type>
<implementation>info.magnolia.ui.app.helloworld.main.HelloWorldMainSubAppViewImpl</implementation>
</component>
</components>
<components>
<id>app-helloworld-greeter</id>
<component>
<type>info.magnolia.ui.app.helloworld.greeter.HelloWorldGreeterSubAppView</type>
<implementation>info.magnolia.ui.app.helloworld.greeter.HelloWorldGreeterSubAppViewImpl</implementation>
</component>
</components>
Project structure
-
Use a Maven archetype to get a ready-made Magnolia module structure. An archetype has a top-level project and a webapp configured as a Maven submodule.
-
Structure your app project in the IDE as shown in the example below. Put the app and subapp classes in the same folder.
-
Export the app configuration and the app launcher configuration into XML and put them in the
resources
folder. The module descriptor file goes inresources/META-INF.magnolia
.
App project structure in your IDE:
Implementing an app
-
Create a class that implements the
App
interface, or extend theBaseApp
class. The second option is more convenient. -
In your app class, implement any custom methods or functionality you need.
-
Inject the
AppContext
andAppView
as a dependency.
This is only needed if you want to extend the App by custom functionality.
Otherwise use info.magnolia.ui.framework.app.BaseApp as the app class in the configuration.
|
public class HelloWorldApp extends BaseApp {
@Inject
public HelloWorldApp(AppContext appContext, AppView view) {
super(appContext, view);
}
...
}
Starting an app
-
To start an app, you can either search for it In the Find Bar or click the app launcher icon (mgnl-app-launcher: [])and then click the the icon. To do this programmatically, you can trigger a
LocationChange
event by callingLocationController.goTo()
with the location as an argument. -
The
AppController
will now take care of opening the correct app and subapp based on the app name and subapp ID as defined in the configuration. -
To hook into the starting cycle or location handling of an app:
public class HelloWorldApp extends BaseApp {
...
@Override
public void start(Location location) {
super.start(location);
// code to extend default behavior
}
@Override
public void locationChanged(Location location) {
super.locationChanged(location);
// code to extend default behavior
}
}
Stopping an app
When an app stops, you get a callback to the stop
method to do
cleanup. The cleanup can involve closing files, closing network
connections and so on.
@Override
public void stop() {
// code to extend default behavior
}
Handling location changes
When your app is requested with a location fragment, you might need to
handle changing the location. In your app class, override the
locationChanged
method.
@Override
public void locationChanged(Location location) {
super.locationChanged(location);
String parameter = location.getParameter();
doFoo(parameter);
}
Remember, if the subapp is requested with a location that is already open in a running subapp, the Magnolia Shell takes care of bringing that subapp to focus instead of starting a new one.
SubApp
Subapp is an app that runs in its own tab. For example, the Pages app
always has the main
subapp open in the first tab and multiple detail
subapps for each page that is edited. The main
subapp has the label
Pages
while the detail
subapps are labeled according to the page
title, for example Travel Home.
When the subapp starts it must return a view. The view draws a user interface on the tab so that there is something for the user to interact with. The view takes up the whole tab and adds a caption.
Implementing a SubApp
Create a class that implements the SubApp interface or extend the
BaseSubApp
class. The second option is more convenient. In your subApp
class, implement any custom methods or functionality you need. Inject
the SubAppContext
and a view associated with the subApp as a
dependency.
public class MyMainSubApp extends BaseSubApp {
@Inject
public MyMainSubApp(final SubAppContext subAppContext, final View view) {
super(subAppContext, view);
}
}
Starting a subApp
The default implementation for starting and changing the location of
Apps is being delegated to the actual subApp defined in the location
.
If a subApp with the referenced subApp Id
is already running, it will
be asked whether it actually supports the location
.
public class BaseSubApp implements SubApp {
...
@Override
public boolean supportsLocation(Location location) {
return true;
}
}
In this case all locations with a reference to its subApp Id will be
handled by this subApp instance. By overriding this method and make it
return false
you would open a new tab for every location change the
AppController gets which is targeted to this subAppId. To open a new tab
based on the parameters passed to the subApp, it would look like this:
public class MySubApp extends BaseSubApp {
...
@Override
public boolean supportsLocation(Location location) {
DefaultLocation newLocation = (DefaultLocation) location;
String currentParameter = getCurrentLocation().getParameter();
return currentParameter.equals(newLocation.getParameter());
}
}
To hook into the starting cycle or location handling of an App:
public class MySubApp extends BaseSubApp {
...
@Override
protected void onSubAppStart() {
// code to extend default behavior
}
@Override
protected void onSubAppStop() {
// code to extend default behavior
}
@Override
public void locationChanged(Location location) {
super.locationChanged(location);
// code to extend default behavior
}
}
Module dependencies
You need to declare your dependencies in the module descriptor. You can define runtime or install time dependencies, not build dependencies. If you define a dependency on another module, then this module will be installed and started before your module. In order to develop a basic app, add the following section to your module descriptor:
<dependencies>
<dependency>
<name>ui-admincentral</name>
<version>5.0/*</version>
</dependency>
</dependencies>
Hello World example
This is a step-by-step guide on how to implement a basic hello world
app. When started, it shows a main
subApp containing two buttons.
Clicking on a button should open a new greeter
subApp greeting the
user associated with the button.
Main SubApp
The logic is taken care of by the HelloWorldMainSubApp
which adds
buttons to the view and listens to actions by implementing the
Listener
interface.
In addition to the SubAppContext
and the View
we also inject the
LocationController
which will be used to open the greeter
subApp.
public class HelloWorldMainSubApp extends BaseSubApp implements HelloWorldMainSubAppView.Listener {
private LocationController locationController;
@Inject
public HelloWorldMainSubApp(final SubAppContext subAppContext, HelloWorldMainSubAppView view, LocationController locationController) {
super(subAppContext, view);
this.locationController = locationController;
}
@Override
protected void onSubAppStart() {
getView().setListener(this);
getView().addUser("Lisa");
getView().addUser("Mark");
}
@Override
public HelloWorldMainSubAppView getView() {
return (HelloWorldMainSubAppView) super.getView();
}
/**
* When a button gets clicked in the {@link HelloWorldMainSubAppView} it will call back to this method.
* The {@link LocationController#goTo(info.magnolia.ui.framework.location.Location)} will cause the app framework to handle the location change event.
*/
@Override
public void greetUser(String user) {
locationController.goTo(new DefaultLocation(DefaultLocation.LOCATION_TYPE_APP, getAppContext().getName(), "greeter", user));
}
}
A simple interface providing methods to add buttons in the view.
public interface HelloWorldMainSubAppView extends View {
void setListener(Listener listener);
void addUser(String user);
public interface Listener {
void greetUser(String user);
}
}
The view consists of a VerticalLayout and a Label Say Hello!
.
public class HelloWorldMainSubAppViewImpl implements HelloWorldMainSubAppView {
private VerticalLayout layout = new VerticalLayout();
private Listener listener;
public HelloWorldMainSubAppViewImpl() {
layout.setMargin(true);
layout.setSpacing(true);
layout.addComponent(new Label("Say Hello!"));
}
@Override
public void setListener(Listener listener) {
this.listener = listener;
}
@Override
public Component asVaadinComponent() {
return layout;
}
@Override
public void addUser(final String user) {
Button button = new Button("Say hello to " + user + "!");
button.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(Button.ClickEvent event) {
listener.greetUser(user);
}
});
layout.addComponent(button);
}
}
Greeter subApp
When the user clicks one of the buttons, a new location is requested.
The AppController
will take care of redirecting it to the hello world
app and launching the greeter
subApp.
A simple interface providing a method to set the name.
public interface HelloWorldGreeterSubAppView extends View {
void setGreeting(String name);
}
The view consists of a VerticalLayout and a Label
"Hello " + name + "!"
.
public class HelloWorldGreeterSubAppViewImpl implements HelloWorldGreeterSubAppView {
VerticalLayout layout = new VerticalLayout();
public HelloWorldGreeterSubAppViewImpl() {
layout.setMargin(true);
layout.setSpacing(true);
}
@Override
public Component asVaadinComponent() {
return layout;
}
@Override
public void setGreeting(String name) {
layout.addComponent(new Label("Hello " + name + "!"));
}
}
The HelloWorldGreeterSubApp
will override the default caption of the
tab by the name passed as a parameter and set it on the view.
public class HelloWorldGreeterSubApp extends BaseSubApp {
private String name;
@Inject
protected HelloWorldGreeterSubApp(final SubAppContext subAppContext, final HelloWorldGreeterSubAppView view) {
super(subAppContext, view);
}
/**
* Extracts the name to greet from the {@link Location}s parameter and passes it to the {@link HelloWorldGreeterSubAppView}.
*/
@Override
protected void onSubAppStart() {
this.name = getCurrentLocation().getParameter();
getView().setGreeting(name);
}
@Override
public HelloWorldGreeterSubAppView getView() {
return (HelloWorldGreeterSubAppView)super.getView();
}
/**
* Used to set the label on the tab.
*/
@Override
public String getCaption() {
return name;
}
/**
* In case there is a subApp instance running, here the decision is made whether this instance will handle the new {@link Location}.
*/
@Override
public boolean supportsLocation(Location location) {
String newUser = location.getParameter();
return getCurrentLocation().getParameter().equals(newUser);
}
}