View Javadoc

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.configurable.mvc.layouts;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Map.Entry;
23  
24  import org.kuali.student.common.ui.client.configurable.mvc.LayoutController;
25  import org.kuali.student.common.ui.client.configurable.mvc.sections.Section;
26  import org.kuali.student.common.ui.client.configurable.mvc.views.SectionView;
27  import org.kuali.student.common.ui.client.event.ActionEvent;
28  import org.kuali.student.common.ui.client.event.SaveActionEvent;
29  import org.kuali.student.common.ui.client.mvc.ActionCompleteCallback;
30  import org.kuali.student.common.ui.client.mvc.Callback;
31  import org.kuali.student.common.ui.client.mvc.View;
32  import org.kuali.student.common.ui.client.widgets.KSButton;
33  import org.kuali.student.common.ui.client.widgets.containers.KSTitleContainerImpl;
34  import org.kuali.student.common.ui.client.widgets.menus.KSMenuItemData;
35  import org.kuali.student.common.ui.client.widgets.menus.impl.KSBlockMenuImpl;
36  import org.kuali.student.common.ui.client.widgets.tabs.KSTabPanel;
37  import org.kuali.student.common.validation.dto.ValidationResultInfo;
38  import org.kuali.student.common.validation.dto.ValidationResultInfo.ErrorLevel;
39  
40  import com.google.gwt.event.dom.client.ClickEvent;
41  import com.google.gwt.event.dom.client.ClickHandler;
42  import com.google.gwt.user.client.ui.Composite;
43  import com.google.gwt.user.client.ui.FlowPanel;
44  import com.google.gwt.user.client.ui.HorizontalPanel;
45  import com.google.gwt.user.client.ui.SimplePanel;
46  import com.google.gwt.user.client.ui.VerticalPanel;
47  import com.google.gwt.user.client.ui.Widget;
48  
49  public class TabbedSectionLayout extends LayoutController implements ConfigurableLayout{
50  
51  	//FIXME: Better way to manage hierarchy, ordering, and handle to views
52  	private final Map<String, KSMenuItemData> menuHierarchyMap = new HashMap<String, KSMenuItemData>();
53  
54  	private Map<String, TabLayout> tabLayoutMap = new HashMap<String, TabLayout>();
55  
56  	private Map<String, String> sectionNameTabMap = new HashMap<String, String>();
57  
58  	private boolean loaded = false;
59  	private final Map<String, Enum<?>> viewEnums = new HashMap<String, Enum<?>>();
60  
61  	Enum<?> defaultView = null;
62  
63  	private KSTabPanel tabPanel = new KSTabPanel();
64  	private KSTitleContainerImpl container = new KSTitleContainerImpl();
65  
66  	private boolean updateableSection = true;
67  
68  	private class TabLayout extends Composite{
69  		private FlowPanel layout = new FlowPanel();
70  		private SimplePanel content = new SimplePanel();
71  		private FlowPanel contentLayout = new FlowPanel();
72  		private KSBlockMenuImpl menu = new KSBlockMenuImpl();
73  		//KSBasicMenu menu = new KSBasicMenu();
74  		private int currSectionIdx = 0;
75  		protected final ArrayList<View> orderedSectionViews = new ArrayList<View>();
76  		private final HorizontalPanel sectionButtonPanel = new HorizontalPanel();
77  		private final ArrayList<KSMenuItemData> sectionMenuItems = new ArrayList<KSMenuItemData>();
78  		private final List<KSMenuItemData> topLevelMenuItems = new ArrayList<KSMenuItemData>();
79  		private boolean menuAdded = false;
80  		private Enum<?> tabDefaultView = null;
81  
82  		public Enum<?> getTabDefaultView() {
83  			return tabDefaultView;
84  		}
85  
86  		public void setTabDefaultView(Enum<?> tabDefaultView) {
87  			this.tabDefaultView = tabDefaultView;
88  		}
89  
90  		public HorizontalPanel getButtonPanel(){
91  		    return this.sectionButtonPanel;
92  		}
93  
94  		public KSButton getNextButton() {
95  		    return nextButton;
96  		}
97  
98  		private KSButton nextButton = new KSButton("Save & Continue", new ClickHandler(){
99  	        public void onClick(final ClickEvent event) {
100 
101                 final SaveActionEvent saveActionEvent = new SaveActionEvent();
102                 saveActionEvent.setAcknowledgeRequired(false);
103                 saveActionEvent.setActionCompleteCallback(new ActionCompleteCallback(){
104                     public void onActionComplete(ActionEvent action) {
105                         int nextSectionIndex = currSectionIdx + 1;
106                         // FIXME this is not safe for all sorts of reasons, do not call handlers directly like this.
107                         if (nextSectionIndex < sectionMenuItems.size()) {
108                             sectionMenuItems.get(nextSectionIndex).getClickHandler().onClick(event);
109                         }
110                     }
111                 });
112 
113                 fireApplicationEvent(saveActionEvent);
114 	        }
115 	    }
116 		);
117 
118 		public TabLayout(){
119 		    if (updateableSection) {
120 	            sectionButtonPanel.add(nextButton);
121 		    }
122 			menu.setTopLevelItems(topLevelMenuItems);
123 			contentLayout.add(content);
124 			contentLayout.add(sectionButtonPanel);
125 
126 			layout.add(contentLayout);
127 			this.initWidget(layout);
128 		}
129 
130 		public void init(){
131 			contentLayout.setStyleName("ks-page-content");
132 			//menu.addStyleName("KS-TabbedSectionLayout-Menu"); // FIXME keep or delete?
133 			menu.setStyleName("ks-page-sub-navigation-container");
134 			menu.setTopLevelItems(topLevelMenuItems);
135 		}
136 
137 		public void setContent(Widget content){
138 			this.content.setWidget(content);
139 		}
140 
141 		public void addMenuItem(String[] hierarchy, final SectionView section) {
142 			String path = "";
143 			StringBuilder pathBuffer= new StringBuilder();
144 			pathBuffer.append(path);
145 			KSMenuItemData current = null;
146 			for (int i=1; i<hierarchy.length; i++) {
147 						    // For configurable section layout the hierarchy element obtained from XML file might contain
148 			   // null element. In such a case we dont require a menu to be displayed on the screen.
149 			    if(hierarchy[i]==null){
150 			        return;
151 			    }
152 				pathBuffer.append("/");
153 				pathBuffer.append(hierarchy[i]);
154 				KSMenuItemData item = menuHierarchyMap.get(pathBuffer.toString());
155 				if (item == null) {
156 					item = new KSMenuItemData(hierarchy[i]);
157 					if (current == null) {
158 						topLevelMenuItems.add(item);
159 						current = item;
160 					} else {
161 						current.addSubItem(item);
162 						current = item;
163 					}
164 					menuHierarchyMap.put(pathBuffer.toString(), item);
165 				} else {
166 					current = item;
167 				}
168 			}
169 
170 			final KSMenuItemData sectionItem = new KSMenuItemData(section.getName());
171 			current.addSubItem(sectionItem);
172 			sectionMenuItems.add(sectionItem);
173 			orderedSectionViews.add(section);
174 
175 			sectionItem.setClickHandler(new ClickHandler() {
176 				public void onClick(ClickEvent event) {
177 
178 				    int newMenuItemIdx = sectionMenuItems.indexOf(sectionItem);
179 				    if (currSectionIdx != newMenuItemIdx){
180 	                    currSectionIdx = newMenuItemIdx;
181 	                    sectionButtonPanel.setVisible(true);
182 	    			    showView(section.getViewEnum(), NO_OP_CALLBACK);
183 				    }
184 				}
185 			});
186 
187 			if(!menuAdded){
188 				layout.insert(menu, 0);
189 				menuAdded = true;
190 			}
191 
192 			if (tabDefaultView == null){
193 			    tabDefaultView = section.getViewEnum();
194 			}
195 		}
196 
197 		public void renderView(View view) {
198 			content.setWidget((Widget)view);
199 			if(menuAdded){
200 			    if (currSectionIdx == sectionMenuItems.size() - 1){
201 			        nextButton.setVisible(false);
202 			    } else {
203 			        nextButton.setVisible(true);
204 			    }
205 		        currSectionIdx = orderedSectionViews.indexOf(view);
206 		        if(currSectionIdx == -1){
207 		            return;
208 		        }
209 				sectionMenuItems.get(currSectionIdx).setSelected(true);
210 			}
211 			else{
212 				nextButton.setVisible(false);
213 			}
214 		}
215 
216 		public void removeContent() {
217 			content.clear();
218 		}
219 
220 		public void addButton(KSButton button) {
221 			sectionButtonPanel.add(button);
222 		}
223 
224 		public void clear() {
225 	        for (View view:orderedSectionViews){
226 	            view.clear();
227 	        }
228 
229 		}
230 
231 		public void updateModel() {
232             for(View sectionView : orderedSectionViews){
233             	sectionView.updateModel();
234             }
235 
236 		}
237 
238 		public void beforeShow(final Callback<Boolean> onReadyCallback) {
239 			showView(tabDefaultView, onReadyCallback);
240 		}
241 
242 
243 	}
244 
245 	private void init(){
246     	for(TabLayout layout: tabLayoutMap.values()){
247 			layout.init();
248 		}
249 	}
250 
251 	public TabbedSectionLayout(String controllerId){
252 	    super();
253 	    container.setContent(tabPanel);
254 		container.setTitle("New Course Proposal");
255 		super.initWidget(container);
256 	}
257 
258 	public TabbedSectionLayout(String controllerId, KSTitleContainerImpl container){
259 	    super();
260 	    this.container.setContent(tabPanel);
261         this.container.setTitle(container.getTitle());
262         this.container.setStatus(container.getStatus());
263         this.container.setLinkText(container.getLinkText());
264         super.initWidget(this.container);
265 	}
266 
267 
268     public KSTitleContainerImpl getContainer(){
269         return this.container;
270     }
271 
272     public void setContainer(KSTitleContainerImpl container){
273         this.container=container;
274     }
275 
276 	@Override
277 	public <V extends Enum<?>> void getView(V viewType, Callback<View> callback) {
278 		callback.exec(viewMap.get(viewType));
279 	}
280 
281 	@Override
282     public Enum<?> getViewEnumValue(String enumValue) {
283         return viewEnums.get(enumValue);
284     }
285 
286 	public boolean isUpdateableSection() {
287         return updateableSection;
288     }
289 
290     public void setUpdateableSection(boolean isUpdateable) {
291         this.updateableSection = isUpdateable;
292     }
293 
294     @Override
295 	protected void hideView(View view) {
296 		//Does nothing: no need to hide, it view is always replaced in this layout
297 /*		String tabName = sectionNameTabMap.get(view.getName());
298 		TabLayout layout = tabLayoutMap.get(tabName);
299 		layout.removeContent();*/
300 	}
301 
302 	@Override
303 	protected void renderView(View view) {
304 		String tabName = sectionNameTabMap.get(view.getName());
305 		if(!(tabPanel.getSelectedTabKey().equals(tabName))){
306 			tabPanel.selectTab(tabName);
307 		}
308 		TabLayout layout = tabLayoutMap.get(tabName);
309 
310 		layout.renderView(view);
311 		view.getName();
312 	}
313 
314 	@Override
315 	public void showDefaultView(final Callback<Boolean> onReadyCallback) {
316         if (!loaded){
317             init();
318             loaded = true;
319         }
320 
321         super.showDefaultView(onReadyCallback);
322 	}
323 
324 	@Override
325 	public void addSection(String[] hierarchy, final SectionView section) {
326 		viewEnums.put(section.getViewEnum().toString(), section.getViewEnum());
327 		String tabKey = hierarchy[0];
328 
329 		sectionNameTabMap.put(section.getName(), tabKey);
330 		viewMap.put(section.getViewEnum(), section);
331 		section.setController(this);
332 		section.setLayoutController(this);
333 
334 		final TabLayout layout;
335 		if(!(tabPanel.hasTabKey(tabKey))){
336 			layout = new TabLayout();
337 			tabLayoutMap.put(tabKey, layout);
338 			tabPanel.addTab(tabKey, tabKey, layout);
339 			if(section != null){
340 				layout.setTabDefaultView(section.getViewEnum());
341 			}
342 
343 			//Handler for when tab is clicked
344 			tabPanel.addTabCustomCallback(tabKey, new Callback<String>(){
345 
346 				@Override
347 				public void exec(String result) {
348 					layout.beforeShow(NO_OP_CALLBACK);
349 				}
350 
351 			});
352 		}
353 		else{
354 			layout = tabLayoutMap.get(tabKey);
355 		}
356 
357 		if(hierarchy.length > 1){
358 			layout.addMenuItem(hierarchy, section);
359 		}
360 		else{
361 			layout.renderView(section);
362 		}
363 
364 		if (defaultView == null){
365 		    defaultView = section.getViewEnum();
366 		}
367 	}
368 
369 	public void addToolbar(Widget toolbar){
370 		this.container.setToolbar(toolbar);
371 	}
372 
373     public void showStartSection(final Callback<Boolean> onReadyCallback){
374         this.showStartPopup(onReadyCallback);
375     }
376 
377     public SectionView getStartSection(){
378     	if(startPopupView instanceof SectionView){
379     		return (SectionView)startPopupView;
380     	}
381     	else{
382     		return null;
383     	}
384         
385     }
386 
387     @Override
388 	public void addStartSection(final SectionView section){
389 	    this.addStartViewPopup(section);
390 
391 	    HorizontalPanel buttonPanel = new HorizontalPanel();
392 
393 	    VerticalPanel panel = new VerticalPanel();
394 	    panel.add(section);
395 	    buttonPanel.add(new KSButton("Save",new ClickHandler(){
396             public void onClick(ClickEvent event) {
397                 section.updateModel();
398                 SaveActionEvent saveActionEvent = new SaveActionEvent();
399 
400                 saveActionEvent.setActionCompleteCallback(new ActionCompleteCallback(){
401                     public void onActionComplete(ActionEvent action) {
402                         startViewWindow.hide();
403                     }
404                 });
405 
406                 fireApplicationEvent(saveActionEvent);
407             }
408 	    }));
409 	    buttonPanel.add(new KSButton("Cancel", new ClickHandler(){
410             public void onClick(ClickEvent event) {
411                 startViewWindow.hide();
412             }
413 	    }));
414 
415 	    panel.add(buttonPanel);
416         section.setController(this);
417 	    startViewWindow.setWidget(panel);
418 	}
419 
420 	public void addButton(String tabKey, KSButton button){
421 		TabLayout layout = tabLayoutMap.get(tabKey);
422 
423 		if(layout != null){
424 			layout.addButton(button);
425 		}
426 
427 	}
428 
429     public HorizontalPanel getButtonPanel(String tabKey){
430         TabLayout layout = tabLayoutMap.get(tabKey);
431 
432         if(layout != null){
433             return layout.getButtonPanel();
434         }
435         return null;
436     }
437 
438     public KSButton getNextButton(String tabKey) {
439         TabLayout layout = tabLayoutMap.get(tabKey);
440 
441         if (layout != null) {
442             return layout.getNextButton();
443         }
444         return null;
445     }
446 
447     public void clear(){
448     	super.clear();
449     	for(TabLayout layout: tabLayoutMap.values()){
450 			layout.clear();
451 		}
452 
453 
454     }
455 
456     public void updateModel(){
457     	for(TabLayout layout: tabLayoutMap.values()){
458 			layout.updateModel();
459 		}
460     }
461 
462 	/**
463  	 * Check to see if current/all section(s) is valid (ie. does not contain any errors)
464  	 *
465 	 * @param validationResults List of validation results for the layouts model.
466 	 * @param checkCurrentSectionOnly true if errors should be checked on current section only, false if all sections should be checked
467 	 * @return true if the specified sections (all or current) has any validation errors
468 	 */
469 	public boolean isValid(List<ValidationResultInfo> validationResults, boolean checkCurrentSectionOnly){
470 		boolean isValid = true;
471 
472 		if (checkCurrentSectionOnly){
473 			//Check for validation errors on the currently displayed section only
474 	    	if(this.isStartViewShowing()){
475 	    		isValid = isValid(validationResults, getStartSection());
476 	    	} else {
477 	    		View v = getCurrentView();
478 	        	if(v instanceof Section){
479 	        		isValid = isValid(validationResults, (Section)v);
480 	        	}
481 	    	}
482 		} else {
483 			//Check for validation errors on all sections
484 			container.clearMessages();
485 			String errorSections = "";
486 			StringBuilder errorSectionsbuffer = new StringBuilder();
487 			errorSectionsbuffer.append(errorSections);
488 			for (Entry<Enum<?>, View> entry:viewMap.entrySet()) {
489 				View v = entry.getValue();
490 				if (v instanceof Section){
491 					if (!isValid(validationResults, (Section)v)){
492 						isValid = false;
493 						errorSectionsbuffer.append(((SectionView)v).getName() + ", ");
494 //						errorSections += ((SectionView)v).getName() + ", ";
495 					}
496 				}
497 			}
498 			errorSections = errorSectionsbuffer.toString();
499 			if (!errorSections.isEmpty()){
500 				errorSections = errorSections.substring(0, errorSections.length()-2);
501 				container.addMessage("Following section(s) has errors & must be corrected: " + errorSections);
502 			}
503 		}
504 
505 		return isValid;
506 	}
507 
508 	private boolean isValid(List<ValidationResultInfo> validationResults, Section section){
509 		section.setFieldHasHadFocusFlags(true);
510 		ErrorLevel status = section.processValidationResults(validationResults);
511 
512 		return (status != ErrorLevel.ERROR);
513 	}
514 }