Skip to content

Description in Russian (описание на русском)

David E. Veliev edited this page Dec 17, 2016 · 24 revisions

Общие сведения

SubNavigator - это server-side аддон для Vaadin 7, расширяющий возможности стандартного объекта Navigator и позволяющий легче организовать иерархическую многоуровневую структуру vaadin-приложения с поддержкой закладок браузера, историей, навигацией Вперёд/Назад, и т.д.

Стандартный Navigator позволяет регистрировать объекты View на определенный URI Fragment и при обращении пользователя к необходимому URL-адресу вызывать enter()-метод у соответствующего View. Всё хорошо, пока не появляется необходимость организовать вложенные View. Navigator позволяет передать в View.enter() какие-либо параметры, т.е. можно несложно организовать двойную иерархию, скажем /main/view1 и /main/view2. Для большей вложенности потребуются дополнительные действия.

SubNavigator позволяет явно указать иерархию объектов и при переходе пользователя с одного адреса (URI Fragment'а, если точнее) на другой уведомлять соответствующие объекты о необходимости очистить/обновить данные в той очерёдности, в какой они находятся в иерархии.

Описание

Два основных интерфейса в SubNavigator - это ISubView и ISubContainer.

public interface ISubView extends Component {
	String getRelativePath();
	void clean();
	void build();
}
public interface ISubContainer extends ISubView {
	ISubView getSelectedView();
	void setSelectedView(ISubView view);
	void deselectView(ISubView view);
}

Как видно, ISubContainer является контейнером и в общем случае может содержать в себе либо другие ISubContainer, либо ISubView. Рассмотрим пример, когда по адресу #!/path1/path2/path3 нужно отобразить какие-то данные. Здесь path1 и path2 - будут наследниками ISubContainer, path3 может быть как ISubView, так и ISubContainer. Методом getRelativePath() эти объекты определяют свой относительный путь path1, path2, path3. Корневой элемент path1 всегда один и содержит в себе дерево остальных элементов. Для примера, path1 - это может быть наследник Panel с элементом TabSheet, path2 - Tab, path3 - VerticalLayout. Пример реализации элемента по адресу path3:

public class SubView3 extends VerticalLayout implements ISubView {

	@Override
	public String getRelativePath() {
		return "path3";
	}

	@Override
	public void clean() {
		removeAllComponents();
	}

	@Override
	public void build() {
		addComponent(new Label("Hello, world!"));
	}

}

Пример реализации контейнера по относительному пути path2:

public class SimpleSubContainer2 extends VerticalLayout implements ISubContainer {

	@Override
	public String getRelativePath() {
		return "path2";
	}

	@Override
	public void clean() {
		removeAllComponents();
	}

	@Override
	public void build() {
		((MyUI) getUI()).getSubNavigator().addView(this, new SubView3());
	}

	@Override
	public ISubView getSelectedView() {
		return (ISubView) getComponent(0);
	}

	@Override
	public void setSelectedView(ISubView view) {
		addComponent(view);
	}

	@Override
	public void deselectView(ISubView view) {
		removeComponent(view);
	}

}

Регистрация объектов. Для задания дерева объектов в приложении используется метод addView(ISubContainer container, ISubView view) интерфейса ISubNavigator:

// registering views
ISubNavigator subNavigator = new SubNavigator(ui, path1View); // path1View - root view
subNavigator.addView(path1View, path2View); // path2View contained in path1View
subNavigator.addView(path2View, path3View);
subNavigator.addView(path1View, path4View);
subNavigator.addView(path4View, path5View);

Ситуация 1 - пользователь в первый раз перешёл по ссылке в приложение. Для каждого объекта от корневого до последнего (path1, path2, path3) SubNavigator поочерёдно вызовет методы build() (для ISubView) и setSelectedView(ISubView view) (для ISubContainer), начиная с корневого:

// navigating to #!/path1/path2/path3
path1View.build()
path1View.setSelectedView(path2View)
path2View.build()
path2View.setSelectedView(path3View)
path3View.build()

Ситуация 2 - пользователь с адреса #!/path1/path2/path3 переходит на адрес #!/path1/path4/path5. Для объектов по относительным адресам path3 и path2 SubNavigator вызовет методы clean() и deselectView(ISubView view), затем для path4 и path5 - build() и setSelectedView(ISubView view):

// navigating from #!/path1/path2/path3 to #!/path1/path4/path5
path3View.clean()
path2View.deselectView(path3View)
path2View.clean()
path1View.deselectView(path2View)
path1View.setSelectedView(path4View)
path4View.build()
path4View.setSelectedView(path5View)
path5View.build()

Динамические контейнеры

Кроме статической регистрации методом ISubNavigator.addView можно использовать ISubDynamicContainer:

public interface ISubDynamicContainer extends ISubContainer {
	ISubView createView(String viewPathAndParameters);
}

Это контейнер, который может создавать вложенный элемент ISubView без его специальной регистрации. Если в предыдущем примере по пути #!/path1/path2/path3 path3 это ISubDynamicContainer, то после перехода по ссылке #!/path1/path2/path3/123 у объекта path3 будет вызван метод createView("123"). Динамические контейнеры могут содержать в себе другие динамические контейнеры.

Пример реализации динамического контейнера, который может создавать вложенные Window:

public class DynamicContainer1 extends VerticalLayout implements ISubDynamicContainer, ISubTitled, CloseListener {

	protected ISubNavigator subNavigator;
	SimpleView selectedView;
	DynamicContainer1 thisView = this;

	Label info;
	TextField id;
	Button button;

	@Override
	public ISubView createView(String viewPathAndParameters) {
		if (!viewPathAndParameters.matches("\\d+"))
			return null;
		SimpleView view = new SimpleView(viewPathAndParameters);
		return view;
	}

	@Override
	public ISubView getSelectedView() {
		return selectedView;
	}

	@Override
	public void setSelectedView(ISubView view) {
		selectedView = (SimpleView) view;
		Window window = new Window();
		window.setModal(true);
		window.setWidth(300, Unit.PIXELS);
		window.setHeight(500, Unit.PIXELS);
		window.setContent(selectedView);
		window.setCaption("Dynamically created window");
		window.addCloseListener(this);
		getUI().addWindow(window);
	}

	@Override
	public void deselectView(ISubView view) {
		Window window = (Window) selectedView.getParent();
		window.removeCloseListener(this);
		window.close();
		selectedView = null;
	}

	@Override
	public void windowClose(CloseEvent e) {
		selectedView = null;
		subNavigator.notifySelectedChangeDirected(this);
	}

	@Override
	public void clean() {
		removeAllComponents();
	}

	@Override
	public String getRelativePath() {
		return "dynamic-container";
	}

	@Override
	public void build() {
		subNavigator = ((SubNavigatorUI) getUI()).getSubNavigator();

		setSizeUndefined();
		setSpacing(true);
		setMargin(true);

		info = new Label("This is dynamic container");
		addComponent(info);

		id = new TextField("Enter object id");
		id.setValue("123");
		id.setImmediate(true);
		addComponent(id);

		button = new Button("Click to open object");
		button.addClickListener(new ClickListener() {

			@Override
			public void buttonClick(ClickEvent event) {
				String sId = id.getValue().replaceAll("\\s+", "");
				subNavigator.navigateTo(thisView, sId);
			}
		});
		addComponent(button);
	}

	@Override
	public String getRelativeTitle() {
		return "Dynamic Container";
	}

}

Перехват ошибок

Для перехвата ошибок можно унаследоваться от ISubErrorContainer:

public interface ISubErrorContainer extends ISubContainer {
	ISubView createErrorView(String viewPath, String errorPath);
	ISubView createErrorView(String viewPath, Throwable t);
}

Метод createErrorView(String viewPath, String errorPath) будет вызван, если SubNavigator не смог найти по введённому пользователем адресу никаких данных. Если например пользователь перешел по несуществующему пути #!/path1/path9/path12 и path1 реализует ISubErrorContainer, у него будет вызван метод createErrorView("error", "path1/path9/path12"). Здесь "error" это относительный путь для вывода созданного объекта ISubView, т.е. он будет находится по адресу #!/path1/error.

Метод createErrorView(String viewPath, Throwable t) будет вызван, если при переходе пользователя была выброшена ошибка при построении дерева.

Пример отображения пользователю информации в случае ошибок:

	@Override
	public ISubView createErrorView(String viewPath, String errorPath) {
		return new ErrorView(viewPath, errorPath);
	}

	@Override
	public ISubView createErrorView(String viewPath, Throwable t) {
		return new ErrorView(viewPath, t);
	}

Заголовок приложения

Для вывода иерархического заголовка страницы (например "Page1 - Inner Page2 - Inner Page3") можно унаследоваться от интерфейса ISubTitled:

public interface ISubTitled {
	String getRelativeTitle();
}

Для того чтобы включить эту возможность, используйте метод ISubNavigator.setEnabledSubTitles(true).