Navigator7 add-on: API of Vaadin 7 ?

A live demo using the navigator
https://hack23.com/
, NOTE: self signed cert and still very much work in progress. user/password or sysop/password for admin user.

All the best, Pether

Congrats Pether!

I’d like to describe the following problem here, because I think it should be addressed by the navigation system of Vaadin 7. It can’t be addressed by Navigator 7 without making choices for Vaadin core.

A batch job must send a mail (very legitimate need). In the mail, it will include a “click here” link to a page of your web application.


ExternalResource res = new ParamPageResource(CoursePage.class, course);
String courseUrl = BlackBeltUriAnalyzer.getFullUrl( res );
String mailBody = "<a href='"+ courseUrl + "'>Click Here!</a>";

We face 2 problems:

  • line 1 needs (through cascading calls) a Nav7 WebApplication initialized.
  • line 2 needs the (non existing in batch) ServletContext to build the url.


Problem 1: WebApplicaiton Initialization

ParamPageResource is a class of Nav7 indirectly extending ExternalResource from Vaadin.
ParamPageResource and its ancestor PageResource will verify that:

  • the given page (CoursePage.class here) is a registered page (your descendant of WebApplication does register pages), and
  • that the given parameter (course) can be injected (type check) in a @Param attribute of the page class (CoursePage.class).

To do the first point, it needs the WebApplication to have been started. But the name of your class (extending WebApplication) is in the web.xml as init parameter of the Vaadin servlet.
Because of that, instantiating your WebApplication class (BlackBeltWebApplication in my exammple) is not possible outside of a web thread. A batch thread within the web application, or a non web application could not start/use the Vaadin navigator.

A possible solution would be to note that Vaadin configuration information outside of the web.xml file. A VaadinConfig.xml (or Vaadin.cfg.xml or Vaadin.properties or whatever) file.
Either we should give the name/location of the file in the web.xml or in the main method of the batch (as Hibernate would do for hibernate.cfg.xml), either we use conventions (as Servlet does for web.xml or JPA does for persistence.xml).

Another solution (than having a config file) would be to annotate the BlackBeltWebApplication class @org.vaadin.Application and have powerful annotation detection code to find it (what do we do if we find two?).

The WebApplication descendant may contain much application specific settings (as the name of the pages), but we need to bootstrap it someway.

Please keep that in mind when doing Vaadin 7.


Problem 2: Absolute URLs

The code of BlackBeltUriAnalyzer.getFullUrl is application specific and simple:


/** With the domain name */
public static String getFullUrl(ExternalResource resource) {
    return "http://www.blackbeltfactory.com/ui" + resource.getURL();
}

Any better or more generic suggestion?

The same problem arises with v6 File upload.

Upload.SucceededListener

Vaadin is not “completely initialized”. File upload requests don’t go through the Vaadin servlet (it’s descendant, the nav7 NavigableApplicationServlet), which means that Nav7 is not properly initialized. We could make Nav7 self defensive enough to handle that, but … user’s code is not supposed to avoid getting the WebApplication through the ThreadLocal variable.

WebApplication.getCurrent()

Is it possible to fix that with elegance in Vaadin v7 ?

I’ve implemented (v7.44) the following work-around: a static variable.
Your WebApplication sub class is instantiated only once (per JVM and per ServletContext) and is storted in both the ServletContext and that static variable.

See the javadoc below for further discussion.


public class WebApplication {

    public static final String WEBAPPLICATION_CONTEXT_ATTRIBUTE_NAME = WebApplication.class.getName();

    /** Holds the ServletContext instance of the current thread.
     * If it's null for the current thread, we are not in a web thread but in a batch thread.
     * 
     * We need the ServletContext because we store the WebApplication instance there.
     *   
     * @See http://vaadin.com/forum/-/message_boards/message/155212?_19_delta=10&_19_keywords=&_19_advancedSearch=false&_19_andOperator=true&cur=2#_19_message_166110
     **/
    static protected ThreadLocal<ServletContext> currentServletContext = new ThreadLocal<ServletContext>();

    /** A batch thread may need the WebApplication instance. 
     * For example, to build an url (with new ParamPageResource(MyPage.class, myEntity)).
     * If this happens, we can get and instance of the WebApplication here (currentServletContext will return null).
     * 
     * This is unfortunate, but somehow dictated by the Servlet specification. Good servlet container practice tells to put the reference in the ServletContext. Batch needs tell to put it in a static variable (singleton). 
     * An improvement would be to store the name of the WebApplication subclass (and all the Vaadin config) in a more neutral place than the web.xml.
     * That way, if the batch thread starts before the first web request (thread) arrives, or if the batch executes outside of the web contained, then we could instantiate from the batch thread (if not instantiated yet, of course).
     *
     * There is probably a difficult design choice to be made by Vaadin guys here, to integrate Navigator7 into Vaadin7 (difficult design choice to have the notion of Web app at all in Vaadin).
     * Keeping the instance in both the ServletContext and a static variable is an overlap.
     *    Keeping it in the ServletContext only is not enough (would make batch jobs fail). 
     *    Maybe keeping it in this static reference only?
     * Note that frameworks as Spring have the same (unsolved as far as I know) problem.
     *    In web applications, they keep the Spring ApplicationContext in the ServletContext.
     *    As Quartz is usually started by Spring, it's easy for Spring to pass its (Web)ApplicationContext to Quartz,
     *    and so to batch jobs. But batch jobs have to do some gym to extract the Spring (Web)ApplicationContext from Quartz,
     *    it's not transparent.
     * We don't have that possibility here (Vaadin does not start batch jobs). 
     * Maybe somebody will have a better idea ;-) 
     */
    static protected WebApplication staticReference;
    
    
    /** Don't hesitate to use this method ;-)
     * Returns null if we are not in a web thread (or a badly initialized web app) */
    public static WebApplication getCurrent() {
        ServletContext servletContext = currentServletContext.get();
        if (servletContext == null) {  // We are in a batch thread, probably: http://vaadin.com/forum/-/message_boards/message/216481?_19_delta=10&_19_keywords=&_19_advancedSearch=false&_19_andOperator=true&cur=3#_19_message_216481
            if (staticReference == null) {
                throw new RuntimeException("WebApplication has not been instantiated yet. " +
                		"A common cause is that the thread executing this, is a batch thread, AND that the NavigableApplicationServlet has not bee initialized yet. " +
                		"Suggestions:" +
                		" delay your batch;" +
                		" or call WebApplication.init(YourWebApplication.class) manually at the beginning of your batch;" +
                		" or call WebApplication.init(YourWebApplication.class) in a ServletContextListener.contextInitialized() when your web application starts instead of NavigableApplicationServlet.init()");
            } else {
                return staticReference;
            }
            
        } else { // web thread (usual case).
            return (WebApplication)servletContext.getAttribute(WEBAPPLICATION_CONTEXT_ATTRIBUTE_NAME);
        }
    }

If a batch job (thread) starts before the ServletContext is initialized (or if the batch job starts outside a web container), then the batch job will have to call WebApplication.init(MyWebApplication.class), to specify his WebApplication subclass, when it starts (if he wants to use Vaadin to build URLs).

Food for the brain: What is the notion of WebApplication? (what should it be in Vaadin 7?). We know what the Navigator7 class will instantiate (NavigatorConfig, ParamUriAnalyzer), but we don’t know what the programmer using the framework will initialize in it’s subclass… What if he starts stuff that should only be done in a web thread (and then instantiation from a batch thread would be unfortunate)? Should we provide him 2 entry points, one for generic application Initialization, and another for web application initialization? …

Hi!

I just started testin Navigator7. I followed the
BasicUsage
-article on how to set things up but I failed on the very first command in the example, NavigableApplication has no registerPages()-method. With some code investigation I found that it had been moved to WebApplication, but how that works along with the rest of the classes still elude me. I suggest that you update the BasicUsage article to correspond to the current architecture.

You are completely right Jens.
I’ve improved that page and removed the links to the videos (that were hosted on sreentoaster which is closed).

Thanks.

Great!

I’ve been cruising along the source code and I want to clarify one thing to myself. Is it correct that every time you call navigateTo(class) a new instance of that view is created, instead of using an existing one?

I came to the same conclusion browsing the code. Because i did not want this, i always use getNavigator().invokeInterceptors(page, params, true) to navigate between pages, where the variable ‘page’ is one i create at the intialization of the app.

However, this only works when having a ‘navigate-to-…’-button or menu in your app. For browsing between pages using urls, i had to built in a Interceptor and ‘intercept’ the navigation to a new instance of the page and overwrite it by instead generating a new PageInvocation of the old page and invoke that one.

I think this is something that should be possible in Vaadin7 without the hacks i’m using…

Yes, it is correct.
Imagine your application has 3 pages. User is on page1.

Then he clicks a link to go to page2. Or he clicks a button and in the listener you forward to page2 (navigateTo).
Page2 is instantiated for that user.

What should happen with page1? Would you keep it in a kind of cache (oh no!) ?
If from page2 or page3, you come back to page1, Nav7 reinstantiates page 1.

Pages instances are not shared accross Windows (browser’s tabs) neither accross users.

That should, according to me, be the normal behavior.

If I understand well what you are trying to do, you need 2 stateful pages, and allow the user to switch between them often. You don’t want to state of the pages to be lost for that user. Nav7 should keep these kind of pages in the Application (user context, not the Nav7 WebApplication) and reuse them. Is that what you want?

If it is what you want, I suggest to add an attribute to the @Page annotation.

@Page(keepAndReuse=true)

Hi John!

We are using your Navigator7 addon, but stumble upon an issue. In NavigableAppLevelWindow you are using a CssLayout, but I need a CustomLayout as container.

What would be the best way to implement this? Trying to extend AppLevelWindow results in an issue with NavigableApplication.createNewNavigableAppLevelWindow(), which returns a NavigableAppLevelWindow.

For now I implemented a patch to Navigator7 (see below), enabling me to extend NavigableAppLevelWindow.

navigator7 jpeeters$ svn diff src/org/vaadin/navigator7/window/NavigableAppLevelWindow.java
Index: src/org/vaadin/navigator7/window/NavigableAppLevelWindow.java

— src/org/vaadin/navigator7/window/NavigableAppLevelWindow.java (revision 32)
+++ src/org/vaadin/navigator7/window/NavigableAppLevelWindow.java (working copy)
@@ -17,7 +17,7 @@
*/
public abstract class NavigableAppLevelWindow extends AppLevelWindow {

  • private Navigator navigator;
  • protected Navigator navigator;

    protected Component page; // Current page being displayed. null if no page set yet.
    public ComponentContainer pageContainer; // Contains page (there could be no page yet, so we cannot rely on this.page.getParent() because this.page could be null. Instantiated by descendants.

Hi Johaness,

Thanks for using Nav7, I love to get real world feedback.

NavigableAppLevelWindow does not play with CssLayouts.
It’s the descendant: FixedAppLevelWindow. You can decide to extend NavigableAppLevelWindow with YourNavigableAppLevelWindow and put the layout constructs that you want.

It would help me to know what you are trying to do (why a CustomLayout?).
Do you need a fixed (width) web layout? (a column with 1000 pixels to contain all your pages)
Do you need a header and a footer?

I’ve not understood your patch (can’t see what you have changed in the code).

Have a good day,
John.

Hi John,

Thanks for the feedback.

In the attach() method of NavigableAppLevelWindow you set the content of the Window to a CssLayout and create the Navigator object.


        Layout main = new CssLayout();
        main.addStyleName("PageTemplate-mainLayout");
//        main.setWidth("100%");   If you do that instead of the addStyleName (containing a width:100%;) above, Vaadin JavaScript will recompute the width everytime you resize the browser.
        this.setContent(main);

        // Must be done after calling this.setConent(main), as for any component added to the window.
        this.navigator = new Navigator();
        this.addComponent(navigator);

When subclassing NavigableAppLevelWindow with MyNavigableAppLevelWindow I can’t override the attach() method since the navigator instance in NavigableAppLevelWindow is private


    private Navigator navigator;

My patch makes navigator protected so I can set it in my overridden attach method. Hereby the current content of MyNavigableAppLevelWindow, for your reference


public class MyAppLevelWindow extends NavigableAppLevelWindow {
	
        @Override
        public void attach() {
                // Main layout creation. Do that before you add anything to the Window.
    	        CustomLayout main = new CustomLayout("main");
                // Needed to prevent Vaadin setting the container size using JavaScript. We prefer CSS styling
                main.setSizeUndefined();
                this.setContent(main);    

                // Must be done after calling this.setConent(main), as for any component added to the window.
                this.navigator = new Navigator();
                this.navigator.addStyleName("navigator7");
                // Add the navigator to every page, on a known location
                main.addComponent(navigator, "navigator7");

                pageContainer = createComponents();  // Let descendants add components in this.getContent().
        }
	
	@Override
	protected ComponentContainer createComponents() {
                // No need to do anything special here, just return the CustomLayout("main") set in attach().
		return this.getContent();
	}

	@Override
	public synchronized void changePage(Component pageParam) {
		CustomLayout customLayout = (CustomLayout)pageContainer;
		this.page = pageParam;
		// add the page to the CustomLayout at the mainContainer location
		customLayout.addComponent(page, "mainContainer");
	}
}


We use CustomLayout to give our layout team full control of the CSS styles.

Ok, sorry, I missed the CssLayout in NavigableAppLevelWindow.

This is Nav7 original code:


public abstract class NavigableAppLevelWindow extends AppLevelWindow {

    private Navigator navigator;
    
    protected Component page;  // Current page being displayed. null if no page set yet.
    public ComponentContainer pageContainer;  // Contains page (there could be no page yet, so we cannot rely on this.page.getParent() because this.page could be null. Instantiated by descendants.
    

    @Override
    public void attach() {
        // Main layout creation. Do that before you add anything to the Window.
        Layout main = new CssLayout(); 
        main.addStyleName("PageTemplate-mainLayout");
//        main.setWidth("100%");   If you do that instead of the addStyleName (containing a width:100%;) above, Vaadin JavaScript will recompute the width everytime you resize the browser.
        this.setContent(main);    

        // Must be done after calling this.setConent(main), as for any component added to the window.
        this.navigator = new Navigator();
        this.addComponent(navigator);

        pageContainer = createComponents();  // Let descendants add components in this.getContent().
        pageContainer.addStyleName("FixedPageTemplate-bandOuterLayoutPage");
    }

I made navigator protected instead of private, you have a good idea, thank you.
I made pageContainer protected instead of public.

I created the method “createMainLayout” that you may override to return your CustomLayout instead of the CssLayout

I moved this line to the FixedAppLevelWindow class:


        pageContainer.addStyleName("FixedPageTemplate-bandOuterLayoutPage");

public abstract class FixedApplLevelWindow extends NavigableAppLevelWindow {
    ...

    @Override
    public void attach() {
        super.attach();
        pageContainer.addStyleName("FixedPageTemplate-bandOuterLayoutPage");
    }

Your class MyNavigableAppLevelWindow may look like this now:


public class MyAppLevelWindow extends NavigableAppLevelWindow {
    @Override
    protected Layout createMainLayout() {
        Layout layout = new CustomLayout("main"); 
        // Needed to prevent Vaadin setting the container size using JavaScript. We prefer CSS styling
        layout.setSizeUndefined();
        retunr layout;
    }

    @Override
    public void attach() {
        super.attach();
        this.navigator.addStyleName("navigator7");
        // navigator is already in the main layout but here we specify the location
        CustomLayout customLayout = (CustomLayout)pageContainer;
        customLayout.addComponent(navigator, "navigator7");
    }
    

    @Override
    public synchronized void changePage(Component pageParam) {
       CustomLayout customLayout = (CustomLayout)pageContainer;
       this.page = pageParam;
       // add the page to the CustomLayout at the mainContainer location
       customLayout.addComponent(page, "mainContainer");
   }
}

It’s it ok with you, I’ll release a new version.

Hi John,

That looks good. I assume you are using Layout main = createMainLayout() in NavibableAppLevelWindow?

I’m fine with the commit.

Hi there!

First of, thank you for that great addon!
ApplicationConnection.updateVariable() method doesn’t work in my vaadin widget in Navigaror7 based application. I just can’t get data from client side. When I change NavigableApplication to usual com.vaadin.Application it works fine. Where could be a problem?

Thank you!

Greg.

Hi guys,

For of all, thanks for this amazing Add-on.

I’ve got few questions regarding this Add-On and Spring.

We’ve got an application which is like this

An Auth module without Header/Footer
Once User is logged on we use Spring to find out implementation on modules the user is registered and build Application with Header and footer

Questions are :

  • How to get access to application context before createComponents() method is called as Spring need it ? I mean in ApplicationLevel Class extending HeaderFooterFixedAppLevelWindow
  • How to modify Header and Footer Application “on the fly” as we need empty header/footer at the beggining
  • Is it possible to keep in memory pages ? As each time we navigate to it, it seems pages are reloaded and we need to keep track of it because of long queries executed on some pages.

  • How to keep variables in AppLevel class and access it in pages
    → use NavigableApplication.getCurrent();
  • And if you have some advice to send me when using Spring and Navigator7 u’ll be welcome :slight_smile:

Sorry for all theses questions, I’m still a noob at programming :wink:

Hi Gregory,

Thank you.

I’m afraid that I’ve no idea. My GWT and client side skills are very poor. I don’t see what in Nav7 could interfer with C/S communications. I suggest you to ask the question in another thread giving more details (what you’ve found out so far).

John.

Thank you Anthony. Feel free to vote 5* on it :wink:

I guess that Vaadin needs the Application instance very early. You probably leave it “empty” until you know the user and then create visual components for header/footer, attached to the Window, attached to the Application.

Sorry, I don’t understand this one. Maybe my replies to the other questions will reply this?

You have an example of modifying the header in the sample application with Nav7. It shows you part of the URI if I remember well.


///// NavigationListener label
        final Label navLabel = new Label();
        navLabel.setWidth(null);
        header.addComponent(navLabel);
        header.setComponentAlignment(navLabel, Alignment.TOP_RIGHT);
        
        ((MyWebApplication)MyWebApplication.getCurrent())
            .getPageChangeListenerInterceptor()
            .addPageChangeListener( new PageChangeListener() {
            @Override  public void pageChanged(NavigationEvent event) {
                navLabel.setValue("PageChangeListener: pageClass = "+ event.getPageClass() +
                                                  " -- params = " + event.getParams());
            }
        });

This example is probably more complex than what you need: it uses an interceptor to change the header at every request.
You have a very specific place (user login) to change the header.
Just store the header component as attribute in MyAppLevelWindow extends HeaderFooterFixedAppLevelWindow, and from the login code, call a method to fill it with the appropriate content.
Now, how from your login code, to get an instance to MyAppLevelWindow ?


NavigableApplication.getCurrentNavigableAppLevelWindow()

Simple.
But maybe not enough: what if your user did open multiple tabs before loging in. When he comes back to another tab, the header should be updated too. There is one instance of (MyAppLevel)Window per browser tab. This is more subtle.

This is some code from BlackBeltFactory application:


    public static void refreshHeaders() {
        for (Object w : BlackBeltApplication.getCurrent().getWindows()) { // For all the windows of the current session.
            if (w instanceof BlackBeltAppLevelWindow) {
                ((BlackBeltAppLevelWindow)w).getHeader().refresh();
            }
        }
    }

I replied above in this thread:
http://vaadin.com/forum/-/message_boards/message/155212?_19_delta=10&_19_keywords=&_19_advancedSearch=false&_19_andOperator=true&cur=3#_19_message_224760

but nobody confirmed that it would help, so I’ve not implemented it yet.

Yes. It’s an important method.
WebApplication.getCurrent() is probably more important (and things not specific to a user should be kept there).

Not for Nav7. For Vaadin in general, I suggest you to use @Configurable(preConstruction=true) on your Vaadin components (as Header, or MyPage,…) and link to your Spring beans (service and DAO layer) with @Autowired. It will require you to install AJDT in Eclipse and to add weaving in your ant build.

You are welcome. These questions show that you are no noob at programming.

O_o !

What a good support !
It has definitely opened my eyes on how to use it !

Thanks a lot, I’ll try all your advices and’ll come back for feedbacks :slight_smile:

[Edit : +5 stars rated :slight_smile: ]