您需要在一个开发环境下编写应用,可以使用与其他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
在模块描述符的XML文件里定义的组件
<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>

项目结构

  1. 使用Maven原型来获取一个做好的Magnolia模块结构,其中包含一个最高级别的项目和一个配置为Maven子模块的Web应用程序。
  2. 在IDE里建立您的应用项目结构,如下例所示。将应用类和子应用类放在同一个文件夹里。
  3. 应用配置应用启动器配置导出到XML,并把它们放在resources文件夹。模块描述符文件放在resources/META-INF.magnolia里。

您的IDE里应用项目结构如下:

实现应用

  1. 创建一个实现App界面的类,或扩展BaseApp类。第二个选项更为方便。
  2. 在您的应用类里,实现任何您需要的定制方法和功能。
  3. 注入AppContext,并将AppView添加为依赖关系。

注意:以上步骤仅仅当您想要通过定制功能来扩展应用时才需要。如非如此,可以在配置中采用info.magnolia.ui.framework.app.BaseApp作为应用类。

实现一个应用类并在AppContext注入依赖
public class HelloWorldApp extends BaseApp {
    @Inject
    public HelloWorldApp(AppContext appContext, AppView view) {
        super(appContext, view);
    }
	...
}

启动应用 

  1. 要启动应用,通常您会在应用启动器中找到您定义的组,然后点击图标打开应用。您也可以通过编程来实现这一动作,只需要调用LocationController.goTo(...)并将位置作为参数,来触发LocationChange事件。
  2. AppController现在就会通过配置中定义的app namesubAppId,来打开正确的应用和子应用。
  3. 以下代码可以与应用的启动周期和位置处理建立联系:
置换应用的方法
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
    }
}

模块依赖性

您需要在模块描述符里声明依赖性。您可以定义运行或安装时的依赖性,但不能建造依赖性。如果您在另一个模块上定义依赖性,那么这个模块就比您的模块优先安装启动。为开发一个基本的应用,在您的模块描述符里添加以下这段代码:

在AdminCentral里添加从属
<dependencies>
    <dependency>
        <name>ui-admincentral</name>
        <version>5.0/*</version>
    </dependency>
</dependencies>

Hello World示例

以下的按步骤入门指南实现一个基本的hello world应用。这个应用启动之后会显示一个包含两个按钮的main子应用,点击按钮会打开一个新的greeter子应用来向用户问好。

Main子应用

HelloWorldMainSubApp负责业务逻辑,包括在视图里添加按钮,并通过实现Listener接口监听动作。

SubAppContextView之外,我们也可以注入LocationController,它将会用来打开greeter子应用。

创建一个main子应用
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));
    }
}

以下为一个简单的接口,提供在视图中添加按钮的方法。

View界面和监听视图中动作的Listener
public interface HelloWorldMainSubAppView extends View {
   
	void setListener(Listener listener);
	void addUser(String user);

    public interface Listener {
        void greetUser(String user);
    }
}

视图中包含一个垂直布局(VerticalLayout)和一个“Say Hello!”标签。

为main子应用实现视图
 
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: 显示子应用、视图接口和视图实现如何互动的图表。

 以下为一个简单的界面,提供一个设置名称的方法。

简单的View界面
public interface HelloWorldGreeterSubAppView extends View {
    void setGreeting(String name);
}

该视图由一个垂直布局(VerticalLayout)和一个“Hello+<名称>+!”标签组成。

Implementing a View for the main subapp
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将会用以参数形式传递的名称,来改写标签卡的默认标题,并在视图上设置标题。

创建一个main子应用
 
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);
    }
}
  • No labels