001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.ajax; 018 019import java.util.UUID; 020 021import org.apache.wicket.Component; 022import org.apache.wicket.Page; 023import org.apache.wicket.ajax.attributes.AjaxCallListener; 024import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 025import org.apache.wicket.markup.head.IHeaderResponse; 026import org.apache.wicket.markup.head.OnLoadHeaderItem; 027import org.apache.wicket.util.lang.Args; 028import org.apache.wicket.util.string.Strings; 029import org.danekja.java.util.function.serializable.SerializableConsumer; 030 031/** 032 * An Ajax behavior that notifies when a new browser window/tab is opened with url to a page 033 * instance which is already opened in another window/tab in the same user session. 034 * 035 * @since 6.0 036 * @see #onNewWindow(AjaxRequestTarget) 037 */ 038public class AjaxNewWindowNotifyingBehavior extends AbstractDefaultAjaxBehavior 039{ 040 private static final long serialVersionUID = 1L; 041 042 /** 043 * The name of the HTTP request parameter that transports the current page window's name. 044 */ 045 private static final String PARAM_WINDOW_NAME = "windowName"; 046 047 /** 048 * The name of the window the page is bound to. 049 */ 050 private String boundName; 051 052 /** 053 * Returns the window's name. 054 * 055 * @return name of {@code null} if not yet bound to a window 056 */ 057 public String getWindowName() 058 { 059 return boundName; 060 } 061 062 /** 063 * Overridden to add the current window name to the request. 064 */ 065 @Override 066 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 067 { 068 super.updateAjaxAttributes(attributes); 069 070 String parameter = "return {'" + PARAM_WINDOW_NAME + "': window.name}"; 071 attributes.getDynamicExtraParameters().add(parameter); 072 073 if (boundName != null) 074 { 075 // already bound, send request only when changed 076 attributes.getAjaxCallListeners().add(new AjaxCallListener() 077 { 078 @Override 079 public CharSequence getPrecondition(Component component) 080 { 081 return String.format("return (window.name !== '%s');", boundName); 082 } 083 }); 084 } 085 } 086 087 /** 088 * Overridden to initiate a request once the page was rendered. 089 */ 090 @Override 091 public void renderHead(Component component, IHeaderResponse response) 092 { 093 super.renderHead(component, response); 094 095 response.render(OnLoadHeaderItem 096 .forScript("setTimeout(function() {" + getCallbackScript().toString() + "}, 30);")); 097 } 098 099 @Override 100 protected void respond(AjaxRequestTarget target) 101 { 102 String windowName = getComponent().getRequest().getRequestParameters().getParameterValue(PARAM_WINDOW_NAME).toString(); 103 104 if (boundName == null) 105 { 106 // not bound to any window yet 107 108 if (Strings.isEmpty(windowName)) 109 { 110 // create new name 111 windowName = newWindowName(); 112 target.appendJavaScript(String.format("window.name = '%s';", windowName)); 113 } 114 115 // now bound to window 116 boundName = windowName; 117 } 118 else if (boundName.equals(windowName) == false) 119 { 120 onNewWindow(target); 121 } 122 } 123 124 /** 125 * Create a name for a nameless window, default uses a random {@link UUID}. 126 * 127 * @return window name 128 */ 129 protected String newWindowName() 130 { 131 return UUID.randomUUID().toString(); 132 } 133 134 /** 135 * A callback method that is called when a new window/tab is opened for a page instance 136 * which is already opened in another window/tab. 137 * <p> 138 * Default implementation redirects to a new page instance with identical page parameters. 139 * 140 * @param target 141 * the current request handler 142 */ 143 protected void onNewWindow(AjaxRequestTarget target) 144 { 145 Page page = getComponent().getPage(); 146 147 getComponent().setResponsePage(page.getClass(), page.getPageParameters()); 148 } 149 150 /** 151 * Creates an {@link AjaxNewWindowNotifyingBehavior} based on lambda expressions 152 * 153 * @param onNewWindow 154 * the {@code SerializableConsumer} which accepts the {@link AjaxRequestTarget} 155 * @return the {@link AjaxNewWindowNotifyingBehavior} 156 */ 157 public static AjaxNewWindowNotifyingBehavior onNewWindow(SerializableConsumer<AjaxRequestTarget> onNewWindow) 158 { 159 Args.notNull(onNewWindow, "onNewWindow"); 160 161 return new AjaxNewWindowNotifyingBehavior() 162 { 163 private static final long serialVersionUID = 1L; 164 165 @Override 166 protected void onNewWindow(AjaxRequestTarget target) 167 { 168 onNewWindow.accept(target); 169 } 170 }; 171 } 172}