001 /**
002 * Copyright 2010 The Kuali Foundation Licensed under the
003 * Educational Community License, Version 2.0 (the "License"); you may
004 * not use this file except in compliance with the License. You may
005 * obtain a copy of the License at
006 *
007 * http://www.osedu.org/licenses/ECL-2.0
008 *
009 * Unless required by applicable law or agreed to in writing,
010 * software distributed under the License is distributed on an "AS IS"
011 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
012 * or implied. See the License for the specific language governing
013 * permissions and limitations under the License.
014 */
015
016 package org.kuali.student.common.ui.client.configurable.mvc;
017
018 import java.util.HashMap;
019 import java.util.LinkedHashMap;
020 import java.util.List;
021 import java.util.Map;
022 import java.util.Map.Entry;
023
024 import org.kuali.student.common.ui.client.configurable.mvc.layouts.ViewLayoutController;
025 import org.kuali.student.common.ui.client.configurable.mvc.sections.Section;
026 import org.kuali.student.common.ui.client.configurable.mvc.views.SectionView;
027 import org.kuali.student.common.ui.client.event.ActionEvent;
028 import org.kuali.student.common.ui.client.event.SaveActionEvent;
029 import org.kuali.student.common.ui.client.event.SectionUpdateEvent;
030 import org.kuali.student.common.ui.client.event.SectionUpdateHandler;
031 import org.kuali.student.common.ui.client.event.ValidateRequestEvent;
032 import org.kuali.student.common.ui.client.event.ValidateRequestHandler;
033 import org.kuali.student.common.ui.client.mvc.ActionCompleteCallback;
034 import org.kuali.student.common.ui.client.mvc.Callback;
035 import org.kuali.student.common.ui.client.mvc.Controller;
036 import org.kuali.student.common.ui.client.mvc.DataModel;
037 import org.kuali.student.common.ui.client.mvc.ModelRequestCallback;
038 import org.kuali.student.common.ui.client.mvc.View;
039 import org.kuali.student.common.ui.client.mvc.history.HistoryManager;
040 import org.kuali.student.common.ui.client.widgets.KSButton;
041 import org.kuali.student.common.ui.client.widgets.KSLightBox;
042 import org.kuali.student.common.ui.client.widgets.field.layout.element.FieldElement;
043 import org.kuali.student.core.validation.dto.ValidationResultInfo;
044 import org.kuali.student.core.validation.dto.ValidationResultInfo.ErrorLevel;
045
046 import com.google.gwt.core.client.GWT;
047 import com.google.gwt.event.dom.client.ClickEvent;
048 import com.google.gwt.event.dom.client.ClickHandler;
049 import com.google.gwt.user.client.ui.FlowPanel;
050 import com.google.gwt.user.client.ui.Widget;
051
052 /**
053 * The LayoutController is a central piece of the UIF. This controller is also itself a view.
054 * As such, LayoutControllers can also have other LayoutControllers as their views.
055 *
056 * @see Controller
057 * @author Kuali Student Team
058 *
059 */
060 public abstract class LayoutController extends Controller implements ViewLayoutController, View {
061
062 protected Map<Enum<?>, View> viewMap = new LinkedHashMap<Enum<?>, View>();
063 protected Map<String, Enum<?>> viewEnumMap = new HashMap<String, Enum<?>>();
064 protected Enum<?> defaultView;
065
066 protected String name;
067 protected Enum<?> viewType;
068
069 protected View startPopupView;
070 protected KSLightBox startViewWindow;
071
072 /**
073 * Constructor
074 * Sets up event handlers fro section update events and validation request events.
075 * @param controllerId - not used
076 */
077 public LayoutController(String controllerId){
078 super(controllerId);
079 //Global section update Event handling
080 addApplicationEventHandler(SectionUpdateEvent.TYPE, new SectionUpdateHandler(){
081
082 @Override
083 public void onSectionUpdate(final SectionUpdateEvent event) {
084 LayoutController.this.requestModel(new ModelRequestCallback<DataModel>(){
085
086 @Override
087 public void onRequestFail(Throwable cause) {
088 GWT.log("Unable to retrieve model for section update", cause);
089 }
090
091 @Override
092 public void onModelReady(DataModel model) {
093 event.getSection().updateModel(model);
094 event.getSection().updateWidgetData(model);
095
096 }
097 });
098
099 }
100 });
101 //Global validation Event handling
102 addApplicationEventHandler(ValidateRequestEvent.TYPE, new ValidateRequestHandler() {
103
104 @Override
105 public void onValidateRequest(final ValidateRequestEvent event) {
106 FieldDescriptor originatingField = event.getFieldDescriptor();
107 String modelId = null;
108 if (originatingField != null) {
109 modelId = originatingField.getModelId();
110 }
111 if (modelId == null) {
112 requestModel(new ModelRequestCallback<DataModel>() {
113 @Override
114 public void onModelReady(DataModel model) {
115 validate(model, event);
116 }
117
118 @Override
119 public void onRequestFail(Throwable cause) {
120 GWT.log("Unable to retrieve model for validation", cause);
121 }
122
123 });
124 } else {
125 requestModel(modelId, new ModelRequestCallback<DataModel>() {
126 @Override
127 public void onModelReady(DataModel model) {
128 validate(model, event);
129 }
130
131 @Override
132 public void onRequestFail(Throwable cause) {
133 GWT.log("Unable to retrieve model for validation", cause);
134 }
135
136 });
137 }
138 }
139
140 });
141 }
142
143 private void validate(DataModel model, final ValidateRequestEvent event) {
144 if(event.validateSingleField()){
145 model.validateField(event.getFieldDescriptor(), new Callback<List<ValidationResultInfo>>() {
146 @Override
147 public void exec(List<ValidationResultInfo> result) {
148 if(event.getFieldDescriptor() != null){
149 //We dont need to traverse since it is single field, so don't do isValid call here
150 //instead add the error messages directly
151 FieldElement element = event.getFieldDescriptor().getFieldElement();
152 if(element != null){
153 element.clearValidationPanel();
154 for(int i = 0; i < result.size(); i++){
155 ValidationResultInfo vr = result.get(i);
156 if(vr.getElement().equals(event.getFieldDescriptor().getFieldKey())
157 && event.getFieldDescriptor().hasHadFocus()){
158 element.processValidationResult(vr);
159 }
160 }
161 }
162 }
163
164 }
165 });
166 }
167 else{
168 model.validate(new Callback<List<ValidationResultInfo>>() {
169 @Override
170 public void exec(List<ValidationResultInfo> result) {
171 isValid(result, false, true);
172 }
173 });
174 }
175 }
176
177 /**
178 * Check to see if the list of validation results have an error.
179 * @param list
180 * @return
181 */
182 public ErrorLevel checkForErrors(List<ValidationResultInfo> list){
183 ErrorLevel errorLevel = ErrorLevel.OK;
184
185 for(ValidationResultInfo vr: list){
186 if(vr.getErrorLevel().getLevel() > errorLevel.getLevel()){
187 errorLevel = vr.getErrorLevel();
188 }
189 if(errorLevel.equals(ErrorLevel.ERROR)){
190 break;
191 }
192 }
193
194 return errorLevel;
195
196 }
197
198 /**
199 * Finds the first parent LayoutController of this LayoutController, returns null if this
200 * is the top level LayoutController.
201 * @param w
202 * @return
203 */
204 public static LayoutController findParentLayout(Widget w){
205 LayoutController result = null;
206 while (true) {
207 if (w == null) {
208 break;
209 } else if (w instanceof HasLayoutController) {
210 result = ((HasLayoutController)w).getLayoutController();
211 if (result != null) {
212 break;
213 }
214 } else if (w instanceof LayoutController) {
215 result = (LayoutController) w;
216 break;
217 }
218 w = w.getParent();
219
220 }
221 return result;
222 }
223
224 /**
225 * @see org.kuali.student.common.ui.client.configurable.mvc.layouts.ViewLayoutController#addStartViewPopup(org.kuali.student.common.ui.client.mvc.View)
226 */
227 public void addStartViewPopup(final View view){
228 startPopupView = view;
229 if(startViewWindow == null){
230 startViewWindow = new KSLightBox();
231 }
232
233 FlowPanel panel = new FlowPanel();
234 panel.add(view.asWidget());
235 KSButton save = new KSButton("Save",new ClickHandler(){
236 public void onClick(ClickEvent event) {
237 view.updateModel();
238 SaveActionEvent saveActionEvent = new SaveActionEvent(true);
239
240 saveActionEvent.setActionCompleteCallback(new ActionCompleteCallback(){
241 public void onActionComplete(ActionEvent action) {
242 startViewWindow.hide();
243 }
244 });
245
246
247 fireApplicationEvent(saveActionEvent);
248 }
249 });
250 startViewWindow.addButton(save);
251
252 KSButton cancel = new KSButton("Cancel", new ClickHandler(){
253 public void onClick(ClickEvent event) {
254 startViewWindow.hide();
255 }
256 });
257 startViewWindow.addButton(cancel);
258
259 if(view instanceof SectionView){
260 ((SectionView) view).setController(this);
261 }
262 startViewWindow.setWidget(panel);
263 }
264
265 /**
266 * @return true if the start popup is showing
267 */
268 public boolean isStartViewShowing(){
269 if(startViewWindow == null){
270 return false;
271 }
272 return startViewWindow.isShowing();
273 }
274
275 public View getStartPopupView(){
276 return startPopupView;
277 }
278
279 public void showStartPopup(final Callback<Boolean> onReadyCallback){
280 startPopupView.beforeShow(new Callback<Boolean>() {
281 @Override
282 public void exec(Boolean result) {
283 if (result) {
284 startViewWindow.show();
285 }
286 onReadyCallback.exec(result);
287 }
288 });
289 }
290
291 public KSLightBox getStartPopup(){
292 return startViewWindow;
293 }
294
295
296 /*New methods*/
297
298 /**
299 * @see org.kuali.student.common.ui.client.configurable.mvc.layouts.ViewLayoutController#addView(org.kuali.student.common.ui.client.mvc.View)
300 */
301 public void addView(View view){
302 viewMap.put(view.getViewEnum(), view);
303 viewEnumMap.put(view.getViewEnum().toString(), view.getViewEnum());
304 if(view instanceof SectionView){
305 ((SectionView) view).setController(this);
306 }
307 else if(view instanceof ToolView){
308 ((ToolView) view).setController(this);
309 }
310 }
311
312 /**
313 * @see org.kuali.student.common.ui.client.configurable.mvc.layouts.ViewLayoutController#setDefaultView(java.lang.Enum)
314 */
315 public <V extends Enum<?>> void setDefaultView(V viewType){
316 this.defaultView = viewType;
317 }
318
319 public Enum<?> getDefaultView(){
320 return this.defaultView;
321 }
322
323 /**
324 * @see org.kuali.student.common.ui.client.mvc.View#updateModel()
325 */
326 public abstract void updateModel();
327
328 /**
329 * Update the model with a single views information
330 * @param viewType
331 */
332 public void updateModelFromView(Enum<?> viewType){
333 View v = viewMap.get(viewType);
334 if(v != null){
335 v.updateModel();
336 }
337 }
338
339 /**
340 * Update a the model from the view that is currently being shown by this controller
341 */
342 public void updateModelFromCurrentView(){
343 if(this.getCurrentView() != null){
344 this.getCurrentView().updateModel();
345 }
346 }
347
348 @Override
349 public <V extends Enum<?>> void getView(V viewType, Callback<View> callback) {
350 callback.exec(viewMap.get(viewType));
351 }
352
353 @Override
354 public Enum<?> getViewEnumValue(String enumValue) {
355 return viewEnumMap.get(enumValue);
356 }
357
358 @Override
359 public void showDefaultView(final Callback<Boolean> onReadyCallback) {
360 HistoryManager.setLogNavigationHistory(false);
361 //turn of history support for default showing until view is ready
362 if(defaultView != null){
363 showView(defaultView, onReadyCallback);
364 }
365 else if(!viewMap.isEmpty()){
366 if(defaultView == null){
367 showView(viewMap.entrySet().iterator().next().getKey(), onReadyCallback);
368 }
369 }
370
371 }
372
373 /**
374 * Show the view that was the first one added and will likely be the first one in a layout's menu, for
375 * example. Note that this is different than show default view.
376 *
377 * @param onReadyCallback
378 */
379 public void showFirstView(Callback<Boolean> onReadyCallback){
380 HistoryManager.setLogNavigationHistory(false);
381 if(!viewMap.isEmpty()){
382 showView(viewMap.entrySet().iterator().next().getKey(), onReadyCallback);
383 }
384 else{
385 showDefaultView(onReadyCallback);
386 }
387 }
388
389 /**
390 * Check to see if current/all section(s) is valid (ie. does not contain any errors)
391 *
392 * @param validationResults List of validation results for the layouts model.
393 * @param checkCurrentSectionOnly true if errors should be checked on current section only, false if all sections should be checked
394 * @return true if the specified sections (all or current) has any validation errors
395 */
396 public boolean isValid(List<ValidationResultInfo> validationResults, boolean checkCurrentSectionOnly){
397 return isValid(validationResults, checkCurrentSectionOnly, true);
398 }
399
400 /**
401 * @see LayoutController#isValid(List, boolean)
402 * @param validationResults
403 * @param checkCurrentSectionOnly
404 * @param allFields
405 * @return
406 */
407 public boolean isValid(List<ValidationResultInfo> validationResults, boolean checkCurrentSectionOnly, boolean allFields){
408 boolean isValid = true;
409
410 if (checkCurrentSectionOnly){
411 //Check for validation errors on the currently displayed section only
412 View v = getCurrentView();
413 if(v instanceof Section){
414 isValid = isValid(validationResults, (Section)v, allFields);
415 }
416 if(this.isStartViewShowing()){
417 if(startPopupView instanceof Section){
418 isValid = isValid(validationResults, ((Section) startPopupView), allFields) && isValid;
419 }
420 }
421 } else {
422 //Check for validation errors on all sections
423 String errorSections = "";
424 StringBuilder errorSectionsbuffer = new StringBuilder();
425 errorSectionsbuffer.append(errorSections);
426 for (Entry<Enum<?>, View> entry:viewMap.entrySet()) {
427 View v = entry.getValue();
428 if (v instanceof Section){
429 if (!isValid(validationResults, (Section)v, allFields)){
430 isValid = false;
431 errorSectionsbuffer.append(((SectionView)v).getName() + ", ");
432 }
433 }
434 }
435 if(this.isStartViewShowing()){
436 if(startPopupView instanceof Section){
437 isValid = isValid(validationResults, ((Section) startPopupView), allFields) && isValid;
438 }
439 }
440 errorSections = errorSectionsbuffer.toString();
441 if (!errorSections.isEmpty()){
442 errorSections = errorSections.substring(0, errorSections.length()-2);
443 //container.addMessage("Following section(s) has errors & must be corrected: " + errorSections);
444 }
445 }
446
447 return isValid;
448 }
449
450 private boolean isValid(List<ValidationResultInfo> validationResults, Section section, boolean allFields){
451 ErrorLevel status;
452 if(allFields){
453 section.setFieldHasHadFocusFlags(true);
454 status = section.processValidationResults(validationResults);
455 }
456 else{
457 status = section.processValidationResults(validationResults, false);
458 }
459
460 return (status != ErrorLevel.ERROR);
461 }
462
463 /**
464 * This particular implementation of beforeViewChange checks to see if all its view contains a Controller
465 * and if it does checks with that controller to see if it is ok to change the view. OkToChange callback
466 * will be exec with true if the view is allowed to be changed at this time. This method can be overriden
467 * to provide additional functionality to stop a view from being changed when there is some additional
468 * processing that needs to occur in the ui before the view changes.
469 *
470 * @see org.kuali.student.common.ui.client.mvc.Controller#beforeViewChange(java.lang.Enum, org.kuali.student.common.ui.client.mvc.Callback)
471 */
472 @Override
473 public void beforeViewChange(Enum<?> viewChangingTo, Callback<Boolean> okToChange) {
474 if(this.getCurrentView() instanceof Controller){
475 ((Controller)this.getCurrentView()).beforeViewChange(viewChangingTo, okToChange);
476 }
477 else{
478 okToChange.exec(true);
479 }
480 }
481
482 @Override
483 public Widget asWidget() {
484 return this;
485 }
486
487 @Override
488 public boolean beforeHide() {
489 return true;
490 }
491
492 /**
493 * Default implementation does nothing on before show. Override to do other things before THIS view is
494 * shown.
495 * @see org.kuali.student.common.ui.client.mvc.View#beforeShow(org.kuali.student.common.ui.client.mvc.Callback)
496 */
497 @Override
498 public void beforeShow(Callback<Boolean> onReadyCallback) {
499 onReadyCallback.exec(true);
500 }
501
502 @Override
503 public Controller getController() {
504 return parentController;
505 }
506
507 @Override
508 public String getName() {
509 if(name == null && viewType != null){
510 return viewType.toString();
511 }
512 else{
513 return name;
514 }
515 }
516
517 @Override
518 public Enum<?> getViewEnum() {
519 return viewType;
520 }
521
522 public void setViewEnum(Enum<?> viewType){
523 this.viewType= viewType;
524 }
525
526 /**
527 * Sets the name of this LayoutController. This name is used in the breadcrumb and window's title.
528 * Setting the name to the empty string will omit the breadcrumb - this is sometimes desired.
529 * @param name
530 */
531 public void setName(String name){
532 this.name = name;
533 }
534
535 public void setController(Controller controller){
536 parentController = controller;
537 }
538
539 @Override
540 public void collectBreadcrumbNames(List<String> names) {
541 names.add(this.getName());
542 if(this.getCurrentView() != null){
543 this.getCurrentView().collectBreadcrumbNames(names);
544 }
545 }
546
547 @Override
548 public void clear() {
549
550 }
551
552 }