您需要在一个开发环境下编写应用,可以使用与其他Magnolia CMS开发任务相同的环境。开发里有怎样建立环境的指导。
组件
注意-这里的组件是个开发术语,指Java对象,而不是创建模板的组件。
组件提供服务,如保留模板的登记等。组件通过一个界面或API来调用其他组件的功能。通常组件依赖于大量的其他组件,并得益于这种依赖性注入,因为Magnolia CMS会将组件连接起来以满足它们的依赖性关系。组件要么是单件(意味着它们在服务器的生命时间里会存在),要么绑定在较短的持续时间,例如一个HTTP请求的持续时间,或者用户对话等。这个持续的时间叫做范围。
应用和组件
每个应用都有自己的组件提供者,使用app-<app name>组里的组件。每个子应用也有一个组件提供者,使用为它描述的组件。后者由前者衍生而来:
在模块描述符里定义应用组件
组件在模块描述符中定义。一个最小的模块描述符可以只声明模块名和版本,如:
<?xml version="1.0" encoding="UTF-8"?> <!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>
将应用或子应用定义为组件
在模块描述符文件里,为每个组件添加一小节代码。按照以下惯例为组件命名:
app-<app name>
app-<app name>-<subapp name>
其中<app name>是指在AdminCentral的应用配置里设置的名字。例如:
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>
项目结构
- 使用Maven原型来获取一个做好的Magnolia模块结构,其中包含一个最高级别的项目和一个配置为Maven子模块的Web应用程序。
- 在IDE里建立您的应用项目结构,如下例所示。将应用类和子应用类放在同一个文件夹里。
- 将应用配置和应用启动器配置导出到XML,并把它们放在
resources文件夹。模块描述符文件放在resources/META-INF.magnolia里。
您的IDE里应用项目结构如下:
实现应用
- 创建一个实现
App界面的类,或扩展BaseApp类。第二个选项更为方便。 - 在您的应用类里,实现任何您需要的定制方法和功能。
注入
AppContext,并将AppView添加为依赖关系。
注意:以上步骤仅仅当您想要通过定制功能来扩展应用时才需要。如非如此,可以在配置中采用info.magnolia.ui.framework.app.BaseApp作为应用类。
public class HelloWorldApp extends BaseApp {
@Inject
public HelloWorldApp(AppContext appContext, AppView view) {
super(appContext, view);
}
...
}
启动应用
- 要启动应用,通常您会在应用启动器中找到您定义的组,然后点击图标打开应用。您也可以通过编程来实现这一动作,只需要调用
LocationController.goTo(...)并将位置作为参数,来触发LocationChange事件。 AppController现在就会通过配置中定义的app name和subAppId,来打开正确的应用和子应用。- 以下代码可以与应用的启动周期和位置处理建立联系:
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
}
}
停止应用
当应用停止时,会收到stop方法的回调来做清理工作。清理过程可能会包含关闭文件,关闭网络连接等等。
@Override
public void stop() {
// code to extend default behavior
}
处理位置变化
当您的应用被请求一个位置片段时,您可能需要处理位置变化。在您的应用类里,改写locationChanged方法。
@Override
public void locationChanged(Location location) {
super.locationChanged(location);
String parameter = location.getParameter();
doFoo(parameter);
}
记住,如果子应用被请求一个运行的子应用里已打开的位置,Magnolia Shell会将那个子应用带回焦点,而不是启动一个新的子应用。
子应用
子应用是在自己的标签卡里运行的应用。比如,页面应用里,main子应用总是在第一个标签卡里打开,每个被编辑的页面各打开一个detail子应用。main子应用有“页面”标签,而detail子应用则根据页面名称添加标签,例如/demo-project。
当子应用启动时,它必须要返回一个视图。这个视图在标签卡里描绘出一个用户界面,好让用户可以与之互动。这个视图会占用整个标签卡,并添加一个标题。
实现子应用
要实现子应用接口,您可以创建一个类,或是扩展AbstractSubApp类。后者比较方便。在您的子应用类里,实现任何您需要的定制方法或功能。注入SubAppContext,并注入与子应用相关的视图作为依赖关系。
public class MyMainSubApp extends BaseSubApp {
@Inject
protected MyMainSubApp(final SubAppContext subAppContext, final View view) {
super(subAppContext, view);
}
}
启动子应用
启动应用和改变应用位置默认由在location里定义的真正的子应用实现。如果与引用的subAppId对应的子应用已经在运行,那么它会被询问是否真正支持这个location。
public class BaseSubApp implements SubApp {
...
@Override
public boolean supportsLocation(Location location) {
return true;
}
}
在本例中,所有引用子应用subAppId的位置都会由这个子应用实例处理。通过重写这个方法并使其返回false值,您将会为每个AppController收到的位置变化打开一个新的标签卡,这些位置变化的目标为此subAppId。要根据传递给子应用的参数,打开一个新的标签卡,代码如下:
public class MySubApp extends BaseSubApp {
...
@Override
public boolean supportsLocation(Location location) {
DefaultLocation newLocation = (DefaultLocation) location;
String currentParameter = getCurrentLocation().getParameter();
return currentParameter.equals(newLocation.getParameter());
}
}
与应用的启动周期和位置处理建立联系:
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
}
}
模块依赖性
您需要在模块描述符里声明依赖性。您可以定义运行或安装时的依赖性,但不能建造依赖性。如果您在另一个模块上定义依赖性,那么这个模块就比您的模块优先安装启动。为开发一个基本的应用,在您的模块描述符里添加以下这段代码:
<dependencies>
<dependency>
<name>ui-admincentral</name>
<version>5.0/*</version>
</dependency>
</dependencies>
Hello World示例
以下的按步骤入门指南实现一个基本的hello world应用。这个应用启动之后会显示一个包含两个按钮的main子应用,点击按钮会打开一个新的greeter子应用来向用户问好。
Main子应用
HelloWorldMainSubApp负责业务逻辑,包括在视图里添加按钮,并通过实现Listener接口监听动作。
除SubAppContext和View之外,我们也可以注入LocationController,它将会用来打开greeter子应用。
public class HelloWorldMainSubApp extends BaseSubApp implements HelloWorldMainSubAppView.Listener {
private LocationController locationController;
@Inject
protected 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));
}
}
以下为一个简单的接口,提供在视图中添加按钮的方法。
public interface HelloWorldMainSubAppView extends View {
void setListener(Listener listener);
void addUser(String user);
public interface Listener {
void greetUser(String user);
}
}
视图中包含一个垂直布局(VerticalLayout)和一个“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子应用
当用户点击一个按钮时,会请求一个新的位置。AppController负责重定向至hello world应用,并启动greeter子应用。
TODO: 显示子应用、视图接口和视图实现如何互动的图表。
以下为一个简单的界面,提供一个设置名称的方法。
public interface HelloWorldGreeterSubAppView extends View {
void setGreeting(String name);
}
该视图由一个垂直布局(VerticalLayout)和一个“Hello+<名称>+!”标签组成。
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 + "!"));
}
}
HelloWorldGreeterSubApp将会用以参数形式传递的名称,来改写标签卡的默认标题,并在视图上设置标题。
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);
}
}