Preventing URI Navigation in vaadin - java

I am using the java framework Vaadin to create a web application. There different user levels for this application, and when the user logs in I change the menu depending on their permission level. The problem I am having is preventing a user from visiting a page they are not suppose to by typing in the URI. For example if a user logs in to the application and only has permission to view page one he can still access page two by typing the URI to page two in the browsers search bar.
I was looking into preventing URI navigation, but was unsuccessful in finding out how this is done in Vaadin. So my question is how to prevent URI navigation in Vaadin? If you have a different method of preventing users from accessing pages they are not suppose to please feel free to post that as well.
So far i've come up short and the one post that i've seen about this on stack overflow does not explain it particularly well.
So far this is what my NavigatorUI class looks like:
#SpringUI
public class NavigatorUI extends UI {
public Navigator navigator;
public static final String LOGIN = "";
public static final String MAINPAGE = "main";
public static final String EMPLOYEEPAGE = "employeepage";
public static final String REGISTERPAGE = "registerpage";
public static final String ADDEMPLOYEEPAGE = "addemployeepage";
public static final String ADDEMPLOYERPAGE = "addemployerpage";
private MainSystem main = new MainSystem();
#Override
protected void init(VaadinRequest request) {
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
layout.setSpacing(true);
setContent(layout);
Navigator.ComponentContainerViewDisplay viewDisplay = new Navigator.ComponentContainerViewDisplay(layout);
navigator = new Navigator(UI.getCurrent(), viewDisplay);
navigator.addView(LOGIN, new LoginView());
navigator.addView(MAINPAGE, new MainView());
navigator.addView(EMPLOYEEPAGE, new EmployeeView());
navigator.addView(REGISTERPAGE, new RegisterView());
navigator.addView(ADDEMPLOYEEPAGE, new AddEmployeeView());
navigator.addView(ADDEMPLOYERPAGE, new AddEmployerView());
navigator.addViewChangeListener(new ViewChangeListener() {
#Override
public boolean beforeViewChange(ViewChangeEvent event) {
View newView = event.getNewView();
String newViewString= newView.toString();
newViewString = newViewString.substring(0,newViewString.length()-9);
View loginView = new LoginView();
String loginViewString = loginView.toString();
loginViewString = loginViewString.substring(0,loginViewString.length()-9);
boolean result = true;
if (newViewString.equals(loginViewString)){
return result;
}
else if (VaadinSession.getCurrent().getAttribute("role").toString().equals("Admin")||VaadinSession.getCurrent().getAttribute("role").toString().equals("Employee")){
return result;
}
else {
result = false;
}
return result;
}
#Override
public void afterViewChange(ViewChangeEvent event) {
//NO-OP
}
});
}
}

You add a ViewchangeListener to your Navigator instance, that can prevent navigation in the beforeViewChange.
If any listener returns false, the view change is not allowed and afterViewChange() methods are not called.
So you implement that Interface with a check of the current users authorizations against the newView of the event. And in case you want to block that, you return false there.

Related

Add behavior to Wicket Tab

I have a wicket application on a page we have various forms for the same model split into separate tabs. What I need to do is whenever a tab is clicked check to see if a js variable tabDirty is set to true or false. If it is true I would launch a confirm prompt if okay then reset that form and move to the clicked tab. If cancel stay on that tab with keeping current changes.
I have this js for the warning nothing fancy
function warnOnChange(){
if(tabDirty){
decision = confirm('Leave?');
if(decision){
resetTab(); //sets tabDirty back to false
} else {
return false;
}
}
}
I have a super simple wicket behavior
public class WarnChangePromptOnClickBehavior extends Behavior {
#Override
public void bind(Component component) {
component.add(JQBehaviors.mouseClick(EditMerchant.WARN_ON_CHANGE));
}
}
and that behavior is added to the AjaxFallBackLink
AjaxTabbedPanel<CustomAjaxTab> tabbedPanel = new AjaxTabbedPanel<CustomAjaxTab>("tabbedPanel", tabList, new Model<>(0)) {
private static final long serialVersionUID = 1L;
#Override
protected WebMarkupContainer newLink(final String linkId, final int index) {
AjaxFallbackLink<Void> link = new AjaxFallbackLink<Void>(linkId) {
private static final long serialVersionUID = 1L;
#Override
public void onClick(final AjaxRequestTarget target) {
TabbedPanel<CustomAjaxTab> selectedTab = setSelectedTab(index);
CustomAjaxTab tab = tabList.get(index);
if (target != null) {
tab.getPanel(linkId);
target.add(selectedTab);
}
onAjaxUpdate(target);
}
};
link.add(new WarnChangePromptOnClickBehavior());
return link;
}
};
Current behavior with this is that if there is no change the tabs switch no prompt. If there is a change then I get the prompt. If okay tabDirty is reset and go to the next page clearing changes. Issue is that if I click cancel I still navigate to the next tab and lose changes. I know there is something in onClick I need to change but it is just not registering with me.
It is not that easy to intercept the JS event loop, especially when using Ajax requests.
Here is an approach that may do the job:
In warnOnChange() if dirty then call event.preventDefault() and event.stopImmediatePropagation(). This will tell the browser to not follow the link / make an Ajax call. Then show the confirmation dialog as you do now.
If the user presses Cancel then there is nothing more to do
If the use confirms then set dirty to false and do jQuery(event.target).triggerHandler(event.type), i.e. execute the same event (click) on the link. This time it won't be dirty and it will proceed with the Ajax call.
Not sure if this is the appropriate way to do this but I solved my issue like this:
Same old js just slightly modified to return what the user chose:
function warnOnChange(){
decision = true;
if(tabDirty){
decision = confirm('Leave?');
if(decision){
resetTab();
}
}
return decision;
}
Dumped the whole behavior code although I still think it could be used just not sure at the moment...
So to make this all work on the link I override the updateAjaxAttributesof the link with a precondition:
AjaxTabbedPanel<CustomAjaxTab> tabbedPanel = new AjaxTabbedPanel<CustomAjaxTab>("tabbedPanel", tabList, new Model<>(0)) {
private static final long serialVersionUID = 1L;
#Override
protected WebMarkupContainer newLink(final String linkId, final int index) {
AjaxFallbackLink<Void> link = new AjaxFallbackLink<Void>(linkId) {
private static final long serialVersionUID = 1L;
#Override
protected void updateAjaxAttributes( AjaxRequestAttributes attributes ) {
super.updateAjaxAttributes( attributes );
AjaxCallListener ajaxCallListener = new AjaxCallListener();
//very important to use the "return" if not then nothing happens with the response
ajaxCallListener.onPrecondition("return " + WARN_ON_CHANGE);
attributes.getAjaxCallListeners().add( ajaxCallListener );
}
#Override
public void onClick(final AjaxRequestTarget target) {
TabbedPanel<CustomAjaxTab> selectedTab = setSelectedTab(index);
CustomAjaxTab tab = tabList.get(index);
if (target != null) {
tab.getPanel(linkId);
target.add(selectedTab);
}
onAjaxUpdate(target);
}
};
link.add(new WarnChangePromptOnClickBehavior());
return link;
}
};

Vaadin 8 creating a tree with Enum class

I am still new to Vaadin so, please bear with it.
We are currently migrating from Vaadin framework 8.0 to 8.3.2. One of the reasons of doing is that there's a requirement of using tree for the menu. Since 8.0 doesn't have tree, the workaround for generating a menu is by instantiating an inner Button class with the help of an Enum class in a loop (for user permission control):
public final class ValoMenuItemButton extends Button {
private static final String STYLE_SELECTED = "selected";
private final DashboardViewType view;
public ValoMenuItemButton(final DashboardViewType view) {
this.view = view;
setPrimaryStyleName("valo-menu-item");
setIcon(view.getIcon());
setCaption(view.getViewName().substring(0, 1).toUpperCase()
+ view.getViewName().substring(1));
DashboardEventBus.register(this);
addClickListener(new ClickListener() {
#Override
public void buttonClick(final ClickEvent event) {
UI.getCurrent().getNavigator()
.navigateTo(view.getViewName());
}
});
}
#Subscribe
public void postViewChange(final PostViewChangeEvent event) {
removeStyleName(STYLE_SELECTED);
if (event.getView() == view) {
addStyleName(STYLE_SELECTED);
}
}
}
The enum class structure is built in this manner:
AUDIT("Receipt Validation", RcptValidation.class, FontAwesome.BAR_CHART_O, false),
AUDIT1("Matching - Receipt not in SYCARDA", RcptNotInSycarda.class, FontAwesome.BAR_CHART_O, false),
AUDIT2("Matching - Receipt not in POS", RcptNotInPos.class, FontAwesome.BAR_CHART_O, false),
AUDIT3("Missing Sequence", MissSequence.class, FontAwesome.BAR_CHART_O, false),
AUDIT4("*Debug Purposes", LineAmtVsTotal.class, FontAwesome.BAR_CHART_O, false);
private DashboardViewType(final String viewName,
final Class<? extends View> viewClass, final Resource icon,
final boolean stateful) {
this.viewName = viewName;
this.viewClass = viewClass;
this.icon = icon;
this.stateful = stateful;
}
So far, I've not found any examples that are written around the v8 framework, while most of the sample code that I've seen are based on v7 framework.
I've attempted to write something like this, but the tree sub menus do not come out as it is (I've left out the expand and collapse event as that can be handled later).
My attempted code on the tree is this:
TreeData <String> treeData = new TreeData();
treeData.addRootItems("Dashboard","Sales","Sales Pattern","Top SKUs","Audit");
// The loop starts here (for DashboardViewType view: DashboardViewType.values)
if(enabled){
if(StringUtils.startsWith(view.getViewName(), "SALES")){
if (StringUtils.contains(view.getViewName(),"SALES_PATTERN")){
treeData.addItem( "Sales Pattern", view.getViewName());
}else{ treeData.addItem( "Sales", view.getViewName());
}
}else if (StringUtils.startsWith(view.getViewName(), "TOP_SKUS")){
treeData.addItem( "Top SKUs", view.getViewName());
}else if (StringUtils.startsWith(view.getViewName(), "AUDIT")){
treeData.addItem( "Audit", view.getViewName());
}else if (StringUtils.startsWith(view.getViewName(), "DASHBOARD")){
treeData.addItem( "Dashboard", view.getViewName());
}
DashboardEventBus.register(view);
}
// loop ends here
Tree<String> tree = new Tree<>("Sycarda Dashboard");
tree.setDataProvider(new TreeDataProvider<>(treeData));
tree.setItemIconGenerator(item -> { return FontAwesome.BAR_CHART_O; });
tree.expand("Sales","Sales Pattern","Top SKUs","Audit");
tree.addSelectionListener(e -> new Button.ClickListener() {
#Override public void buttonClick(Button.ClickEvent event) {
DashboardEventBus.register(event);
UI.getCurrent().getNavigator().navigateTo(event.getClass().getName());
}
});
This was posted originally at the Vaadin forum, but since there were no answers to that, I am putting it here. I'd appreciate if there's any input or another approach for this problem.
Thanks in advance.
In Vaadin 8 you can simply define the "get children" method when adding the data. In your case the enum class should provide some method like "getSubItems", which you could then set as the value provider. The following example shows it in a similar way, where "rootItems" is simply the same as your top level enum instances and MenuItem the same as your enumeration.
static {
rootItems = Arrays.asList(...);
}
#PostConstruct
private void init() {
Tree<MenuItem> tree = new Tree<>();
tree.setItems(rootItems, MenuItem::getSubItems);
}
private class MenuItem {
private String name;
private Resource icon;
private Collection<MenuItem> subItems;
public Collection<MenuItem> getSubItems() {
return subItems;
}
// ... other getter and constructor omitted;
}
Someone has shown an example and it is similar to what Stefan mentioned. In context with my requirement, the steps involved include:
Create a wrapper class that includes:
private DashboardViewType view;
private Resource icon;
private boolean stateful;
private Class<? extends View> viewClass;
private String viewName;
//Create the get / set methods for those attributes above
//Constructor for the wrapper class is below.
public TreeMenuItem(DashboardViewType view){
this.view = view;
}
For the Enum class additional main menu items are added. Default main class can be used since you can't put a null.
public enum DashboardViewType {
SALES("Sales",DashboardView.class,FontAwesome.HOME,false),
SALES_PATTERN("Sales Pattern",DashboardView.class,FontAwesome.HOME,false),
TOP_SKUs("Top SKUs",DashboardView.class,FontAwesome.HOME,false),
AUDIT("Audit",DashboardView.class,FontAwesome.HOME,false)
}
The tree is built in this manner:
private Component buildTree(){
Tree<TreeMenuItem> tree = new Tree<>();
TreeData<TreeMenuItem> treeData = new TreeData<>();
//This is for items that have no child.
TreeMenuItem dashboardItem = new TreeMenuItem(DashboardViewType.DASHBOARD);
dashboardItem.setIcon(VaadinIcons.HOME_O);
dashboardItem.setStateful(DashboardViewType.DASHBOARD.isStateful());
dashboardItem.setViewName(DashboardViewType.DASHBOARD.getViewName());
treeData.addItem(null, dashboardItem);
for (DashboardViewType type : DashboardViewType.values()) {
TreeMenuItem menuItem = new TreeMenuItem(type);
menuItem.setIcon(VaadinIcons.HOME_O);
menuItem.setViewName(type.getViewName());
menuItem.setStateful(false);
treeData.addItem(null, menuItem);
getSubMenuItems(type).forEach(subView -> {
TreeMenuItem subItem = new TreeMenuItem(subView);
subItem.setViewName(subView.getViewName().substring(0, 1).toUpperCase()
+ subView.getViewName().substring(1));
subItem.setIcon(subView.getIcon());
subItem.setStateful(subView.isStateful());
subItem.setView(subView);
subItem.setViewClass(subView.getViewClass());
treeData.addItem(menuItem, subItem);
});
}
}
tree.setDataProvider(new TreeDataProvider<>(treeData));
tree.setItemIconGenerator(TreeMenuItem::getIcon);
tree.setItemCaptionGenerator(TreeMenuItem::getViewName);
tree.addItemClickListener((Tree.ItemClick<TreeMenuItem> event) -> {
DashboardEventBus.register(event.getItem().getView()); UI.getCurrent().getNavigator().navigateTo(event.getItem().getViewName());
});
}
The logic to create subviews:
private List getSubMenuItems(DashboardViewType type) {
List<DashboardViewType> dashboardList;
switch(type){
case TOP_SKUs:
dashboardList = new LinkedList<>(Arrays.asList(DashboardViewType.TOP_SKUs1,
DashboardViewType.TOP_SKUs2,
DashboardViewType.TOP_SKUs3,
DashboardViewType.TOP_SKUs4));
filterByUserLevel(dashboardList,subACL4);
return dashboardList;
case AUDIT:
dashboardList = new LinkedList<>(Arrays.asList(DashboardViewType.AUDIT1,
DashboardViewType.AUDIT2,
DashboardViewType.AUDIT3,
DashboardViewType.AUDIT4,
DashboardViewType.AUDIT5));
filterByUserLevel(dashboardList,subACL5);
return dashboardList;
case DASHBOARD:
break;
default:
break;
}
return Collections.emptyList();
}
Add additional cases if required so. After that, the function controls remove the elements that are not part of the user level:
private List<DashboardType> filterByUserLevel(List<DashboardType>list, String u){
if(list.size() == subACL.length()){
for(int i=0; i<list.size(); i++){
if(StringUtils.substring(subACL, i, i+1).equalsIgnoreCase("0")){
list.remove(i);
}
}
Collections.sort(list);
return list;
//this removes unwanted sub-menu items according current user level.
}
}

Refactoring a class implementing an interface by creating subclasses in GWT /java

I am implementing the frontend of an application in GWT (see attached picture) and I have view class which is getting bigger as more widgets are added to the frontend.
As stated in GWT tutorial, the view class must implement the Display interface of the presenter class. y problem is I have a lot a methods in that interface and as I implement them in the view class, it becomes too big. That's why I would like to refactor the code to reduce the size of the view class by implementing those methods in others
classes and reference them where needed in the view class;for instand by grouping them per group box (one class per group box).
Below is a sample code: Note that in the real application we have more widgets per group box.
The problem I am facing will be well explained as you read through the whole posting because I will be adding more details.
code to be refactored:
ContactPrewsenter.java
public class ContactPresenter {
public interface Display
{
void methodA();
void methodB();
void methodC();
void methodD();
void methodE();
void methodF();
.
.
.
void methodM();
}
public ContactPresenter()
{
//Some stuff here
}
......
......
#Override
public void bind(){
//Some stuff here
}
}
ContactView.java:
public class ContactView implements ContactPresenter.Display
{
private final Listbox listBoxA;
private final Listbox listBoxB;
private final Listbox listBoxC;
private final Listbox listBoxD;
private final Listbox listBoxE;
private final Listbox listBoxF;
private final Listbox listBoxG;
private final Listbox listBoxH;
private final Listbox listBoxI;
private final Listbox listBoxJ;
private final Listbox listBoxK;
private final Listbox listBoxL;
private final Listbox listBoxM;
public ContactView()
{
listBoxA = new ListBox();
listBoxB = new ListBox();
VerticalPanel vPanel1= new VerticalPanel();
vPanel1.add(listBoxA);
vPanel1.add(listBoxB);
GrooupBox groupBox1 = new GroupBox();
groupBox1.add(vPanel1);
listBoxC = new ListBox();
listBoxD = new ListBox();
VerticalPanel vPanel2 = new VerticalPanel();
vPanel2.add(listBoxC);
vPanel2.add(listBoxD);
GrooupBox groupBox2 = new GroupBox();
groupBox2.add(vPanel2);
listBoxE = new ListBox();
listBoxF = new ListBox();
VerticalPanel vPanel3 = new VerticalPanel();
vPanel3.add(listBoxE);
vPanel3.add(listBoxF);
GrooupBox groupBox3 = new GroupBox();
groupBox3.add(vPanel3);
listBoxE = new ListBox();
listBoxF = new ListBox();
VerticalPanel vPanel4 = new VerticalPanel();
vPanel4.add(ListBoxE);
vPanel4.add(ListBoxF);
....
GrooupBox groupBox3 = new GroupBox();
groupBox3.add(vPanel4);
listBoxG = new ListBox();
listBoxH = new ListBox();
....
VerticalPanel vPanel = new VerticalPanel();
vPanel.add(ListBoxG);
vPanel.add(ListBoxH);
....
GrooupBox groupBox4 = new GroupBox();
groupBox4.add(vPanel);
......
//create Horizontal/vertical panels, docklayout panel as well, to position the group boxes on the gui
....
}
#Override
void methodA(){
//uses listBoxA
}
#Override
void methodB(){
//used listBoxB
}
#Override
void methodC(){
//uses listBoxC
}
#Override
void methodD(){
//uses listBoxD
}
#Override
void methodE(){
//uses listBoxE
}
#Override
void methodF(){
//uses listBoxF
}
#Override
void methodG(){
//uses listBoxG
}
#Override
void methodH(){
//uses listBoxH
}
.
.
.
#Override
void methodM(){
//uses listBoxM
}
}
I have tried as follows:
ContactPreseter.java
public class ContactPresenter
{
public interface Display extends groupBox1View.Display, groupBox2View.Display, groupBox3View.Display, groupBox4View.Display
{
}
}
preseter classes of each group box
public class groupBox1Presenter
{
public interface Display
{
void methodA();
void methodB();
}
}
public class groupBox2Presenter
{
public interface Display
{
void methodC();
void methodD();
}
}
public class groupBox3Presenter
{
public interface Display
{
void methodE();
void methodF();
}
}
public class groupBox4Presenter
{
public interface Display
{
void methodG();
void methodH();
}
}
ContactView.java
public abstract class ContactView implements ContactPresenter.Display
{
// adds group boxes to horizontal/vertical panels, and docklayout panel
}
Below are the view classes for each group box:
But here I eclipse forces me to implement all the methods of the interface ContactPresenter.Display in each of these classes whereas , I wanted it to be the way you see implemented here.
I was wondering if there were a way to play with access modifiers in order to achieve that ? If not, please I would you to help with ideas how to do it ?
public groupBox1View extends ContactView implements groupBox1Presenter
{
public groupBox1View()
{
}
#Override
void methodA(){
//uses listBoxA
}
#Override
void methodB(){
//used listBoxB
}
}
public groupBox2View extends ContactView implements groupBox2Presenter
{
public groupBox2View()
{
}
#Override
void methodC(){
//uses listBoxC
}
#Override
void methodD(){
//used listBoxD
}
}
public groupBox3View extends ContactView implements groupBox3Presenter
{
public groupBox3View()
{
}
#Override
void methodE(){
//uses listBoxE
}
#Override
void methodF(){
//used listBoxF
}
}
public groupBox4View extends ContactView implements groupBox4Presenter
{
public groupBox4View()
{
}
#Override
void methodG(){
//uses listBoxG
}
#Override
void methodH(){
//used listBoxH
}
}
You are right, your view is growing too big. You need to cut it into components which are handling their own concerns.
The editor framework will prove helpful but has it's own caveats.
In the end, you have one presenter, working with the whole thing, but only reading and writing one contact object.
You build your view from multiple components, each may have it's own presenter and is responsible for one part of your large contact object.
An example: Instead of running 10 listboxes of generic type, make that 10 semantically different components, responsible for selection of different types of data: AgeListBox, CityListBox, FooListBox, BarListBox.
This will seperate the data provisioning for the boxes out of your central presenter, and into the specific presenters for each listbox.
Start at the lowest level and combine editing views for each semantic unit and combine them to larger return objects:
NameEditor, AgeEditor, FooEditor, BarEditor are combined into an AddressEditor, which assembles with a CVEditor into something bigger until you finally arrive at the contact level.
I hope this makes sense to you.
UPdate: You asked for code, let's try some pseudocode:
Let's say you have a profile you want to edit. It contains of
the user's personal data
contains the user address
a bunch of email or mobile addresses
an image or connection to Gravatar
payment information
the list of tags the user is interested in
the list of channels he subscribed
Newsletter/marketing information
public class UserProfile {
PersonalData person;
List<NewsTopic> topicsOfInterest;
List<NewsChannel> subscriptions;
MarketingInfo marketingInfo;
// bean stuff, constr, equals etc.
}
public class PersonalData {
String name;
String firstName;
List<ContactDevice>phoneAndMailList;
ImageContainer userImage;
BankAccount paymentData;
}
You get the idea, I guess...
You can now write ONE view class, detailing all the information you see here, resulting in a monolitic monster view and the matching monster presenter. Or you follow the advice in the gwtproject and cut the view in small as possible pieces (components). That is, subviews and presenters that form a hierarchy, matching the one of your UserProfile class. This is basically what the editor framework is really good at.
In the editor fw, the views are called "Editors" (makes sense), and they get fed the data from top editor down to the smallest part by an EditorDriver class. GWT will generate most of the code for you, which is very cool, but also is not working so perfect, if you have optional parts in the profile.
If we would implement this ourselves, you will build a structure like the following (I avoid the "Editor" and replaced by "Dialog"):
public class UserProfileDialogView extends Component implements HasValue<UserProfile> {
// subviews
#UiField
PersonalDataDialog personDataDlg;
#UiField
UserTopicListDialog topicListDlg;
#UiField
SubscriptionListDialog subscriptionListDlg;
#UiField
MarketingInfoDialog marketingInfoDlg;
#Overwrite
public UserProfile getValue() {
// we do not need to copy back the data from the sub-dialogs, because we gave them the values from the profile itself.
// Beware, substructures cannot be null in such a case!
return userProfile;
}
#Ovewrite
public void setValue(UserProfile profile) {
this.userProfile = profile;
// option one: manually distribute the profile parts
personDataDlg.getPresenter().setValue(profile.getPerson());
topicListDlg.getPresenter().setValue(profile.getTopicsOfInterest());
subscriptionListDlg.getPresenter().setValue(profile.getSubscriptions());
// option two: use eventbus and valuechanged event, dialogs are
}
}
There is now a variance of concerns: Who will set the value in the sub-dialogs. You can forward to the presenter of the sub-dialog, or you set it directly in the sub-dialog.
Anyway, what should get clear to you now, is that you do not have only one presenter to rule all parts of the view. You need to split the presenters to be responsible for one subview each. What I found useful in such a large dialog tree, was to have a separate model class, that keeps the object currently beeing edited and provides change communication logic for other dialogs. For example, if you add a list of topics, and you add one topic, the dialog for the channel subscription selection may want to know, that there is now one more topic to be shown in the topic-filter.

How to pass information from one view (dockable) to another?

I am facing a design problem. If I have (for example) on the left dockable view a list view that contains some pojo's, how do I notify the center dockable which one is selected? I am trying to implement some kind of Master-Detail-View where the user selects one item and then can configure it in the center area and the right area.
Thanks in advance :)
It depends on how you want to design your application.
If you want to create a separate Editor for each pojo then you can have a look at the LeftTestPane provided by the drombler fx archetype for a sample.
#FXML
private void onNewSampleAction(ActionEvent event) {
sampleCounter++;
Sample sample = new Sample("Sample " + sampleCounter);
SampleEditorPane sampleEditorPane = new SampleEditorPane(sample);
Dockables.inject(sampleEditorPane);
Dockables.open(sampleEditorPane);
}
There is currently no API for selecting an already opened editor, but please note that editors are currently being improved with the work done for issue #111.
If you want a single detail view then you can use the Context Framework, which allows components such as Dockables and Actions to communicate in a loosly coupled way.
The ListView should implement LocalContextProvider and keep the selected pojo in its local Context.
#ViewDocking(...)
public class ListView extends SomeNode implements LocalContextProvider {
private final SimpleContextContent contextContent = new SimpleContextContent();
private final SimpleContext context = new SimpleContext(contextContent);
private MyPojo currentSelection;
...
#Override
public Context getLocalContext() {
return context;
}
...
if (currentSelection != null){
contextContent.remove(currentSelection);
}
currentSelection = <current selection>
if (currentSelection != null){
contextContent.add(currentSelection);
}
...
}
In this case, the DetailsView should be registered as a view (singleton), too, and implement LocalContextProvider as well as ActiveContextSensitive:
#ViewDocking(...)
public class DetailsPane extends SomeNode implements ActiveContextSensitive, LocalContextProvider {
private final SimpleContextContent contextContent = new SimpleContextContent();
private final SimpleContext context = new SimpleContext(contextContent);
private Context activeContext;
private MyPojo myPojo;
...
#Override
public Context getLocalContext() {
return context;
}
#Override
public void setActiveContext(Context activeContext) {
this.activeContext = activeContext;
this.activeContext.addContextListener(MyPojo.class, (ContextEvent event) -> contextChanged());
contextChanged();
}
private void contextChanged() {
MyPojo newMyPojo = activeContext.find(MyPojo.class);
if ((myPojo == null && newMyPojo != null) || (myPojo null && !sample.equals(newMyPojo))) {
if (myPojo != null) {
unregister();
}
myPojo = newMyPojo;
if (myPojo != null) {
register();
}
}
}
private void unregister() {
contextContent.remove(myPojo);
//reset DetailsView
}
private void register() {
// configure DetailsView
contextContent.add(myPojo);
}
...
}
Have a look at the RightTestPane provided by the drombler fx archetype for a sample.

NPE in Vaadin7 Navigation with my own ViewProvider implementation.

I tried to create a vaadin7 application with the use of Navigator.
I have my own ViewProvider implementation.
here is the relevant part of the UI class:
#Override
protected void init(VaadinRequest request) {
layout = new VerticalLayout();
layout.setSizeFull();
setContent(layout);
navigator = new Navigator(this, layout);
mainView = new MainView(navigator);
navigator.addView("", mainView);
ViewProviderImpl viewProviderImpl = new ViewProviderImpl(mainView);
navigator.addProvider(viewProviderImpl);
}
here is MainView:(this is the one that should be displayed by default. Currently it contains two buttons only. Should one hit the buttons, the navigator should take him to one of the other Views)
public MainView(Navigator navigator) {
this.setSizeFull();
this.addComponent(new Label("This is the main view 1"));
int i = 1;
createSubViewButtons(i++ , Constants.DASHBOARD, new DashboardView());
createSubViewButtons(i++ , Constants.SCHEDULE, new ScheduleView());
}
private void createSubViewButtons(int exNum, String caption, View view) {
navigator.addView(caption, view);
Button button = new Button(caption, new ClickListener() {
private static final long serialVersionUID = 1L;
#Override
public void buttonClick(ClickEvent event) {
navigator.navigateTo(event.getButton().getData().toString());
}
});
button.setData(caption);
button.setStyleName(Reindeer.BUTTON_DEFAULT);
this.addComponent(button);
}
and I have a class that implements ViewProvider.
This basically should map URLs to views. The getViewName() methods removes the unnecessary parts of the url, and the getView() should return the View instance based on the return value of getViewName(). (Anyway, I have the strong feeling that the code execution never gets here, as the exception happens earlier)
public class ViewProviderImpl implements ViewProvider {
private static final long serialVersionUID = 1L;
private static Map<String, View> mapping;
static {
mapping = new HashMap<String, View>();
ScheduleView scheduleView = new ScheduleView();
DashboardView dashboardView = new DashboardView();
mapping.put("CORE/maintain/schedule", scheduleView);
mapping.put("CORE/maintain/dashboard", dashboardView);
mapping.put(Constants.DASHBOARD, dashboardView);
mapping.put(Constants.SCHEDULE, scheduleView);
}
public ViewProviderImpl(MainView mv) {
mapping.put("", mv);
}
#Override
public String getViewName(String viewAndParameters) {
// to do --if it is non empty than take it otherwise use Page
StringBuilder sb = new StringBuilder();
String fullURL = Page.getCurrent().getLocation().toString();
String fullURL = viewAndParameters;
String arr[] = fullURL.split(Constants.ARRANGER_WITH_SLASH);
if (arr.length > 1) {
String shortURL = arr[1];
if (shortURL.contains(Constants.QUESTION_MARK)) {
shortURL = shortURL.split("\\?")[0];
}
if (shortURL.contains(Constants.SLASH)) {
// always remove the two first and keep the rest of it.
String split[] = shortURL.split(Constants.SLASH);
for (int i = 0; i < split.length; i++ ) {
if (i <= 1) {
continue;
}
sb.append(split[i]);
if (i >= 2 && i != split.length - 1) {
sb.append(Constants.SLASH);
}
}
}
}
return sb.toString();
}
For me, it seems logical. In reality, however it throws NPE. Why?
Probably I abuse the way how navigation should be used in Vaadin7, but I can't figure out what should I do...
java.lang.NullPointerException
vaadinacrys.MainView.createSubViewButtons(MainView.java:39)
vaadinacrys.MainView.<init>(MainView.java:33)
vaadinacrys.PoolarrangerUI.init(PoolarrangerUI.java:36)
com.vaadin.ui.UI.doInit(UI.java:641)
com.vaadin.server.communication.UIInitHandler.getBrowserDetailsUI(UIInitHandler.java:222)
com.vaadin.server.communication.UIInitHandler.synchronizedHandleRequest(UIInitHandler.java:74)
com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41)
com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1402)
com.vaadin.server.VaadinServlet.service(VaadinServlet.java:295)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Following up my original comment, you have not posted your MainView class fully but in it's constructor you're not assigning the Navigator navigator variable to a field nor passing it as a parameter to the createSubViewButtons method so you can use it there. If you have indeed a field called navigator, by the time navigator.addView(caption, view); gets executed it will be null, hence you get a NPE. Quick fix, this.navigator = navigator in your constructor & enjoy.

Categories