Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
HistoryManager |
|
| 3.357142857142857;3.357 | ||||
HistoryManager$1 |
|
| 3.357142857142857;3.357 | ||||
HistoryManager$NavigationEventMonitor |
|
| 3.357142857142857;3.357 | ||||
HistoryManager$NavigationEventMonitor$1 |
|
| 3.357142857142857;3.357 |
1 | /** | |
2 | * Copyright 2010 The Kuali Foundation Licensed under the | |
3 | * Educational Community License, Version 2.0 (the "License"); you may | |
4 | * not use this file except in compliance with the License. You may | |
5 | * obtain a copy of the License at | |
6 | * | |
7 | * http://www.osedu.org/licenses/ECL-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, | |
10 | * software distributed under the License is distributed on an "AS IS" | |
11 | * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | |
12 | * or implied. See the License for the specific language governing | |
13 | * permissions and limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.kuali.student.common.ui.client.mvc.history; | |
17 | ||
18 | import java.util.HashMap; | |
19 | import java.util.Iterator; | |
20 | import java.util.Map; | |
21 | ||
22 | import org.kuali.student.common.ui.client.application.ViewContext; | |
23 | import org.kuali.student.common.ui.client.mvc.Controller; | |
24 | import org.kuali.student.common.ui.client.mvc.breadcrumb.BreadcrumbManager; | |
25 | ||
26 | import com.google.gwt.event.logical.shared.ValueChangeEvent; | |
27 | import com.google.gwt.event.logical.shared.ValueChangeHandler; | |
28 | import com.google.gwt.user.client.DOM; | |
29 | import com.google.gwt.user.client.Element; | |
30 | import com.google.gwt.user.client.History; | |
31 | import com.google.gwt.user.client.Timer; | |
32 | import com.google.gwt.user.client.Window; | |
33 | import com.google.gwt.user.client.ui.Hidden; | |
34 | ||
35 | /** | |
36 | * The HistoryManager is responsible for handling history events and navigating within the application. Leverages | |
37 | * GWT history class. | |
38 | * <br><br> | |
39 | * History tokens which appear in the url follow this format: | |
40 | * <br>/HOME/CURRICULUM_HOME/VIEW_COURSE/VIEW&docId=<id>&idType=<type>/BRIEF | |
41 | * <br>In this example, the location is app -> home controller -> curriculum home controller -> view course controller -> | |
42 | * view controller with docId and idType specified for use in its view context - > brief view | |
43 | * | |
44 | * | |
45 | * @author Kuali Student Team | |
46 | * @see History | |
47 | */ | |
48 | /** | |
49 | * @author Kuali Student Team | |
50 | * | |
51 | */ | |
52 | 0 | public class HistoryManager { |
53 | 0 | private static final NavigationEventMonitor monitor = new NavigationEventMonitor(); |
54 | 0 | public static String VIEW_ATR = "view"; |
55 | private static Controller root; | |
56 | 0 | private static boolean logNavigationHistory = true; |
57 | private static Locations locations; | |
58 | ||
59 | /** | |
60 | * Binds a controller to this HistoryManager that specifies the top level controller for the application. | |
61 | * The locations passed in are used for determining navigable locations. | |
62 | * | |
63 | * @param controller | |
64 | * @param views | |
65 | */ | |
66 | public static void bind(Controller controller, Locations views) { | |
67 | 0 | locations = views; |
68 | 0 | root = controller; |
69 | 0 | root.addApplicationEventHandler(NavigationEvent.TYPE, monitor); |
70 | 0 | History.addValueChangeHandler(new ValueChangeHandler<String>() { |
71 | ||
72 | @Override | |
73 | public void onValueChange(ValueChangeEvent<String> event) { | |
74 | 0 | String token = event.getValue(); |
75 | 0 | if (token != null) { |
76 | 0 | root.onHistoryEvent(token); |
77 | } | |
78 | 0 | } |
79 | ||
80 | }); | |
81 | 0 | } |
82 | ||
83 | public static String[] splitHistoryStack(String historyStack){ | |
84 | 0 | return historyStack.split("/"); |
85 | } | |
86 | ||
87 | /** | |
88 | * Gets the property map from the history token passed in. Properties specified by | |
89 | * "&propertyName=propertyValue" in the url token. | |
90 | * | |
91 | * @param token | |
92 | * @return | |
93 | */ | |
94 | public static Map<String, String> getTokenMap(String token){ | |
95 | 0 | Map<String, String> pairs = new HashMap<String, String>(); |
96 | 0 | String[] arr = token.split("&"); |
97 | 0 | for (String s : arr) { |
98 | 0 | if(s.contains("=")){ |
99 | 0 | String[] tmp = s.split("="); |
100 | 0 | if(tmp.length == 2){ |
101 | 0 | pairs.put(tmp[0], tmp[1]); |
102 | } | |
103 | 0 | } |
104 | else{ | |
105 | //view name do not have = sign required; for better readability | |
106 | //putting a token without = sign as the view key | |
107 | 0 | pairs.put("view", s); |
108 | } | |
109 | } | |
110 | 0 | return pairs; |
111 | } | |
112 | ||
113 | /** | |
114 | * Gets the next piece of the history token to be interpreted split on "/" | |
115 | * | |
116 | * @param historyStack | |
117 | * @return | |
118 | */ | |
119 | public static String nextHistoryStack(String historyStack){ | |
120 | 0 | String[] arr= historyStack.split("/", 2); |
121 | 0 | if(arr.length == 2){ |
122 | 0 | return arr[1]; |
123 | } | |
124 | else{ | |
125 | 0 | return ""; |
126 | } | |
127 | } | |
128 | ||
129 | /** | |
130 | * Reads the url history token and navigates to the appropriate location. History following the | |
131 | * "#" takes precedence. Url can also be formatted in a parameter format following the "?" symbol using | |
132 | * the "view" parameter for the view path to navigate to, but must exist in Locations passed into this | |
133 | * HistoryManager. | |
134 | */ | |
135 | public static void processWindowLocation(){ | |
136 | 0 | boolean navigateSuccess = false; |
137 | 0 | Element loc = DOM.getElementById("locationHash"); |
138 | //String value = loc.getValue().trim(); | |
139 | 0 | String value = loc.getAttribute("value"); |
140 | 0 | if(value != null && !value.equals("") && value.startsWith("/")){ |
141 | 0 | navigate(value); |
142 | } | |
143 | 0 | else if(Window.Location.getQueryString() != null && |
144 | !Window.Location.getQueryString().isEmpty()){ | |
145 | 0 | String view = Window.Location.getParameter(VIEW_ATR); |
146 | 0 | String docId = Window.Location.getParameter(ViewContext.ID_ATR); |
147 | 0 | String idType = Window.Location.getParameter(ViewContext.ID_TYPE_ATR); |
148 | 0 | if(view != null && docId != null && idType != null){ |
149 | 0 | String path = locations.getLocation(view); |
150 | 0 | if(path != null){ |
151 | 0 | ViewContext context = new ViewContext(); |
152 | 0 | context.setIdType(idType); |
153 | 0 | context.setId(docId); |
154 | 0 | navigate(path, context); |
155 | 0 | navigateSuccess = true; |
156 | } | |
157 | 0 | }else if(view != null && docId != null){ |
158 | 0 | String path = locations.getLocation(view); |
159 | 0 | if(path != null){ |
160 | 0 | ViewContext context = new ViewContext(); |
161 | 0 | context.setId(docId); |
162 | 0 | navigate(path, context); |
163 | 0 | navigateSuccess = true; |
164 | } | |
165 | 0 | } |
166 | 0 | else if(view != null){ |
167 | 0 | String path = locations.getLocation(view); |
168 | 0 | navigate(path); |
169 | 0 | navigateSuccess = true; |
170 | } | |
171 | } | |
172 | 0 | if(!navigateSuccess){ |
173 | 0 | navigate(Window.Location.getHash().trim()); |
174 | } | |
175 | 0 | } |
176 | ||
177 | /** | |
178 | * Navigate to the path specified including any parameters needed by the views. Paths must begin | |
179 | * with / and parameters following view enum names within that path in the format "&name=value" | |
180 | * @param path | |
181 | */ | |
182 | public static void navigate(String path){ | |
183 | 0 | if(path != null && !path.isEmpty() && path.startsWith("/")){ |
184 | 0 | logNavigationHistory = false; |
185 | 0 | root.onHistoryEvent(path); |
186 | 0 | logHistoryChange(); |
187 | 0 | logNavigationHistory = true; |
188 | } | |
189 | 0 | } |
190 | ||
191 | /** | |
192 | * Collects the current history stack based on user location in the app | |
193 | * | |
194 | * @return | |
195 | */ | |
196 | public static String collectHistoryStack() { | |
197 | 0 | String result = root.collectHistory(""); |
198 | 0 | if(result == null){ |
199 | 0 | result = ""; |
200 | } | |
201 | 0 | return result; |
202 | } | |
203 | ||
204 | /** | |
205 | * Logs the current location to the history stack and updates the breadcrumb manager as appropriate | |
206 | * | |
207 | */ | |
208 | public static void logHistoryChange() { | |
209 | 0 | String historyStack = collectHistoryStack(); |
210 | 0 | if(historyStack.endsWith("/")){ |
211 | 0 | historyStack = historyStack.substring(0, historyStack.length()-1); |
212 | } | |
213 | 0 | String currentToken = History.getToken(); |
214 | 0 | if(!currentToken.equals(historyStack)){ |
215 | 0 | History.newItem(historyStack, false); |
216 | } | |
217 | 0 | BreadcrumbManager.updateLinks(historyStack); |
218 | 0 | } |
219 | ||
220 | 0 | private static class NavigationEventMonitor implements NavigationEventHandler{ |
221 | private static final int EVENT_DELAY = 100; | |
222 | 0 | private long lastEvent = -1; |
223 | ||
224 | 0 | private final Timer timer = new Timer() { |
225 | ||
226 | @Override | |
227 | public void run() { | |
228 | 0 | if (lastEvent < (System.currentTimeMillis()-EVENT_DELAY)) { |
229 | 0 | this.cancel(); |
230 | 0 | lastEvent = -1; |
231 | 0 | logHistoryChange(); |
232 | } | |
233 | 0 | } |
234 | ||
235 | }; | |
236 | ||
237 | @Override | |
238 | public void onNavigationEvent(NavigationEvent event) { | |
239 | 0 | if(logNavigationHistory){ |
240 | 0 | logHistoryChange(); |
241 | } | |
242 | else{ | |
243 | 0 | String historyStack = collectHistoryStack(); |
244 | 0 | BreadcrumbManager.updateLinks(historyStack); |
245 | 0 | HistoryManager.setLogNavigationHistory(true); |
246 | } | |
247 | 0 | } |
248 | } | |
249 | ||
250 | /** | |
251 | * Passing in false, turns off history tracking. History tracking is turned back on by the default | |
252 | * controller implementation after a navigation is completed. | |
253 | * | |
254 | * @param log | |
255 | */ | |
256 | public static void setLogNavigationHistory(boolean log){ | |
257 | 0 | logNavigationHistory = log; |
258 | 0 | } |
259 | ||
260 | /** | |
261 | * Navigate to the path passed in with the context passed in appended to it appropriately. The view context | |
262 | * passed in will be appended to the url in the history token/address bar | |
263 | * | |
264 | * @param path | |
265 | * @param context | |
266 | */ | |
267 | public static void navigate(String path, ViewContext context) { | |
268 | 0 | path = appendContext(path, context); |
269 | 0 | navigate(path); |
270 | ||
271 | 0 | } |
272 | ||
273 | /** | |
274 | * Appends the context to the path passed safely in a format the navigate command will recognize | |
275 | * @param path | |
276 | * @param context | |
277 | * @return | |
278 | */ | |
279 | public static String appendContext(String path, ViewContext context) { | |
280 | 0 | if(context.getId() != null && !context.getId().isEmpty()){ |
281 | 0 | path = path + "&" + ViewContext.ID_ATR + "=" + context.getId(); |
282 | } | |
283 | 0 | if(context.getIdType() != null){ |
284 | 0 | path = path + "&" + ViewContext.ID_TYPE_ATR + "=" + context.getIdType(); |
285 | } | |
286 | 0 | if(!context.getAttributes().isEmpty()){ |
287 | 0 | Map<String,String> attributes = context.getAttributes(); |
288 | 0 | Iterator<String> it = attributes.keySet().iterator(); |
289 | 0 | while(it.hasNext()){ |
290 | 0 | String key = it.next(); |
291 | 0 | String value = attributes.get(key); |
292 | 0 | path = path + "&" + key + "=" + value; |
293 | 0 | } |
294 | } | |
295 | //TODO add the ability for view context to add a variety of additional attributes | |
296 | 0 | return path; |
297 | } | |
298 | } |