@@ -119,7 +119,7 @@ public NavigationState getNavigationState() {
119
119
*/
120
120
@ SuppressWarnings ("unchecked" )
121
121
// Non-private for testing purposes
122
- static <T extends HasElement > T getRouteTarget (Class <T > routeTargetType ,
122
+ <T extends HasElement > T getRouteTarget (Class <T > routeTargetType ,
123
123
NavigationEvent event ) {
124
124
UI ui = event .getUI ();
125
125
Optional <HasElement > currentInstance = ui .getInternals ()
@@ -160,34 +160,13 @@ public int handle(NavigationEvent event) {
160
160
return result .get ();
161
161
}
162
162
163
- final ArrayList <HasElement > chain ;
163
+ final ArrayList <HasElement > chain = new ArrayList <>() ;
164
164
165
165
final boolean preserveOnRefreshTarget = isPreserveOnRefreshTarget (
166
166
routeTargetType , routeLayoutTypes );
167
167
168
- if (preserveOnRefreshTarget ) {
169
- final Optional <ArrayList <HasElement >> maybeChain = getPreservedChain (
170
- event );
171
- if (!maybeChain .isPresent ()) {
172
- // We're returning because the preserved chain is not ready to
173
- // be used as is, and requires client data requested within
174
- // `getPreservedChain`. Once the data is retrieved from the
175
- // client, `handle` method will be invoked with the same
176
- // `NavigationEvent` argument.
177
- return HttpStatusCode .OK .getCode ();
178
- } else {
179
- chain = maybeChain .get ();
180
- }
181
- } else {
182
-
183
- // Create an empty chain which gets populated later in
184
- // `createChainIfEmptyAndExecuteBeforeEnterNavigation`.
185
- chain = new ArrayList <>();
186
-
187
- // Has any preserved components already been created here? If so,
188
- // we don't want to navigate back to them ever so clear cache for
189
- // window.
190
- clearAllPreservedChains (ui );
168
+ if (populateChain (chain , preserveOnRefreshTarget , event )) {
169
+ return HttpStatusCode .OK .getCode ();
191
170
}
192
171
193
172
// Set navigationTrigger to RELOAD if this is a refresh of a preserve
@@ -248,6 +227,94 @@ public int handle(NavigationEvent event) {
248
227
return statusCode ;
249
228
}
250
229
230
+ /**
231
+ * Populate element chain from a preserved chain or give clean chain to be
232
+ * populated.
233
+ *
234
+ * @param chain
235
+ * chain to populate
236
+ * @param preserveOnRefreshTarget
237
+ * preserve on refresh boolean
238
+ * @param event
239
+ * current navigation event
240
+ * @return {@code true} if additional client data requested, else
241
+ * {@code false}
242
+ */
243
+ private boolean populateChain (ArrayList <HasElement > chain ,
244
+ boolean preserveOnRefreshTarget , NavigationEvent event ) {
245
+ if (preserveOnRefreshTarget ) {
246
+ final Optional <ArrayList <HasElement >> maybeChain = getPreservedChain (
247
+ event );
248
+ if (!maybeChain .isPresent ()) {
249
+ // We're returning because the preserved chain is not ready to
250
+ // be used as is, and requires client data requested within
251
+ // `getPreservedChain`. Once the data is retrieved from the
252
+ // client, `handle` method will be invoked with the same
253
+ // `NavigationEvent` argument.
254
+ return true ;
255
+ } else {
256
+ chain .addAll (maybeChain .get ());
257
+ // If partialMatch is set to true check if the cache contains a
258
+ // chain and possibly request extended details to get window
259
+ // name
260
+ // to select cached chain.
261
+ if (chain .isEmpty () && isPreservePartialTarget (
262
+ navigationState .getNavigationTarget (),
263
+ routeLayoutTypes )) {
264
+ UI ui = event .getUI ();
265
+ if (ui .getInternals ().getExtendedClientDetails () == null ) {
266
+ PreservedComponentCache cache = ui .getSession ()
267
+ .getAttribute (PreservedComponentCache .class );
268
+ if (cache != null && !cache .isEmpty ()) {
269
+ // As there is a cached chain we get the client
270
+ // details
271
+ // to get the window name so we can determine if the
272
+ // cache contains a chain for us to use.
273
+ ui .getPage ().retrieveExtendedClientDetails (
274
+ details -> handle (event ));
275
+ return true ;
276
+ }
277
+ } else {
278
+ Optional <List <HasElement >> partialChain = getWindowPreservedChain (
279
+ ui .getSession (),
280
+ ui .getInternals ().getExtendedClientDetails ()
281
+ .getWindowName ());
282
+ if (partialChain .isPresent ()) {
283
+ List <HasElement > oldChain = partialChain .get ();
284
+ disconnectElements (oldChain , ui );
285
+
286
+ List <RouterLayout > routerLayouts = new ArrayList <>();
287
+
288
+ for (HasElement hasElement : oldChain ) {
289
+ if (hasElement instanceof RouterLayout ) {
290
+ routerLayouts
291
+ .add ((RouterLayout ) hasElement );
292
+ } else {
293
+ // Remove any non element from their parent
294
+ // to
295
+ // not get old or duplicate route content
296
+ hasElement .getElement ().removeFromParent ();
297
+ }
298
+ }
299
+ ui .getInternals ()
300
+ .setRouterTargetChain (routerLayouts );
301
+ }
302
+ }
303
+ }
304
+ }
305
+ } else {
306
+ // Create an empty chain which gets populated later in
307
+ // `createChainIfEmptyAndExecuteBeforeEnterNavigation`.
308
+ chain .clear ();
309
+
310
+ // Has any preserved components already been created here? If so,
311
+ // we don't want to navigate back to them ever so clear cache for
312
+ // window.
313
+ clearAllPreservedChains (event .getUI ());
314
+ }
315
+ return false ;
316
+ }
317
+
251
318
private void pushHistoryStateIfNeeded (NavigationEvent event , UI ui ) {
252
319
if (event instanceof ErrorNavigationEvent ) {
253
320
ErrorNavigationEvent errorEvent = (ErrorNavigationEvent ) event ;
@@ -819,31 +886,34 @@ private Optional<ArrayList<HasElement>> getPreservedChain(
819
886
if (maybePreserved .isPresent ()) {
820
887
// Re-use preserved chain for this route
821
888
ArrayList <HasElement > chain = maybePreserved .get ();
822
- final HasElement root = chain .get (chain .size () - 1 );
823
- final Component component = (Component ) chain .get (0 );
824
- final Optional <UI > maybePrevUI = component .getUI ();
825
-
826
- if (maybePrevUI .isPresent () && maybePrevUI .get ().equals (ui )) {
827
- return Optional .of (chain );
828
- }
829
-
830
- // Remove the top-level component from the tree
831
- root .getElement ().removeFromTree (false );
832
-
833
- // Transfer all remaining UI child elements (typically dialogs
834
- // and notifications) to the new UI
835
- maybePrevUI .ifPresent (prevUi -> {
836
- ui .getInternals ().moveElementsFrom (prevUi );
837
- prevUi .close ();
838
- });
839
-
889
+ disconnectElements (chain , ui );
840
890
return Optional .of (chain );
841
891
}
842
892
}
843
893
844
894
return Optional .of (new ArrayList <>(0 ));
845
895
}
846
896
897
+ private static void disconnectElements (List <HasElement > chain , UI ui ) {
898
+ final HasElement root = chain .get (chain .size () - 1 );
899
+ final Component component = (Component ) chain .get (0 );
900
+ final Optional <UI > maybePrevUI = component .getUI ();
901
+
902
+ if (maybePrevUI .isPresent () && maybePrevUI .get ().equals (ui )) {
903
+ return ;
904
+ }
905
+
906
+ // Remove the top-level component from the tree
907
+ root .getElement ().removeFromTree (false );
908
+
909
+ // Transfer all remaining UI child elements (typically dialogs
910
+ // and notifications) to the new UI
911
+ maybePrevUI .ifPresent (prevUi -> {
912
+ ui .getInternals ().moveElementsFrom (prevUi );
913
+ prevUi .close ();
914
+ });
915
+ }
916
+
847
917
/**
848
918
* Invoke this method with the chain that needs to be preserved after
849
919
* {@link #handle(NavigationEvent)} method created it.
@@ -932,6 +1002,18 @@ private static boolean isPreserveOnRefreshTarget(
932
1002
.isAnnotationPresent (PreserveOnRefresh .class ));
933
1003
}
934
1004
1005
+ private static boolean isPreservePartialTarget (
1006
+ Class <? extends Component > routeTargetType ,
1007
+ List <Class <? extends RouterLayout >> routeLayoutTypes ) {
1008
+ return (routeTargetType .isAnnotationPresent (PreserveOnRefresh .class )
1009
+ && routeTargetType .getAnnotation (PreserveOnRefresh .class )
1010
+ .partialMatch ())
1011
+ || routeLayoutTypes .stream ().anyMatch (layoutType -> layoutType
1012
+ .isAnnotationPresent (PreserveOnRefresh .class )
1013
+ && layoutType .getAnnotation (PreserveOnRefresh .class )
1014
+ .partialMatch ());
1015
+ }
1016
+
935
1017
// maps window.name to (location, chain)
936
1018
private static class PreservedComponentCache
937
1019
extends HashMap <String , Pair <String , ArrayList <HasElement >>> {
@@ -958,9 +1040,27 @@ static Optional<ArrayList<HasElement>> getPreservedChain(
958
1040
if (cache != null && cache .containsKey (windowName ) && cache
959
1041
.get (windowName ).getFirst ().equals (location .getPath ())) {
960
1042
return Optional .of (cache .get (windowName ).getSecond ());
961
- } else {
962
- return Optional .empty ();
963
1043
}
1044
+ return Optional .empty ();
1045
+ }
1046
+
1047
+ /**
1048
+ * Get a preserved chain by window name only ignoring location path.
1049
+ *
1050
+ * @param session
1051
+ * current session
1052
+ * @param windowName
1053
+ * window name to get cached view stack for
1054
+ * @return view stack cache if available for window name
1055
+ */
1056
+ static Optional <List <HasElement >> getWindowPreservedChain (
1057
+ VaadinSession session , String windowName ) {
1058
+ final PreservedComponentCache cache = session
1059
+ .getAttribute (PreservedComponentCache .class );
1060
+ if (cache != null && cache .containsKey (windowName )) {
1061
+ return Optional .of (cache .get (windowName ).getSecond ());
1062
+ }
1063
+ return Optional .empty ();
964
1064
}
965
1065
966
1066
static void setPreservedChain (VaadinSession session , String windowName ,
0 commit comments