-
Notifications
You must be signed in to change notification settings - Fork 0
Description in Russian (описание на русском)
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)
.