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;
17  
18  import java.util.HashMap;
19  import java.util.LinkedHashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Map.Entry;
23  
24  import org.kuali.student.common.assembly.data.QueryPath;
25  import org.kuali.student.common.ui.client.application.Application;
26  import org.kuali.student.common.ui.client.configurable.mvc.layouts.MenuSectionController;
27  import org.kuali.student.common.ui.client.configurable.mvc.layouts.TabMenuController;
28  import org.kuali.student.common.ui.client.configurable.mvc.layouts.ViewLayoutController;
29  import org.kuali.student.common.ui.client.configurable.mvc.sections.Section;
30  import org.kuali.student.common.ui.client.configurable.mvc.views.SectionView;
31  import org.kuali.student.common.ui.client.event.ActionEvent;
32  import org.kuali.student.common.ui.client.event.SaveActionEvent;
33  import org.kuali.student.common.ui.client.event.SectionUpdateEvent;
34  import org.kuali.student.common.ui.client.event.SectionUpdateHandler;
35  import org.kuali.student.common.ui.client.event.ValidateRequestEvent;
36  import org.kuali.student.common.ui.client.event.ValidateRequestHandler;
37  import org.kuali.student.common.ui.client.mvc.ActionCompleteCallback;
38  import org.kuali.student.common.ui.client.mvc.Callback;
39  import org.kuali.student.common.ui.client.mvc.Controller;
40  import org.kuali.student.common.ui.client.mvc.DataModel;
41  import org.kuali.student.common.ui.client.mvc.ModelRequestCallback;
42  import org.kuali.student.common.ui.client.mvc.View;
43  import org.kuali.student.common.ui.client.mvc.history.HistoryManager;
44  import org.kuali.student.common.ui.client.widgets.KSButton;
45  import org.kuali.student.common.ui.client.widgets.KSLightBox;
46  import org.kuali.student.common.ui.client.widgets.field.layout.element.FieldElement;
47  import org.kuali.student.common.validation.dto.ValidationResultInfo;
48  import org.kuali.student.common.validation.dto.ValidationResultInfo.ErrorLevel;
49  
50  import com.google.gwt.core.client.GWT;
51  import com.google.gwt.event.dom.client.ClickEvent;
52  import com.google.gwt.event.dom.client.ClickHandler;
53  import com.google.gwt.user.client.ui.FlowPanel;
54  import com.google.gwt.user.client.ui.Widget;
55  
56  /**
57   * The LayoutController is a central piece of the UIF.  This controller is also itself a view.
58   * As such, LayoutControllers can also have other LayoutControllers as their views.  
59   * 
60   * @see Controller
61   * @author Kuali Student Team
62   *
63   */
64  public abstract class LayoutController extends Controller implements ViewLayoutController, View {
65  
66  	protected Map<Enum<?>, View> viewMap = new LinkedHashMap<Enum<?>, View>();
67  	protected Map<String, Enum<?>> viewEnumMap = new HashMap<String, Enum<?>>();
68  	protected Enum<?> defaultView;
69  	
70  	protected String name;
71  	protected Enum<?> viewType;
72  
73      protected View startPopupView;
74      protected KSLightBox startViewWindow;
75  	
76      /**
77       * Constructor
78       * Sets up event handlers fro section update events and validation request events.
79       */
80      public LayoutController(){
81          super();
82          //Global section update Event handling
83  		addApplicationEventHandler(SectionUpdateEvent.TYPE, new SectionUpdateHandler(){
84  
85  			@Override
86  			public void onSectionUpdate(final SectionUpdateEvent event) {
87  				LayoutController.this.requestModel(new ModelRequestCallback<DataModel>(){
88  
89  					@Override
90  					public void onRequestFail(Throwable cause) {
91  						GWT.log("Unable to retrieve model for section update", cause);
92  					}
93  
94  					@Override
95  					public void onModelReady(DataModel model) {
96  						event.getSection().updateModel(model);
97  						event.getSection().updateWidgetData(model);
98  						
99  					}
100 				});
101 				
102 			}
103 		});
104 		//Global validation Event handling
105         addApplicationEventHandler(ValidateRequestEvent.TYPE, new ValidateRequestHandler() {
106 
107             @Override
108             public void onValidateRequest(final ValidateRequestEvent event) {
109             	FieldDescriptor originatingField = event.getFieldDescriptor();
110             	String modelId = null;
111             	if (originatingField != null) {
112             		modelId = originatingField.getModelId();
113             	}
114             	if (modelId == null) {
115             		requestModel(new ModelRequestCallback<DataModel>() {
116             			@Override
117             			public void onModelReady(DataModel model) {
118             				validate(model, event);
119             			}
120 
121             			@Override
122             			public void onRequestFail(Throwable cause) {
123             				GWT.log("Unable to retrieve model for validation", cause);
124             			}
125 
126             		});
127             	} else {
128             		requestModel(modelId, new ModelRequestCallback<DataModel>() {
129             			@Override
130             			public void onModelReady(DataModel model) {
131             				validate(model, event);
132             			}
133 
134             			@Override
135             			public void onRequestFail(Throwable cause) {
136             				GWT.log("Unable to retrieve model for validation", cause);
137             			}
138 
139             		});
140             	}
141             }
142 
143         });
144     }
145     
146     private void validate(DataModel model, final ValidateRequestEvent event) {
147         if(event.validateSingleField()){
148             model.validateField(event.getFieldDescriptor(), new Callback<List<ValidationResultInfo>>() {
149                 @Override
150                 public void exec(List<ValidationResultInfo> result) {
151                     if(event.getFieldDescriptor() != null) {
152                         // We dont need to traverse since it is single field, so don't do isValid call here
153                         // instead add the error messages directly
154                         FieldElement element = event.getFieldDescriptor().getFieldElement();
155                         Widget w = event.getFieldDescriptor().getFieldWidget();
156                         if(element != null) {
157                             element.clearValidationErrors();
158 
159                             if ((w instanceof CanProcessValidationResults) && ((CanProcessValidationResults) w).doesOnTheFlyValidation()) {
160                                 ((CanProcessValidationResults) w).Validate(event, result);
161                             } else {
162                                 for(int i = 0; i < result.size(); i++) {
163                                     ValidationResultInfo vr = result.get(i);
164                                     if (vr.getElement().equals(event.getFieldDescriptor().getFieldKey()) && event.getFieldDescriptor().hasHadFocus()) {
165                                         element.processValidationResult(vr);
166                                     }
167                                 }
168                             }
169                         }
170                     }
171 
172                 }
173             });
174         } else {
175             model.validate(new Callback<List<ValidationResultInfo>>() {
176                 @Override
177                 public void exec(List<ValidationResultInfo> result) {
178                     isValid(result, false, true);
179                 }
180             });
181         }
182     }
183     
184     /**
185      * Check to see if the list of validation results have an error.
186      * @param list
187      * @return
188      */
189     public ErrorLevel checkForErrors(List<ValidationResultInfo> list){
190 		ErrorLevel errorLevel = ErrorLevel.OK;
191 		
192 		for(ValidationResultInfo vr: list){
193 			if(vr.getErrorLevel().getLevel() > errorLevel.getLevel()){
194 				errorLevel = vr.getErrorLevel();
195 			}
196 			if(errorLevel.equals(ErrorLevel.ERROR)){
197 				break;
198 			}
199 		}
200     	
201     	return errorLevel;
202     	
203     }
204     
205     /**
206      * Finds the first parent LayoutController of this LayoutController, returns null if this
207      * is the top level LayoutController.
208      * @param w
209      * @return
210      */
211     public static LayoutController findParentLayout(Widget w){
212         LayoutController result = null;
213         while (true) {
214             if (w == null) {
215                 break;
216             } else if (w instanceof HasLayoutController) {
217             	result = ((HasLayoutController)w).getLayoutController();
218             	if (result != null) {
219             		break;
220             	}
221             } else if (w instanceof LayoutController) {
222                 result = (LayoutController) w;
223                 break;
224             }
225             w = w.getParent();
226             
227         }
228         return result;
229     }
230     
231 	/**
232 	 * @see org.kuali.student.common.ui.client.configurable.mvc.layouts.ViewLayoutController#addStartViewPopup(org.kuali.student.common.ui.client.mvc.View)
233 	 */
234 	public void addStartViewPopup(final View view){
235 	    startPopupView = view;
236 	    if(startViewWindow == null){
237 	    	startViewWindow = new KSLightBox();
238 	    }
239 
240 	    FlowPanel panel = new FlowPanel();
241 	    panel.add(view.asWidget());
242 	    KSButton save = new KSButton("Save",new ClickHandler(){
243             public void onClick(ClickEvent event) {
244                 view.updateModel();
245                 SaveActionEvent saveActionEvent = new SaveActionEvent(true);
246 
247                 saveActionEvent.setActionCompleteCallback(new ActionCompleteCallback(){
248                     public void onActionComplete(ActionEvent action) {
249                         startViewWindow.hide();
250                     }
251                 });
252                 
253 
254                 fireApplicationEvent(saveActionEvent);
255             }
256 	    });
257 	    startViewWindow.addButton(save);
258 	    
259 	    KSButton cancel = new KSButton("Cancel", new ClickHandler(){
260             public void onClick(ClickEvent event) {
261                 startViewWindow.hide();
262             }
263 	    });
264 	    startViewWindow.addButton(cancel);
265 
266 	    if(view instanceof SectionView){
267 	    	((SectionView) view).setController(this);
268 	    }
269 	    startViewWindow.setWidget(panel);
270 	}
271 	
272     /**
273      * @return true if the start popup is showing
274      */
275     public boolean isStartViewShowing(){
276         if(startViewWindow == null){
277             return false;
278         }
279     	return startViewWindow.isShowing();
280     }
281 
282     public View getStartPopupView(){
283         return startPopupView;
284     }
285     
286     public void showStartPopup(final Callback<Boolean> onReadyCallback){
287         startPopupView.beforeShow(new Callback<Boolean>() {
288 			@Override
289 			public void exec(Boolean result) {
290 				if (result) {
291 					startViewWindow.show();
292 				}
293 				onReadyCallback.exec(result);
294 			}
295         });
296     }
297     
298     public KSLightBox getStartPopup(){
299         return startViewWindow;
300     }
301 
302 
303     /*New methods*/
304 	
305 	/**
306 	 * @see org.kuali.student.common.ui.client.configurable.mvc.layouts.ViewLayoutController#addView(org.kuali.student.common.ui.client.mvc.View)
307 	 */
308 	public void addView(View view){
309 		viewMap.put(view.getViewEnum(), view);
310 		viewEnumMap.put(view.getViewEnum().toString(), view.getViewEnum());
311 		if(view instanceof SectionView){
312 			((SectionView) view).setController(this);
313 		}
314 		else if(view instanceof ToolView){
315 			((ToolView) view).setController(this);
316 		}
317 	}
318 	
319 	/**
320 	 * @see org.kuali.student.common.ui.client.configurable.mvc.layouts.ViewLayoutController#setDefaultView(java.lang.Enum)
321 	 */
322 	public <V extends Enum<?>> void setDefaultView(V viewType){
323 		this.defaultView = viewType;
324 	}
325 	
326 	public Enum<?> getDefaultView(){
327 		return this.defaultView;
328 	}
329 	
330 	/**
331 	 * @see org.kuali.student.common.ui.client.mvc.View#updateModel()
332 	 */
333 	public abstract void updateModel();
334 	
335 	/**
336 	 * Update the model with a single views information
337 	 * @param viewType
338 	 */
339 	public void updateModelFromView(Enum<?> viewType){
340 		View v = viewMap.get(viewType);
341 		if(v != null){
342 			v.updateModel();
343 		}
344 	}
345 	
346 	/**
347 	 * Update a the model from the view that is currently being shown by this controller
348 	 */
349 	public void updateModelFromCurrentView(){
350         if(this.getCurrentView() != null){
351 		    this.getCurrentView().updateModel();
352         }
353 	}
354 
355 	@Override
356 	public <V extends Enum<?>> void getView(V viewType, Callback<View> callback) {
357 		callback.exec(viewMap.get(viewType));
358 	}
359 
360 	@Override
361 	public Enum<?> getViewEnumValue(String enumValue) {
362 		return viewEnumMap.get(enumValue);
363 	}
364 
365 	@Override
366 	public void showDefaultView(final Callback<Boolean> onReadyCallback) {
367 		HistoryManager.setLogNavigationHistory(false);
368 		//turn of history support for default showing until view is ready
369 		if(defaultView != null){
370 			showView(defaultView, onReadyCallback);
371 		}
372 		else if(!viewMap.isEmpty()){		
373 			if(defaultView == null){
374 				showView(viewMap.entrySet().iterator().next().getKey(), onReadyCallback);
375 			}	
376 		}
377 		
378 	}
379 	
380 	/**
381 	 * Show the view that was the first one added and will likely be the first one in a layout's menu, for
382 	 * example.  Note that this is different than show default view.
383 	 * 
384 	 * @param onReadyCallback
385 	 */
386 	public void showFirstView(Callback<Boolean> onReadyCallback){
387 		HistoryManager.setLogNavigationHistory(false);
388 		if(!viewMap.isEmpty()){	
389 			showView(viewMap.entrySet().iterator().next().getKey(), onReadyCallback);
390 		}
391 		else{
392 			showDefaultView(onReadyCallback);
393 		}
394 	}
395 	
396 	/**
397  	 * Check to see if current/all section(s) is valid (ie. does not contain any errors) - also displays
398  	 * them in the ui if possible
399  	 *
400 	 * @param validationResults List of validation results for the layouts model.
401 	 * @param checkCurrentSectionOnly true if errors should be checked on current section only, false if all sections should be checked
402 	 * @return true if the specified sections (all or current) has any validation errors
403 	 */
404 	public boolean isValid(List<ValidationResultInfo> validationResults, boolean checkCurrentSectionOnly){
405 		return isValid(validationResults, checkCurrentSectionOnly, true);
406 	}
407 	
408 	/**
409 	 * @see LayoutController#isValid(List, boolean)
410 	 * @param validationResults
411 	 * @param checkCurrentSectionOnly
412 	 * @param allFields
413 	 * @return
414 	 */
415 	public boolean isValid(List<ValidationResultInfo> validationResults, boolean checkCurrentSectionOnly, boolean allFields){
416 		
417 		boolean isValid = true;
418 		
419 		clearAllWarnings();
420 		
421 		if (checkCurrentSectionOnly){
422 			//Check for validation errors on the currently displayed section only
423 	    	View v = getCurrentView();
424 	        if(v instanceof Section){
425 	        	isValid = isValid(validationResults, (Section)v, allFields);
426 	    	}
427 	     	if(this.isStartViewShowing()){
428 	     		if(startPopupView instanceof Section){
429 	     			isValid = isValid(validationResults, ((Section) startPopupView), allFields) && isValid;
430 	     		}
431 	     	}
432 		} else {
433 			//Check for validation errors on all sections
434 			String errorSections = "";
435 			StringBuilder errorSectionsbuffer = new StringBuilder();
436 			errorSectionsbuffer.append(errorSections);
437 			for (Entry<Enum<?>, View> entry:viewMap.entrySet()) {
438 				View v = entry.getValue();
439 				if (v instanceof Section){
440 					if (!isValid(validationResults, (Section)v, allFields)){
441 						isValid = false;
442 						errorSectionsbuffer.append(((SectionView)v).getName() + ", ");
443 					}
444 				}
445 			}
446 	     	if(this.isStartViewShowing()){
447 	     		if(startPopupView instanceof Section){
448 	     			isValid = isValid(validationResults, ((Section) startPopupView), allFields) && isValid;
449 	     		}
450 	     	}
451 			errorSections = errorSectionsbuffer.toString();
452 			if (!errorSections.isEmpty()){
453 				errorSections = errorSections.substring(0, errorSections.length()-2);
454 				//container.addMessage("Following section(s) has errors & must be corrected: " + errorSections);
455 			}
456 		}
457 
458 		return isValid;
459 	}
460 
461 	private boolean isValid(List<ValidationResultInfo> validationResults, Section section, boolean allFields){		
462 		ErrorLevel status;
463 		if(allFields){
464 			section.setFieldHasHadFocusFlags(true);
465 			status = section.processValidationResults(validationResults);
466 		}
467 		else{
468 			status = section.processValidationResults(validationResults, false);
469 		}
470 
471 		return (status != ErrorLevel.ERROR);
472 	}
473 	
474 	/**
475 	 * This clears all warnings that currently displayed on all fields and sections.
476 	 */
477 	protected void clearAllWarnings(){
478 		for (Entry<Enum<?>, View> entry:viewMap.entrySet()) {
479 			View v = entry.getValue();
480 			if (v instanceof Section){
481 				((Section)v).clearValidationWarnings();
482 			}
483 		}
484 	}
485 	
486 	/**
487 	 * This particular implementation of beforeViewChange checks to see if all its view contains a Controller
488 	 * and if it does checks with that controller to see if it is ok to change the view.  OkToChange callback
489 	 * will be exec with true if the view is allowed to be changed at this time.  This method can be overriden
490 	 * to provide additional functionality to stop a view from being changed when there is some additional
491 	 * processing that needs to occur in the ui before the view changes.
492 	 * 
493 	 * @see org.kuali.student.common.ui.client.mvc.Controller#beforeViewChange(java.lang.Enum, org.kuali.student.common.ui.client.mvc.Callback)
494 	 */
495 	@Override
496 	public void beforeViewChange(Enum<?> viewChangingTo, Callback<Boolean> okToChange) {
497 	    
498 	    if(this.getCurrentView() instanceof Controller){
499 			((Controller)this.getCurrentView()).beforeViewChange(viewChangingTo, okToChange);
500 	    }
501 		else{
502 			okToChange.exec(true);
503 		}
504 	    
505 	    this.showExport(isExportButtonActive());
506 	}
507 
508 	/**
509 	 * Shows warnings stored to the application context
510 	 * (i.e: dark-yellow highlighting of conflicts during review of a Course Proposal)
511 	 */
512 	protected void showWarnings(){		
513 		if (!Application.getApplicationContext().getValidationWarnings().isEmpty()){
514 			isValid(Application.getApplicationContext().getValidationWarnings(), true);
515     	}				
516 	}
517 	
518 	@Override
519 	public Widget asWidget() {
520 		return this;
521 	}
522 
523 	@Override
524 	public boolean beforeHide() {
525 		return true;
526 	}
527 
528 	/**
529 	 * Default implementation does nothing on before show.  Override to do other things before THIS view is
530 	 * shown.
531 	 * @see org.kuali.student.common.ui.client.mvc.View#beforeShow(org.kuali.student.common.ui.client.mvc.Callback)
532 	 */
533 	@Override
534 	public void beforeShow(Callback<Boolean> onReadyCallback) {
535 		onReadyCallback.exec(true);
536 	}
537 
538 	@Override
539 	public Controller getController() {
540 		return parentController;
541 	}
542 
543 	@Override
544 	public String getName() {
545 		if(name == null && viewType != null){
546 			return viewType.toString();
547 		}
548 		else{
549 			return name;
550 		}
551 	}
552 
553 	@Override
554 	public Enum<?> getViewEnum() {
555 		return viewType;
556 	}
557 	
558 	public void setViewEnum(Enum<?> viewType){
559 		this.viewType= viewType;
560 	}
561 	
562 	/**
563 	 * Sets the name of this LayoutController.  This name is used in the breadcrumb and window's title.
564 	 * Setting the name to the empty string will omit the breadcrumb - this is sometimes desired.
565 	 * @param name
566 	 */
567 	public void setName(String name){
568 		this.name = name;
569 	}
570 	
571 	public void setController(Controller controller){
572 		parentController = controller;
573 	}
574 	
575 	@Override
576 	public void collectBreadcrumbNames(List<String> names) {
577 		names.add(this.getName());
578 		if(this.getCurrentView() != null){
579 			this.getCurrentView().collectBreadcrumbNames(names);
580 		}
581 	}
582 	
583 	@Override
584 	public void clear() {
585 		
586 	}
587 
588 	public boolean isExportButtonActive() {
589 	    return false;
590 	}
591 
592 	@Override
593 	public void showExport(boolean show) {
594 		// TODO Auto-generated method stub
595 		
596 	}
597 }