Before we start using domino-history we need to understand some terminology about how domino-history see a URL.
A URL in domino-history has three distinct parts, Paths, query parameters and fragments, please note here that domino-history does not operate on the host or port portions of a URL, so lets take an example :
http://localhost:8080/path1/path2?param1=valuea,valueb¶m2=valuec#fragment1/fragment2
In the above URL domino-history can operate on the following sections :
The paths in the above example we have two paths path1 and path2 domino-history API can append,remove, replace, or clear URL paths.
Paths in general are used for the navigation from one page or view in the application to another.
In the above example we have two query parameters param1=valuea,valueb and param2=valuec param1 has a multiple values valuea and valueb while param2 has a single value valuec, domino-history assumes that all query parameters are multi-value params and the single value parameters are just multi value but with a single entry.
Query parameters are usually used for search queries and specifying content context regarding the current data being processed in the page or view.
Fragments are the part that comes after the # in a URL, in the above example we have two fragments fragment1 and fragment2, domino-history API can append,remove, replace, or clear URL fragments.
Fragments are usually used for navigation to different parts within the same page or view.
In general the URL is assumed to hold your current page or view state, meaning that the URL should hold enough information to reconstruct the page from its current state exactly when the browser is refreshed or if the same URL is used in a different tab or browser. Thus, domino-history consider the URL as a state, And as we navigate from page to page or between different views we are actually changing that state, and we keep track of those states, that how the browser back button takes you to the previous page. Domino-history also keeps a track of that history and so it refers to the browser URL as StateHistory and the current URL token as a StateHistoryToken.
To start using Domino-history we need to create a new instance of the StateHistory
StateHistory history = new StateHistory()
We can have as many instances as needed, but in most cased a single instance should be enough, unless we need to operate over different root paths which is a topic we will discuss later, but now once we get the instance we can use to do one of the following operation
Pushing a state is domino-history of changing the URL without firing and event, it will just update the URL with the specified token, Example :
Assuming we start with the url http://localhost:8080 the following code :
stateHistory.pushState(StateToken.of("path1/path2"));
We change the browser URL to http://localhost:8080/path1/path2
Everytime we use pushState we replace the whole URL with the specified token and not part of the URL, this includes the paths, query parameters and the fragments, for Example the following will also replace the whole URL :
stateHistory.pushState(StateToken.of("path1/path2?param1=valuea¶m2=valueb"));
stateHistory.pushState(StateToken.of("path1/path2?param1=valuea¶m2=valueb#fragment"));
Another useful variation of the pushState is to use expression parameters in the token and substitute them for specified values dynamically, this is helpful in case you want to push state with variable paths, query parameters or fragments dynamically based on some logic, Example :
stateHistory.pushState(StateToken.of("path1/:namedPath"), TokenParameter.of("namedPath", "path2"));
//Result : http://localhost:8080/path1/path2
In this case the path namedPath which is prefixed with : will be replaced with the value specified in the TokenParameter before we push the token changing the URL to http://localhost:8080/path1/path2
Fragments can be the same, Example:
stateHistory.pushState(StateToken.of("path1/:namedPath#:namedFragment"),
TokenParameter.of("namedPath", "path2"),
TokenParameter.of("namedFragment", "fragment1"),
TokenParameter.query("param1", "valuea,valueb"),
TokenParameter.query("param2", "valuec")
);
//Result : http://localhost:8080/path1/path2?param1=valuea,valueb¶m2=valuec#fragment1
We can also specify the page title in the state token to reflect what view or page is currently active in the browser tab header, Example:
stateHistory.pushState(StateToken.of("dashboard/:dashboardView")
.title("Opened tickets")
, TokenParameter.of("dashboardView", "tickets"));
//Result : http://localhost:8080/dashboard/tickets
//And the browser tab title will become : Opened tickets
When ever we use push state we are actually pushing a new state to the browser history API, so clicking on the browser back button will still work and will cycle through the states we pushed.
The only difference between pushState and fireState is that when using fireState we will also fire an event that allow ant defined listener through domino-history to be triggered
The next section will explain how we can listen to state changes and react accordingly.
The StateHistory instance allow us to define listeners for the URL changes that are applied through the browser state API and domino-history, Example :
stateHistory.listen(state -> {
List<String> paths = state.token().paths();
List<String> fragments = state.token().fragments();
Map<String, List<String>> queryParams = state.token().queryParameters();
});
The above listener will listen to any change to the browser URL and will call the specified callback passing the State object that represent the new parsed URL value, In the listener you get the HistoryToken from the state which contains all information available in the URL parsed and ready to use in your application logic. for example, it will contain a list of the paths currently present in the URL, The received token can be further modified, and then we can use it to push a new state, or we can just read the information from the token and change our application current active page or data.
We will discuss the HistoryToken in details in later sections in this documentation.
Listeners can also filter which token they should listen to, The above example used an omitted default filter token that listen to any token change, so the above example can be also rewritten like this :
stateHistory.listen(TokenFilter.any(), state -> {
List<String> paths = state.token().paths();
List<String> fragments = state.token().fragments();
Map<String, List<String>> queryParams = state.token().queryParameters();
});
The TokenFilter.any() is a factory for one of the many predefined filters that comes built-in in domino-history, the filters provided can handle almost all the cases needed to check for a specific token either it is filtering by path, fragments or query parameters, We will discuss and list all the available filters in another part of the documentation, but for now we need to state that you still can implement your own filters to handle any special case you might need.
When we use listeners we are actually wrapping the browser state API popstate event, and this mean that in case we refresh the page or open a link in a new tab or browser window the even will not be fired by the browser, As this a limitation from the native browser API domino-history implements a mechanism to go around this, So in order to actually to trigger a listener in such scenario we just need to specify that in the listener itself using the following syntax :
stateHistory.listen(TokenFilter.any(), state -> {
List<String> paths = state.token().paths();
List<String> fragments = state.token().fragments();
Map<String, List<String>> queryParams = state.token().queryParameters();
}).onDirectUrl();
Or the following :
stateHistory.listen(TokenFilter.any(), state -> {
List<String> paths = state.token().paths();
List<String> fragments = state.token().fragments();
Map<String, List<String>> queryParams = state.token().queryParameters();
}).onDirectUrl(TokenFilter.endsWithPathFilter("path1"));
The onDirectUrl allow us to register the listener as one that will be triggered when the browser window is refreshed, or we open a link in a new tab or window, the state in that case will be checked against the filter specified in the listen method or with its own filter if specified in the onDirectUrl method.
Sometimes we might need to read the browser URL without firing a state or wait for change so we can get the state in a listener, for Example, we might need to reread the URL to re-update the page data periodically, or we want to push a new state that is very similar to the current active one but with a slight change and we dont want to forge the state token from scratch, in such cases the currentToken comes in handy, the method will read the current state and parse it for you allowing any kind of modification, then we can use the modified state to push or fire a new state token, for example :
//Assuming the url is http://localhost:8080/path1/path2
stateHistory.fireState(StateToken.of(stateHistory.currentToken().appendPath("path3")));
//Result : http://localhost:8080/path1/path2/path3
One important note here is that the currentToken will always return an instance of the represent the current URL and modifying this instance does not affect the URL directly , instead we need to push or fire the state in order to change the URL, for example :
//Assuming the url is http://localhost:8080/path1/path2
StateHistoryToken currentToken = stateHistory.currentToken();
currentToken.appendPath("path3");
stateHistory.fireState(StateToken.of(stateHistory.currentToken().appendPath("path4")));
//Result : http://localhost:8080/path1/path2/path4