Skip to content

Routing

Ahmad K. Bawaneh edited this page Nov 7, 2021 · 1 revision

Routing is the process of navigating from one presenter/view to another, but before we discuss routing in domino-mvp we need to understand how presenters are actually get activated.

First we need to know that simply creating a new instance of a presenter will not initialize it correctly, it will not make it go into the life-cycle stages, instead we need to create and initialize the presenter through the domino-mvp framework, which provide a simple mechanism to do so using what is called a presenter command, we create and send a command to the framework asking it to activate a presenter for us, each presenter will have its own auto generated command, for example, for the following presenter called SimplePresenter there will an auto generated command named SimplePresenterCommand and we can use the command to obtain a fully initialized presenter instance like this :

new SimplePresenterCommand().onPresenterReady(presenter -> {
    //Do something with the initialized presenter instance
}).send();

We send the command and receives an initialized presenter instance in the onPresenterReady method, but with this we will need handle the presenter life-cycle manually, so even though we can do this it is mainly for the framework internal usage.

Routing in domino-mvp is all about sending the presenter command and handling the life-cycle of the presenter.

  • URL Token routing

    In URL token routing we control the activation of a presenter based on the token presented in the URL bar in the browser -for web implementation-, when the URL is changed we check if the new URL matches a token assigned to our presenter and if so we activate it, but before we go into more details lets understand the URL Token:

    URL Token

    The URL token is the string in the URL bar of the browser except the base URL, for example if the URL bar has the string http://localhost:8080/path1/path2?query1=value1&query2=value2#fargment1/fragnment2 then the URL token that we will be using is /path1/path2?query1=value1&query2=value2#fargment1/fragnment2

    and this token devided into 3 different parts :

    • The path : /path1/path2.
    • The query : query1=value1&query2=value2.
    • The fragments : fargment1/fragnment2.

    and we can do routing based on any or a combo of the three parts

    To do a URL token routing we will need to listen to the browser URL changes and check if the new URL is a match to what we need to activate the presenter, we assign a token to a presenter using the annotation @AutoRoute on a presenter proxy, like the following example :

    for the proxy

    import org.dominokit.domino.api.client.annotations.presenter.AutoRoute;
    
    @PresenterProxy
    @AutoRoute
    public class SimpleViewPresenter extends ViewBaseClientPresenter<SimpleView> {
    }

    The following presnter will be generated :

    /**
     * This is a generated class, please don't modify
     */
    @Presenter(
        name = "",
        parent = ""
    )
    @AutoRoute(
        token = "",
        routeOnce = false,
        reRouteActivated = false,
        generateTask = true
    )
    @RoutingTask(SimpleViewPresenter_PresenterHistoryListenerTask.class)
    public class SimpleViewPresenter_Presenter extends SimpleViewPresenter {
    }

    Notice the defaults in the auto generated proxy, the default token is an empty token, and notice that now we have something called RotingTask, the AutoRoute annotation will also generate a startup task that registers a listner for the URL changes to activate the presenter when it should be.

    and the generated task should be like the following :

    /**
     * This is a generated class, please don't modify
     */
    @StartupTask
    public class SimpleViewPresenter_PresenterHistoryListenerTask extends BaseNoTokenRoutingStartupTask {
      public SimpleViewPresenter_PresenterHistoryListenerTask() {
        super(Arrays.asList(new DefaultEventAggregator()));
      }
    
      @Override
      protected void onStateReady(DominoHistory.State state) {
         new SimpleViewPresenter_PresenterCommand().onPresenterReady(presenter -> {
          bindPresenter(presenter);
        } ).send();
      }
    }

    The generated task in this case will register a listener that listen to any url change and will send the presenter command to activate it, this is because we left the token empty and empty token in domino-mvp means any token, so what ever the URL in the browser such a presenter will always get activated.

    But if we change the proxy like this :

    import org.dominokit.domino.api.client.annotations.presenter.AutoRoute;
    
    @PresenterProxy
    @AutoRoute(token = "path1/path2")
    public class SimpleViewPresenter extends ViewBaseClientPresenter<SimpleView> {
    }

    The generated task will be like this :

    /**
     * This is a generated class, please don't modify
     */
    @StartupTask
    public class SimpleViewPresenter_PresenterHistoryListenerTask extends BaseRoutingStartupTask {
      public SimpleViewPresenter_PresenterHistoryListenerTask() {
        super(Arrays.asList(new DefaultEventAggregator()));
      }
    
      @Override
      protected TokenFilter getTokenFilter() {
        return TokenFilter.endsWithPathFilter("path1/path2");
      }
    
      @Override
      protected TokenFilter getStartupTokenFilter() {
        return TokenFilter.startsWithPathFilter("path1/path2");
      }
    
      @Override
      protected void onStateReady(DominoHistory.State state) {
         new SimpleViewPresenter_PresenterCommand().onPresenterReady(presenter -> {
          bindPresenter(presenter);
        } ).send();
      }
    }

    Now we have two token filters being assigned in this task, one of them is for on application start-up (when first time open the applicaton or when we hit the refresh button) and the other is when we do an in application routing, in common cases what is generated is enough but all of this canbe customized as we will see later.

    To explain why we have 2 different filters for start-up and in application navigation lets take and example :

    Assume we start with an empty token so the url is http://localhost:8080 and we have 2 presenters A and B, presenter A will be activate by the token pathA ans so in our home page we have a link or a button that changes the URL to http://localhost:8080/pathA once it does the presenter A will be activated and its view should be revealed in the page, now inside A view we want to reveal presenter B view so we have a link or a button that adds the path pathB to the URL making it like this http://localhost:8080/pathA/pathB then presenter B will be activated and its view will be revealed, now when we added pathB we changes the URL and that will also trigger the listener for presenter A but we dont want to reactivte presnter A because it is already active, thats why we say token ends with pathA., But what if we hit the refresh button on the browser? ends with pathA will not be true and presenter A wont be activated, so instead on a refresh we say token starts with pathA instead. The idea here is to give you full control over when and how you activated the presenters in different cases.

    lets focus on the getTokeFilter which return a fiter that says the path ends with which means the presenter can only be activated if the path part of the URL token ends with the specified token, we can always specify different token filters for our presenter in the proxy using both @RoutingTokenFilter and @StartupTokenFilter annoations, We add those annotation in the proxy to static methods that take a string token and return a TokenFilter

    For example the proxy below :

    import org.dominokit.domino.api.client.annotations.presenter.AutoRoute;
    import org.dominokit.domino.api.client.annotations.presenter.StartupTokenFilter;
    import org.dominokit.domino.api.client.annotations.presenter.RoutingTokenFilter;
    
    @PresenterProxy
    @AutoRoute(token = "path1/path2")
    public class SimpleViewPresenter extends ViewBaseClientPresenter<SimpleView> {
    
        @RoutingTokenFilter
        public static TokenFilter onRoutingFilter(String token){
            return TokenFilter.contains(token);
        }
    
        @StartupTokenFilter
        public static TokenFilter onStartupFilter(String token){
            return TokenFilter.exactPathFilter(token);
        }
    }

    Will generate the following task

    /**
     * This is a generated class, please don't modify
     */
    @StartupTask
    public class SimpleViewPresenter_PresenterHistoryListenerTask extends BaseRoutingStartupTask {
      public SimpleViewPresenter_PresenterHistoryListenerTask() {
        super(Arrays.asList(new DefaultEventAggregator()));
      }
    
      @Override
      protected TokenFilter getTokenFilter() {
        return SimpleViewPresenter_Presenter.onRoutingFilter("path1/path2");
      }
    
      @Override
      protected TokenFilter getStartupTokenFilter() {
        return SimpleViewPresenter_Presenter.onStartupFilter("path1/path2");
      }
    
      @Override
      protected void onStateReady(DominoHistory.State state) {
         new SimpleViewPresenter_PresenterCommand().onPresenterReady(presenter -> {
          bindPresenter(presenter);
        } ).send();
      }
    }

    Notice how the task is now referencing the annotated methods in the proxy.

    The methods must be public static as they will be referenced before an instance of the presenter is actually created.

    To learn more about how we listen to URL changes and what kind of token filters we can use, please refer the Domino-history project.

    The @AutoRoute also has some other parameters to fine control the routing behavior :

    • generateTask : Default is true, when it is set to false no startup task will be generated so that we can manually write our own customized task.
    • routeOnce : Default is false, when set to true the URL change listener registered by the routing task will be removed once a routing is completed preventing the routing from happening again, this is usefull for cases where the UI component does not change an remains on the screen all time in regards of how we navigate in the application, example is the application layout.
    • reRouteActivated : Default value is false, when set to true even if the presenter is currently active it will deactivated and activated agian.

    So far we learned how we can assign tokens and token filters to our presenters, next we will learn about how we change the token and how we can have variable tokens, and how we can read information from the token.

    • Firing token

      In Domino-mvp the URL changes are like the navigation history for our application and therefor we call the navigation as history we can obtain an instance of our application navigation history by calling the method history() in any presenter/proxy, The history api will allow us to manipulate the URL and will also parse the URL for us.

      To change the URL we have two options history().fireState(..) and history().pushState(...) , the fireState will change the URL and publish the events to all listeners including the current active presenter history listener while the pushState will change the URL without firing an event.

      To change the URL anywhere in the presenter/proxy use history().fireState(new URL token) :

      public void onNavigationButton(){
          history().fireState("path1/path2");
      }

      If the URL had http://localhost:8080 after that call it will become http://localhost:8080/path1/paht2, firing or pushing a new token will always update the whole token, if you want to change the current token you can always obtain the current token in the URL using history().currentToken() and do the modification then fire it again, for example if the current token is http://localhost:8080/path1/paht2 and we want to add path3 to it we can do it like this :

      history().fireState(history().currentToken().appendPath("path3"));

      To learn the full history api please refere the Domino-history project.

    • Tokens variables

      Token in general are not just a way to activate the presenters but they also can contains valuable information needed by our presenter to do its job, they are the best way to keep track of the application state between refreshes or restarts, for example assume we have page that show a book details, a URL that might represent this specific book might look like this http://localhost:8080/books/1234 where 1234 is the ID or a unique identifire, then we take this URL and paste in the browser tab or window URL bar we should endup viewing the same book details.

      But, it does not make sense to fix the ID in the presenter token as we dont write a presenter for each specific book, instead we need the ID to be a variable and we need to fetch the ID value from the URL so our presnter can tell which book it needs to dispplay.

      In Domino-mvp we can always fetch all kind of information from the token parts - Path, query or fragments - and use it in the presenter, and we can do routing based on token with wildcards or variables, for example a book details proxy can define the token like the following :

      @PresenterProxy
      @AutoRoute(token = "books/:bookId")
      public class BookDetailsProxy extends ViewBaseClientPresenter<BookDetailsView> implements     BookDetailsView.BookDetailsUiHandlers {
      }

      Now as bookId is a variable any token that can make up for the variable will activate the presenter, for example : books/1234, books/4356, books/someId, books/blahblah are all valid tokens that ill activate that presenter.

      In a proxy we can obtain the actual value of token variable using annotations for each part of the token :

      • @PathParameter : We use this method to annotate a field in the proxy to hold the actual value of the a path variable, exmple :
      import org.dominokit.domino.api.client.annotations.presenter.PathParameter;
      
      @PresenterProxy
      @AutoRoute(token = "books/:bookId")
      public class BookProxy extends ViewBaseClientPresenter<BookView> {
      
          @PathParameter
          protected String bookId;
      }

      When the presenter is activated it will assign the actual value of the :bookId from the URL token to the annotated field, the name of the field should match the variable name or if we want to use a different name in the field we specify the variable name in the annotation, example :

      import org.dominokit.domino.api.client.annotations.presenter.PathParameter;
      
      @PresenterProxy
      @AutoRoute(token = "books/:bookId")
      public class BookProxy extends ViewBaseClientPresenter<BookView> {
      
          @PathParameter("bookId")
          protected String id;
      }

      We can have as many path variables in the token as long as the names of thoses variable does not collide, and we can obtain each path variable in the same way, for example if our book has a composite ID :

      import org.dominokit.domino.api.client.annotations.presenter.PathParameter;
      
      @PresenterProxy
      @AutoRoute(token = "books/:bookId/:year")
      public class BookProxy extends ViewBaseClientPresenter<BookView> {
      
          @PathParameter
          protected String bookId;
          
          @PathParameter
          protected String year;
      }
      • @QueryParameter : We use this method to annotate a field in the proxy to hold the actual value of the a query parameters.

      Unlike the path, the query part of the URL may or may not exist in presenter token, for example a presenter with the token book/:bookId can be activated by tokens like this book/:bookId or like this book/:bookId?year=2021?author=dominokit, In this case the query part of the URL can also have some information the presenter can make use of, for example for a search, but also unlike the path parameters query paramters does not need to be specified with a variable in the token as they already a pair of key and value by design, in a proxy we can obtain the value of a query parameter like the following :

      import org.dominokit.domino.api.client.annotations.presenter.PathParameter;
      
      @PresenterProxy
      @AutoRoute(token = "books/:bookId")
      public class BookProxy extends ViewBaseClientPresenter<BookView> {
      
          @QueryParameter
          protected List<String> search;
      }

      Notice that we dont have search in the token but we still can get its value when it is present in the URL, also notice that we are using a list here insted of a single string, that is because a query parameter can have multiple values, example books/123?author=dominokit&author=vegegoku and therefor we have a list.

      And same as the path variables, we can have as many query parameters and we can specify the name in the annotation.

      • @FragmentParameter : We use this method to annotate a field in the proxy to hold the actual value of the a fragment variable, exmple :
      import org.dominokit.domino.api.client.annotations.presenter.PathParameter;
      
      @PresenterProxy
      @AutoRoute(token = "books/:bookId#:activeTab")
      public class BookProxy extends ViewBaseClientPresenter<BookView> {
      
          @FragmentParameter
          protected String activeTab;
      }

      Fragment parameters works exactly like the path parameters, but it asign values from the part after the # in the url, fragments can be a good choice to implement navigation to a specific section in the page.

    In Domino-mvp routing can be acheived with all parts of the token, but in general we assume that we will use the path for routing from presenter to another, and we use query to hold more information for our presenter that can even be passed to the server, while we expect the fragment to be used for inside same page navigation, but we dont restrict using them in any other way you desire.

    In some cases we might need to access the token and the state that was responsible for the presenter activation and read whatever information it has manually, in such case we can use @RoutingState, example

    import org.dominokit.domino.api.client.annotations.presenter.RoutingState;
    
    @PresenterProxy
    @AutoRoute(token = "books/:bookId")
    public class BookProxy extends ViewBaseClientPresenter<BookView> {
    
        @RoutingState
        protected DominoHistory.State state;
    }

    We can also use @OnRouting to do some logic right after a sucess routinig and before the view is revealed, example :

    import org.dominokit.domino.api.client.annotations.presenter.OnRouting;
    
    @PresenterProxy
    @AutoRoute(token = "books/:bookId")
    public class BookProxy extends ViewBaseClientPresenter<BookView> {
    
        @RoutingState
        protected DominoHistory.State state;
    
        @OnRouting
        public void doSomething(){
            //do something after success routing and before view reveal
        }
    }