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    }