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.lum.lu.ui.course.client.controllers;
17  
18  import java.text.DateFormat;
19  import java.util.ArrayList;
20  import java.util.Date;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.kuali.student.common.assembly.data.Data;
26  import org.kuali.student.common.assembly.data.Metadata;
27  import org.kuali.student.common.assembly.data.QueryPath;
28  import org.kuali.student.common.dto.DtoConstants;
29  import org.kuali.student.common.rice.StudentIdentityConstants;
30  import org.kuali.student.common.rice.authorization.PermissionType;
31  import org.kuali.student.common.ui.client.application.Application;
32  import org.kuali.student.common.ui.client.application.KSAsyncCallback;
33  import org.kuali.student.common.ui.client.application.ViewContext;
34  import org.kuali.student.common.ui.client.configurable.mvc.FieldDescriptor;
35  import org.kuali.student.common.ui.client.configurable.mvc.layouts.MenuEditableSectionController;
36  import org.kuali.student.common.ui.client.configurable.mvc.sections.BaseSection;
37  import org.kuali.student.common.ui.client.configurable.mvc.sections.Section;
38  import org.kuali.student.common.ui.client.configurable.mvc.views.SectionView;
39  import org.kuali.student.common.ui.client.event.ActionEvent;
40  import org.kuali.student.common.ui.client.event.ContentDirtyEvent;
41  import org.kuali.student.common.ui.client.event.ContentDirtyEventHandler;
42  import org.kuali.student.common.ui.client.event.SaveActionEvent;
43  import org.kuali.student.common.ui.client.event.SaveActionHandler;
44  import org.kuali.student.common.ui.client.mvc.ActionCompleteCallback;
45  import org.kuali.student.common.ui.client.mvc.Callback;
46  import org.kuali.student.common.ui.client.mvc.Controller;
47  import org.kuali.student.common.ui.client.mvc.DataModel;
48  import org.kuali.student.common.ui.client.mvc.DataModelDefinition;
49  import org.kuali.student.common.ui.client.mvc.HasCrossConstraints;
50  import org.kuali.student.common.ui.client.mvc.ModelProvider;
51  import org.kuali.student.common.ui.client.mvc.ModelRequestCallback;
52  import org.kuali.student.common.ui.client.mvc.View;
53  import org.kuali.student.common.ui.client.mvc.WorkQueue;
54  import org.kuali.student.common.ui.client.mvc.WorkQueue.WorkItem;
55  import org.kuali.student.common.ui.client.mvc.dto.ReferenceModel;
56  import org.kuali.student.common.ui.client.mvc.history.HistoryManager;
57  import org.kuali.student.common.ui.client.security.AuthorizationCallback;
58  import org.kuali.student.common.ui.client.security.RequiresAuthorization;
59  import org.kuali.student.common.ui.client.service.BaseDataOrchestrationRpcServiceAsync;
60  import org.kuali.student.common.ui.client.service.DataSaveResult;
61  import org.kuali.student.common.ui.client.util.ExportElement;
62  import org.kuali.student.common.ui.client.util.ExportUtils;
63  import org.kuali.student.common.ui.client.util.WindowTitleUtils;
64  import org.kuali.student.common.ui.client.validator.ValidatorClientUtils;
65  import org.kuali.student.common.ui.client.widgets.KSButton;
66  import org.kuali.student.common.ui.client.widgets.KSCheckBox;
67  import org.kuali.student.common.ui.client.widgets.KSDropDown;
68  import org.kuali.student.common.ui.client.widgets.KSLabel;
69  import org.kuali.student.common.ui.client.widgets.KSButtonAbstract.ButtonStyle;
70  import org.kuali.student.common.ui.client.widgets.buttongroups.ButtonEnumerations.YesNoCancelEnum;
71  import org.kuali.student.common.ui.client.widgets.dialog.ButtonMessageDialog;
72  import org.kuali.student.common.ui.client.widgets.field.layout.button.ButtonGroup;
73  import org.kuali.student.common.ui.client.widgets.field.layout.button.YesNoCancelGroup;
74  import org.kuali.student.common.ui.client.widgets.menus.KSMenuItemData;
75  import org.kuali.student.common.ui.client.widgets.notification.KSNotification;
76  import org.kuali.student.common.ui.client.widgets.notification.KSNotifier;
77  import org.kuali.student.common.ui.client.widgets.progress.BlockingTask;
78  import org.kuali.student.common.ui.client.widgets.progress.KSBlockingProgressIndicator;
79  import org.kuali.student.common.ui.client.widgets.search.KSPicker;
80  import org.kuali.student.common.ui.client.widgets.table.summary.SummaryTableSection;
81  import org.kuali.student.common.ui.shared.IdAttributes;
82  import org.kuali.student.common.ui.shared.IdAttributes.IdType;
83  import org.kuali.student.common.validation.dto.ValidationResultInfo;
84  import org.kuali.student.core.statement.dto.StatementTypeInfo;
85  import org.kuali.student.core.workflow.ui.client.widgets.WorkflowEnhancedNavController;
86  import org.kuali.student.core.workflow.ui.client.widgets.WorkflowUtilities;
87  import org.kuali.student.lum.common.client.helpers.RecentlyViewedHelper;
88  import org.kuali.student.lum.common.client.lu.LUUIConstants;
89  import org.kuali.student.lum.common.client.widgets.AppLocations;
90  import org.kuali.student.lum.lu.LUConstants;
91  import org.kuali.student.lum.lu.assembly.data.client.constants.orch.CreditCourseConstants;
92  import org.kuali.student.lum.lu.ui.course.client.configuration.CourseProposalConfigurer;
93  import org.kuali.student.lum.lu.ui.course.client.configuration.CourseProposalConfigurer.CourseSections;
94  import org.kuali.student.lum.lu.ui.course.client.requirements.CourseRequirementsDataModel;
95  import org.kuali.student.lum.lu.ui.course.client.requirements.HasRequirements;
96  import org.kuali.student.lum.lu.ui.course.client.service.CourseRpcService;
97  import org.kuali.student.lum.lu.ui.course.client.service.CourseRpcServiceAsync;
98  import org.kuali.student.lum.lu.ui.course.client.service.CreditCourseProposalRpcService;
99  import org.kuali.student.lum.lu.ui.course.client.service.CreditCourseProposalRpcServiceAsync;
100 
101 import com.google.gwt.core.client.GWT;
102 import com.google.gwt.event.dom.client.ClickEvent;
103 import com.google.gwt.event.dom.client.ClickHandler;
104 import com.google.gwt.event.logical.shared.ValueChangeEvent;
105 import com.google.gwt.event.logical.shared.ValueChangeHandler;
106 import com.google.gwt.user.client.Window;
107 import com.google.gwt.user.client.rpc.AsyncCallback;
108 
109 /**
110  * Controller for course proposal screens.  This controller controls all functions of the course proposal process
111  * and contains the data model and is responsible for retrieving its data and metadata from the server. In
112  * addition, this controller is responsible for course proposal save events and updating its ui accordingly.
113  * 
114  *
115  * @author Kuali Student Team
116  *
117  */
118 
119 public class CourseProposalController extends MenuEditableSectionController implements RequiresAuthorization, WorkflowEnhancedNavController, HasRequirements {
120 
121 	//RPC Services
122 	protected CreditCourseProposalRpcServiceAsync cluProposalRpcServiceAsync = GWT.create(CreditCourseProposalRpcService.class);
123 	protected CourseRpcServiceAsync courseServiceAsync = GWT.create(CourseRpcService.class);
124 	//Models
125 	protected final DataModel cluProposalModel = new DataModel("Proposal");
126 	protected final DataModel comparisonModel = new DataModel("Original Course");
127 
128 	protected CourseProposalConfigurer cfg;
129 	
130 	private WorkQueue modelRequestQueue;
131 
132     protected WorkflowUtilities workflowUtil;
133 
134 	private boolean initialized = false;
135 	protected boolean isNew = false;
136 
137 	private static final String UPDATED_KEY = "metaInfo/updateTime";
138 	private static final String VERSION_KEY  = "versionInfo/versionedFromId";
139     private static final String MSG_GROUP = "course";
140 	
141 	protected String currentDocType = LUConstants.PROPOSAL_TYPE_COURSE_CREATE;
142 	protected String proposalPath = "";
143 	protected String currentTitle;
144 
145 	private final DateFormat df = DateFormat.getInstance();
146 
147 	private final BlockingTask initializingTask = new BlockingTask("Loading");
148 	protected final BlockingTask loadDataTask = new BlockingTask("Retrieving Data");
149 	private final BlockingTask saving = new BlockingTask("Saving");
150 
151 	protected CourseRequirementsDataModel reqDataModel;
152 	protected CourseRequirementsDataModel reqDataModelComp;
153    
154     public CourseProposalController(){
155         super();
156         initializeController();
157     }
158 
159     protected void initializeController() {
160     	cfg = GWT.create(CourseProposalConfigurer.class);
161    		proposalPath = cfg.getProposalPath();
162    		workflowUtil = new WorkflowUtilities(CourseProposalController.this, proposalPath, "Proposal Actions",
163    				CourseProposalConfigurer.CourseSections.WF_APPROVE_DIALOG,"", cfg.getModelId());//TODO make msg
164    		cfg.setState(DtoConstants.STATE_DRAFT);
165    		
166    		//Add an extra menu item to copy the proposal to a new proposal.
167    		workflowUtil.getAdditionalItems().add(new KSMenuItemData(this.getMessage("cluCopyItem"), new ClickHandler(){
168 			@Override
169 			public void onClick(ClickEvent event) {
170 			    if(getViewContext() != null && getViewContext().getId() != null && !getViewContext().getId().isEmpty()){
171 		    		getViewContext().setId((String)cluProposalModel.get(cfg.getProposalPath()+"/id"));
172 		    		getViewContext().setIdType(IdType.COPY_OF_KS_KEW_OBJECT_ID);
173 		    		getViewContext().getAttributes().remove(StudentIdentityConstants.DOCUMENT_TYPE_NAME);
174 		    		cluProposalModel.resetRoot(); // Reset the root so that the model can be reloaded from the copied proposal.
175 		        }
176                 HistoryManager.navigate("/HOME/CURRICULUM_HOME/COURSE_PROPOSAL", getViewContext());
177 			}
178 		}));
179    		
180    		super.setDefaultModelId(cfg.getModelId());
181    		registerModelsAndHandlers();
182    		
183         addStyleName("courseProposal");
184     }
185     
186     protected void registerModelsAndHandlers(){
187         reqDataModel = new CourseRequirementsDataModel(this);
188         reqDataModelComp = new CourseRequirementsDataModel(this);
189     	
190         super.registerModel(super.getDefaultModelId(), new ModelProvider<DataModel>() {
191 
192             @Override
193             public void requestModel(final ModelRequestCallback<DataModel> callback) {
194                 if (modelRequestQueue == null){
195                     modelRequestQueue = new WorkQueue();
196                 }
197 
198                 WorkItem workItem = new WorkItem(){
199                     @Override
200                     public void exec(Callback<Boolean> workCompleteCallback) {
201                         if (cluProposalModel.getRoot() == null || initialized == false){
202                             populateModel(callback, workCompleteCallback);
203                         } else {
204                             callback.onModelReady(cluProposalModel);
205                             workCompleteCallback.exec(true);
206                         }
207                     }
208                 };
209                 modelRequestQueue.submit(workItem);
210 
211             }
212 
213         });
214         super.registerModel("ComparisonModel", new ModelProvider<DataModel>() {
215             @Override
216             public void requestModel(final ModelRequestCallback<DataModel> callback) {
217             	if(comparisonModel.getRoot() != null && comparisonModel.getRoot().size() != 0){
218             		callback.onModelReady(comparisonModel);
219             		
220             	}
221             	else{
222             		callback.onModelReady(null);
223             	}
224                 
225             }
226         });
227         super.addApplicationEventHandler(ContentDirtyEvent.TYPE, new ContentDirtyEventHandler(){
228 			public void onContentDirty(ContentDirtyEvent event) {
229         		setContentWarning("You have unsaved changes");				
230 			}        	
231         });
232 
233         super.addApplicationEventHandler(SaveActionEvent.TYPE, new SaveActionHandler(){
234             public void doSave(SaveActionEvent saveAction) {
235                 GWT.log("CluProposalController received save action request.", null);
236                 doSaveAction(saveAction);
237             }
238         }); 
239             }
240     
241     /**
242      * Used to populate the proposal model based on the view context.  
243      * 
244      * @param callback
245      * @param workCompleteCallback
246      */
247     private void populateModel(final ModelRequestCallback<DataModel> callback, Callback<Boolean> workCompleteCallback){
248     	if(getViewContext().getIdType() == IdType.DOCUMENT_ID){
249             getCluProposalFromWorkflowId(callback, workCompleteCallback);
250         } else if (getViewContext().getIdType() == IdType.KS_KEW_OBJECT_ID || getViewContext().getIdType() == IdType.OBJECT_ID){
251             getCluProposalFromProposalId(getViewContext().getId(), callback, workCompleteCallback);
252         } else if (getViewContext().getIdType() == IdType.COPY_OF_OBJECT_ID){
253         	if(LUConstants.PROPOSAL_TYPE_COURSE_MODIFY.equals(getViewContext().getAttribute(StudentIdentityConstants.DOCUMENT_TYPE_NAME))||
254        			LUConstants.PROPOSAL_TYPE_COURSE_MODIFY_ADMIN.equals(getViewContext().getAttribute(StudentIdentityConstants.DOCUMENT_TYPE_NAME))){
255         		createModifyCluProposalModel("versionComment", callback, workCompleteCallback);
256         	}else{
257         		createCopyCourseModel(getViewContext().getId(), callback, workCompleteCallback);
258         	}
259         } else if (getViewContext().getIdType() == IdType.COPY_OF_KS_KEW_OBJECT_ID){
260         	createCopyCourseProposalModel(getViewContext().getId(), callback, workCompleteCallback);
261         } else{
262             createNewCluProposalModel(callback, workCompleteCallback);
263         }
264     }
265 
266     protected void getCurrentModel(final ModelRequestCallback<DataModel> callback, Callback<Boolean> workCompleteCallback){
267     	if (cluProposalModel.getRoot() != null && cluProposalModel.getRoot().size() > 0){
268         	String id = cluProposalModel.get(cfg.getProposalPath()+"/id");
269         	if(id != null){
270         		getCluProposalFromProposalId(id, callback, workCompleteCallback);
271         	}
272         	else{
273         		populateModel(callback, workCompleteCallback);
274         	}
275     	}
276     	else{
277     		populateModel(callback, workCompleteCallback);
278     	}
279     }
280 
281     private void intializeView(final Callback<Boolean> onReadyCallback) {
282     	if (initialized) {
283     		onReadyCallback.exec(true);
284     	} else {
285     		initialized = true;
286     		KSBlockingProgressIndicator.addTask(initializingTask);
287     		setContentWarning("");
288             this.requestModel(new ModelRequestCallback<DataModel>(){
289 
290 				@Override
291 				public void onModelReady(DataModel model) {
292 					
293 					//Setup View Context & determine id type
294 					String idType = null;
295 		    		String viewContextId = "";
296 		    		if(getViewContext().getIdType() != null){
297 		                idType = getViewContext().getIdType().toString();
298 		                viewContextId = getViewContext().getId();
299 		                if(getViewContext().getIdType()==IdAttributes.IdType.COPY_OF_OBJECT_ID){
300 		                	viewContextId = null;
301 		                }
302 
303 		    		}
304 
305 		    		//FIXME: Something looks odd in determining currentDocType
306 		    		if(cluProposalModel.get(VERSION_KEY) != null && !((String)cluProposalModel.get(VERSION_KEY)).equals("") && !LUConstants.PROPOSAL_TYPE_COURSE_MODIFY_ADMIN.equals(currentDocType)){
307 		    			currentDocType = LUConstants.PROPOSAL_TYPE_COURSE_MODIFY;
308 		    		}
309 		    		//Check for admin modify type
310 		    		if(LUConstants.PROPOSAL_TYPE_COURSE_MODIFY_ADMIN.equals(cluProposalModel.get(cfg.getProposalPath()+"/type"))){
311 		    			currentDocType = LUConstants.PROPOSAL_TYPE_COURSE_MODIFY_ADMIN;
312 		    		}
313 		    		
314 		    		//Get the state for save action
315 		    		String dtoState = getStateforSaveAction(cluProposalModel); 
316 		    		
317 		    		//Get the current workflow node for proposal
318 		    		String workflowNode = cluProposalModel.get(cfg.getProposalPath()+"/workflowNode");
319 		    		
320 		    		//Add properties to an id attributes map so metadata service can get correct metadata
321 					HashMap<String, String> idAttributes = new HashMap<String, String>();
322 		    		if(idType != null){
323 		    			idAttributes.put(IdAttributes.ID_TYPE, idType);
324 		    		}
325 		    		idAttributes.put(StudentIdentityConstants.DOCUMENT_TYPE_NAME, currentDocType);		    				    		
326 		    		idAttributes.put(DtoConstants.DTO_STATE, dtoState);		    		
327 		    		idAttributes.put(DtoConstants.DTO_NEXT_STATE, cfg.getNextState());
328 		    		if (LUConstants.PROPOSAL_TYPE_COURSE_MODIFY.equalsIgnoreCase(currentDocType) ||
329 		    			LUConstants.PROPOSAL_TYPE_COURSE_CREATE.equals(currentDocType)){		    			
330 		    			idAttributes.put(DtoConstants.DTO_WORKFLOW_NODE, workflowNode);
331 		    		}
332 
333 		    		
334 		    		//Get metadata and complete initializing the screen
335 		    		getCourseProposalRpcService().getMetadata(viewContextId, idAttributes, new KSAsyncCallback<Metadata>(){
336 						@Override
337                         public void handleTimeout(Throwable caught) {
338 		                	initializeFailed(); 
339 						}
340 
341 						@Override
342                         public void handleFailure(Throwable caught) {
343 							initializeFailed();
344 		                    throw new RuntimeException("Failed to get model definition.", caught);
345 		                }
346 
347 						public void initializeFailed(){
348 			        		initialized = false;
349 		                	onReadyCallback.exec(false);
350 		                	KSBlockingProgressIndicator.removeTask(initializingTask);							
351 						}
352 						
353 		                public void onSuccess(Metadata result) {
354 		                	DataModelDefinition def = new DataModelDefinition(result);
355 		                    cluProposalModel.setDefinition(def);
356 		                    comparisonModel.setDefinition(def);
357 
358 		                    configureScreens(def, onReadyCallback);
359 
360 		                }
361 			          });
362 					
363 				}
364 
365 				@Override
366 				public void onRequestFail(Throwable cause) {
367 					GWT.log("Failed to get modeld for proposal controller init");
368 					onReadyCallback.exec(false);
369 				}
370 			});
371                 		
372     	}
373     }
374 
375     @Override
376     public void getMetadataForFinalState(final KSAsyncCallback<Metadata> callback){
377 		//Setup View Context
378 		String idType = null;
379 		String viewContextId = "";
380 		if(getViewContext().getIdType() != null){
381             idType = getViewContext().getIdType().toString();
382             viewContextId = getViewContext().getId();
383             if(getViewContext().getIdType()==IdAttributes.IdType.COPY_OF_OBJECT_ID){
384             	viewContextId = null;
385             }
386 		}
387 		HashMap<String, String> idAttributes = new HashMap<String, String>();
388 		if(idType != null){
389 			idAttributes.put(IdAttributes.ID_TYPE, idType);
390 		}
391 
392 		idAttributes.put(StudentIdentityConstants.DOCUMENT_TYPE_NAME, currentDocType);
393 		idAttributes.put(DtoConstants.DTO_STATE, cfg.getState());		    		
394 		idAttributes.put(DtoConstants.DTO_NEXT_STATE, cfg.getNextState());
395 		idAttributes.put(DtoConstants.DTO_WORKFLOW_NODE, "Publication Review");
396 		
397 		//Get metadata and complete initializing the screen
398 		getCourseProposalRpcService().getMetadata(viewContextId, idAttributes, new KSAsyncCallback<Metadata>(){
399 			@Override
400 			public void onSuccess(Metadata result) {
401 				callback.onSuccess(result);
402 			}
403 		});
404     }
405     
406     protected void configureScreens(final DataModelDefinition modelDefinition, final Callback<Boolean> onReadyCallback){
407     	if (workflowUtil != null){
408     		workflowUtil.requestAndSetupModel(NO_OP_CALLBACK);	
409     	}
410 
411         CourseRequirementsDataModel.getStatementTypes(new Callback<List<StatementTypeInfo>>() {
412 
413             @Override
414             public void exec(List<StatementTypeInfo> stmtTypes) {
415                 List<StatementTypeInfo> stmtTypesOut = new ArrayList<StatementTypeInfo>();
416                 if (stmtTypes != null) {
417                     for (StatementTypeInfo stmtType : stmtTypes) {
418                         if (stmtType.getId().contains("kuali.statement.type.course.enrollmentEligibility") ||
419                             stmtType.getId().contains("kuali.statement.type.course.creditConstraints")) {
420                             continue;
421                         }
422                         stmtTypesOut.add(stmtType);
423                     }
424                 }
425 
426                 cfg.setStatementTypes(stmtTypesOut);
427                 cfg.setModelDefinition(modelDefinition);
428                 cfg.configure(CourseProposalController.this);
429                 
430                 //Add fields to workflow utils screens
431                 if(workflowUtil!=null){
432                 	requestModel(new ModelRequestCallback<DataModel>(){
433 						public void onModelReady(DataModel model) {
434 							//Only display if this is a modification
435 							String versionedFromId = model.get("versionInfo/versionedFromId");
436 							if(versionedFromId!=null && !versionedFromId.isEmpty()){	
437 							    KSLabel descLabel = new KSLabel();
438 							    descLabel.setText(Application.getApplicationContext().getUILabel("course", LUUIConstants.FINAL_APPROVAL_DIALOG));
439 							    if (workflowUtil.getApproveDialogue() != null) {
440 							        workflowUtil.getApproveDialogue().addWidget(descLabel);
441 							    }
442 							    workflowUtil.addApproveDialogField("", "startTerm", cfg.generateMessageInfo(LUUIConstants.PROPOSAL_START_TERM), modelDefinition, true, true);
443 							    workflowUtil.addApproveDialogField("proposal", "prevEndTerm", cfg.generateMessageInfo(LUUIConstants.PROPOSAL_PREV_END_TERM), modelDefinition, false);
444                                 
445 							    workflowUtil.updateApproveFields();							    
446 							    workflowUtil.progressiveEnableFields();							    
447 							}else{
448 								//Ignore this field (so blanket approve works if this is a new course proposal and not modifiaction)
449 								workflowUtil.addIgnoreDialogField("proposal/prevEndTerm");
450 							}
451 						}
452 						public void onRequestFail(Throwable cause) {
453 						}
454                 	});
455                 }
456 
457                 progressiveEnableFields();
458                 
459                 onReadyCallback.exec(true);
460                 KSBlockingProgressIndicator.removeTask(initializingTask);
461             }
462         });
463     }
464     
465     /**
466      * This progressively enables/disables screen fields based on other fields present in the screens.
467      *  
468      * NOTE: This metod must be caled after cfg.configure() is called, otherwise path to field mappings won't exist in ApplicationContext
469      */
470     protected void progressiveEnableFields(){
471 		final FieldDescriptor endTerm = Application.getApplicationContext().getPathToFieldMapping(null,CreditCourseConstants.END_TERM);
472 		final FieldDescriptor pilotCourse = Application.getApplicationContext().getPathToFieldMapping(null,CreditCourseConstants.PILOT_COURSE);
473 		
474 		if (pilotCourse != null && endTerm != null){
475 	    	//Enable and require end term field based on pilot course value in model loaded		
476 	        Boolean enableEndTerm = Boolean.TRUE.equals(cluProposalModel.get(CreditCourseConstants.PILOT_COURSE)) 
477 	        	|| DtoConstants.STATE_RETIRED.equalsIgnoreCase((String)cluProposalModel.get(CreditCourseConstants.STATE));
478 			
479             BaseSection.progressiveEnableAndRequireFields(enableEndTerm, endTerm);
480 
481 	        //Add a click handler to pilot checkbox to toggle enabling and requiredness of end term field
482 			KSCheckBox pilotCheckbox = ((KSCheckBox)pilotCourse.getFieldWidget());
483 	        pilotCheckbox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
484 				@Override
485 				public void onValueChange(ValueChangeEvent<Boolean> event) {
486 					//Disable/enable end term field based on new value of pilot checkbox
487                     BaseSection.progressiveEnableAndRequireFields(event.getValue(), endTerm);
488 	            
489 			        //Clear out endTerm value if pilot course unchecked (as this field is not required when not pilot course)
490 			        if (!event.getValue()){
491 						((KSDropDown)((KSPicker)endTerm.getFieldWidget()).getInputWidget()).clear();				
492 					}					        
493 				}
494 			});
495 		}
496     }
497 
498     @Override
499     @SuppressWarnings("unchecked")
500     public void requestModel(Class modelType, final ModelRequestCallback callback) {
501         if(modelType == ReferenceModel.class){
502         	if (cluProposalModel != null){
503         		ReferenceModel ref = new ReferenceModel();
504 
505         		if(cluProposalModel.get(cfg.getProposalPath()) != null){
506             		ref.setReferenceId((String)cluProposalModel.get(cfg.getProposalPath()+"/id"));
507         		} else {
508         			ref.setReferenceId(null);
509         		}
510         		
511         		//Use the referenceAttribute to store misc data from the parent model like reference name, etc
512         		if(cluProposalModel.get(cfg.getProposalPath()) != null){
513         			Map<String, String> attributes = new HashMap<String, String>();
514         			attributes.put("name", (String)cluProposalModel.get(cfg.getProposalPath()+"/name"));
515         			ref.setReferenceAttributes(attributes);
516         		} else {
517         			ref.setReferenceAttributes(null);
518         		}
519 
520         		ref.setReferenceTypeKey(cfg.getProposalReferenceTypeKey());
521         		ref.setReferenceType(cfg.getProposalReferenceObjectType());
522         		ref.setReferenceState(getViewContext().getState());
523 
524         		callback.onModelReady(ref);
525         	}
526         } else if (modelType == Data.class){
527         	requestModel(cfg.getModelId(), callback);
528         } else {
529             super.requestModel(modelType, callback);
530         }
531 
532     }
533 
534     private void getCluProposalFromWorkflowId(@SuppressWarnings("rawtypes") final ModelRequestCallback callback, final Callback<Boolean> workCompleteCallback){
535         KSBlockingProgressIndicator.addTask(loadDataTask);
536         workflowUtil.getDataIdFromWorkflowId(getViewContext().getId(), new KSAsyncCallback<String>(){
537 			@Override
538 			public void handleFailure(Throwable caught) {
539                 Window.alert("Error loading Proposal from Workflow Document: "+caught.getMessage());
540                 createNewCluProposalModel(callback, workCompleteCallback);
541                 KSBlockingProgressIndicator.removeTask(loadDataTask);
542 			}
543 
544 			@Override
545 			public void onSuccess(String proposalId) {
546 				KSBlockingProgressIndicator.removeTask(loadDataTask);
547 				getCluProposalFromProposalId(proposalId, callback, workCompleteCallback);
548 			}
549         });
550     }
551 
552     protected void getCluProposalFromProposalId(String id, @SuppressWarnings("rawtypes") final ModelRequestCallback callback, final Callback<Boolean> workCompleteCallback){
553     	KSBlockingProgressIndicator.addTask(loadDataTask);
554     	getCourseProposalRpcService().getData(id, new KSAsyncCallback<Data>(){
555 
556 			@Override
557 			public void handleFailure(Throwable caught) {
558                 Window.alert("Error loading Proposal: "+caught.getMessage());
559                 createNewCluProposalModel(callback, workCompleteCallback);
560                 KSBlockingProgressIndicator.removeTask(loadDataTask);
561 			}
562 
563 			@Override
564 			public void onSuccess(Data result) {
565 				cluProposalModel.setRoot(result);
566 		        setHeaderTitle();
567 		        setLastUpdated();
568                 reqDataModel.retrieveStatementTypes(cluProposalModel.<String>get("id"), new Callback<Boolean>() {
569                     @Override
570                     public void exec(Boolean result) {
571                        if(result){
572                           getCourseComparisonModelAndReqs(callback, workCompleteCallback);
573                        }
574                     }
575                 });
576 			}
577 
578     	});
579     }
580 
581     @SuppressWarnings("unchecked")
582 	protected void getCourseComparisonModelAndReqs(final ModelRequestCallback proposalModelRequestCallback, final Callback<Boolean> workCompleteCallback){
583 		if(cluProposalModel.get(VERSION_KEY) != null && !((String)cluProposalModel.get(VERSION_KEY)).equals("")){
584 			courseServiceAsync.getData((String)cluProposalModel.get(VERSION_KEY), new KSAsyncCallback<Data>(){
585 	
586 	    		@Override
587 	            public void handleFailure(Throwable caught) {
588 	                Window.alert("Error loading Proposal: "+caught.getMessage());
589 	                createNewCluProposalModel(proposalModelRequestCallback, workCompleteCallback);
590 	                KSBlockingProgressIndicator.removeTask(loadDataTask);
591 	            }
592 
593                 @Override
594                 public void onSuccess(Data result) {
595                 	// ??? why result would be null ever?
596                     if (result != null) 
597                         comparisonModel.setRoot(result);
598 
599                     reqDataModel.retrieveStatementTypes(cluProposalModel.<String>get("id"), new Callback<Boolean>() {
600                    		@Override
601                    		public void exec(Boolean result) {
602                    			if (result) {
603                    				KSBlockingProgressIndicator.removeTask(loadDataTask);
604                                 reqDataModelComp.retrieveStatementTypes(comparisonModel.<String>get("id"), new Callback<Boolean>() {
605                                     @Override
606                                     public void exec(Boolean result) {
607                                         if (result) {
608                                             KSBlockingProgressIndicator.removeTask(loadDataTask);
609                                          }
610                                     }
611                                 });
612                                 proposalModelRequestCallback.onModelReady(cluProposalModel);
613                                 workCompleteCallback.exec(true);
614                   			}
615                    		}
616                    	});
617                }
618             });
619         } else {
620             proposalModelRequestCallback.onModelReady(cluProposalModel);
621             workCompleteCallback.exec(true);
622             KSBlockingProgressIndicator.removeTask(loadDataTask);
623         }
624     }
625 
626     @SuppressWarnings("unchecked")
627     protected void createNewCluProposalModel(final ModelRequestCallback callback, final Callback<Boolean> workCompleteCallback){
628         Data data = new Data();
629     	cluProposalModel.setRoot(data);
630         
631         Data proposalData = new Data();
632         proposalData.set(new Data.StringKey("type"), currentDocType);
633         data.set(new Data.StringKey("proposal"), proposalData);                
634         if (cfg.getNextState() == null || cfg.getNextState().isEmpty()){
635         	proposalData.set(new Data.StringKey("workflowNode"), "PreRoute");
636         }
637         
638         isNew = true;
639         setHeaderTitle();
640         setLastUpdated();
641         callback.onModelReady(cluProposalModel);
642         workCompleteCallback.exec(true);
643     }
644 
645     private void createModifyCluProposalModel(String versionComment, final ModelRequestCallback callback, final Callback<Boolean> workCompleteCallback){
646         Data data = new Data();
647         cluProposalModel.setRoot(data);        
648         
649         this.currentDocType = getViewContext().getAttribute(StudentIdentityConstants.DOCUMENT_TYPE_NAME);
650         Data proposalData = new Data();
651         proposalData.set(new Data.StringKey("type"), currentDocType);
652         data.set(new Data.StringKey("proposal"), proposalData);
653         if (cfg.getNextState() == null && cfg.getNextState().isEmpty()){
654         	proposalData.set(new Data.StringKey("workflowNode"), "PreRoute");
655         }
656                 
657         Data versionData = new Data();
658         versionData.set(new Data.StringKey("versionIndId"), getViewContext().getId());
659         versionData.set(new Data.StringKey("versionComment"), versionComment);
660         data.set(new Data.StringKey("versionInfo"), versionData);
661         
662         cluProposalRpcServiceAsync.saveData(cluProposalModel.getRoot(), new AsyncCallback<DataSaveResult>() {
663 			public void onSuccess(DataSaveResult result) {
664 				cluProposalModel.setRoot(result.getValue());
665 				setHeaderTitle();
666 		        setLastUpdated();
667 		        //add to recently viewed now that we know the id of proposal
668 		        ViewContext docContext = new ViewContext();
669 		        docContext.setId((String) cluProposalModel.get(cfg.getProposalPath()+"/id"));
670 		        docContext.setIdType(IdType.KS_KEW_OBJECT_ID);
671 		        //RecentlyViewedHelper.addDocument(getProposalTitle(), 
672 		        //	HistoryManager.appendContext(AppLocations.Locations.COURSE_PROPOSAL.getLocation(), docContext)
673 		        //		+ "/SUMMARY");
674 		        getCourseComparisonModelAndReqs(callback, workCompleteCallback);
675 		        
676 		        // We need to update the current view context so that if the user clicks the back button it doesn't 
677 		        // create a duplicate course proposal. 
678 		        getViewContext().setIdType(docContext.getIdType());
679 		        getViewContext().setId(docContext.getId());
680 		        
681 			}
682 			
683 			public void onFailure(Throwable caught) {
684                 Window.alert("Error loading Proposal: "+caught.getMessage());
685                 createNewCluProposalModel(callback, workCompleteCallback);
686                 KSBlockingProgressIndicator.removeTask(loadDataTask);
687 			}
688 		});
689     }
690 
691     @SuppressWarnings("unchecked")
692     private void createCopyCourseModel(String originalCluId, final ModelRequestCallback callback, final Callback<Boolean> workCompleteCallback){
693 
694     	cluProposalRpcServiceAsync.createCopyCourse(originalCluId, new AsyncCallback<DataSaveResult>() {
695 			public void onSuccess(DataSaveResult result) {
696 				cluProposalModel.setRoot(result.getValue());
697 				
698 				//Add in a blank proposal placeholder
699 		        Data proposalData = new Data();
700 		        cluProposalModel.getRoot().set(new Data.StringKey("proposal"), proposalData);
701 		        if (cfg.getNextState() == null || cfg.getNextState().isEmpty()){
702 		            proposalData.set(new Data.StringKey("workflowNode"), "PreRoute");
703 		        }
704 		        
705 		        isNew = true;
706 				setHeaderTitle();
707 		        setLastUpdated();
708 
709 		        callback.onModelReady(cluProposalModel);
710 		        workCompleteCallback.exec(true);
711 			}
712 			
713 			public void onFailure(Throwable caught) {
714                 Window.alert("Error loading Proposal: "+caught.getMessage());
715                 createNewCluProposalModel(callback, workCompleteCallback);
716                 KSBlockingProgressIndicator.removeTask(loadDataTask);
717 			}
718 		});
719     }
720     
721     @SuppressWarnings("unchecked")
722     private void createCopyCourseProposalModel(String originalProposalId, final ModelRequestCallback callback, final Callback<Boolean> workCompleteCallback){
723 
724     	cluProposalRpcServiceAsync.createCopyCourseProposal(originalProposalId, new AsyncCallback<DataSaveResult>() {
725 			public void onSuccess(DataSaveResult result) {
726 				cluProposalModel.setRoot(result.getValue());
727 		        setHeaderTitle();
728 		        setLastUpdated();
729 		        //add to recently viewed now that we know the id of proposal
730 		        ViewContext docContext = new ViewContext();
731 		        docContext.setId((String) cluProposalModel.get(cfg.getProposalPath()+"/id"));
732 		        docContext.setIdType(IdType.KS_KEW_OBJECT_ID);
733 		        RecentlyViewedHelper.addDocument(getProposalTitle(), 
734 		        		HistoryManager.appendContext(AppLocations.Locations.COURSE_PROPOSAL.getLocation(), docContext)
735 		        		+ "/COURSE_INFO");
736 		        
737 		        // We need to update the current view context so that if the user clicks the back button it doesn't 
738 		        // create a duplicate course proposal. 
739 		        getViewContext().setIdType(docContext.getIdType());
740 		        getViewContext().setId(docContext.getId());
741 		        
742 		        callback.onModelReady(cluProposalModel);
743 		        workCompleteCallback.exec(true);
744 			}
745 			
746 			public void onFailure(Throwable caught) {
747                 Window.alert("Error loading Proposal: "+caught.getMessage());
748                 createNewCluProposalModel(callback, workCompleteCallback);
749                 KSBlockingProgressIndicator.removeTask(loadDataTask);
750 			}
751 		});
752     }
753     
754     public void doSaveAction(final SaveActionEvent saveActionEvent){
755         requestModel(new ModelRequestCallback<DataModel>() {
756             @Override
757             public void onModelReady(DataModel model) {
758                 CourseProposalController.this.updateModelFromCurrentView();
759 
760                 if (isStartViewShowing()){
761                 	//This call required so fields in start section, which also appear in
762                 	//other sections don't get overridden from updateModel call above.
763                 	getStartPopupView().updateModel();
764                 }
765 
766             	model.validate(new Callback<List<ValidationResultInfo>>() {
767                     @Override
768                     public void exec(List<ValidationResultInfo> result) {
769 
770                     	boolean isSectionValid = isValid(result, true);
771 
772                     	if(isSectionValid){
773                             if (startSectionRequired()){
774                                 showStartPopup(NO_OP_CALLBACK);
775                                 saveActionEvent.doActionComplete();
776                             }
777                             else{
778 	                            saveProposalClu(saveActionEvent);
779                             }
780                     	}
781                     	else{
782                     		//saveActionEvent.doActionComplete();
783                     		KSNotifier.add(new KSNotification("Unable to save, please check fields for errors.", false, true, 5000));
784                     	}
785 
786                     }
787                 });
788             }
789 
790             @Override
791             public void onRequestFail(Throwable cause) {
792             	saveActionEvent.doActionComplete();
793                 GWT.log("Unable to retrieve model for validation and save", cause);
794             }
795 
796         });
797 
798     }
799 
800     public boolean startSectionRequired(){
801         String proposalId = cluProposalModel.get(cfg.getProposalPath()+"/id");
802         
803         //Defaulting the proposalTitle to courseTitle, this way course data gets set and assembler doesn't
804         //complain. This may not be the correct approach.
805         String proposalTitle = cluProposalModel.get(cfg.getProposalTitlePath());
806         String courseTitle = cluProposalModel.get(cfg.getCourseTitlePath());
807         if (proposalTitle == null || proposalTitle.isEmpty()){
808             cluProposalModel.set(QueryPath.parse(cfg.getProposalTitlePath()), courseTitle);
809         }
810         
811     	return proposalId==null && !CourseProposalController.this.isStartViewShowing() && !hasTitles(proposalTitle, courseTitle);
812     }
813 
814     private boolean hasTitles(String proposalTitle, String courseTitle){
815     	return (proposalTitle != null && !proposalTitle.isEmpty()) && (courseTitle != null && !courseTitle.isEmpty());
816     }
817     
818     public void saveProposalClu(final SaveActionEvent saveActionEvent){
819     	KSBlockingProgressIndicator.addTask(saving);
820         getCourseProposalRpcService().saveData(cluProposalModel.getRoot(), new KSAsyncCallback<DataSaveResult>(){
821 
822             @Override
823             public void handleFailure(Throwable caught) {
824                 GWT.log("Save Failed.", caught);
825                 KSBlockingProgressIndicator.removeTask(saving);
826                 KSNotifier.add(new KSNotification("Save Failed on server. Please try again.", false, true, 5000));
827             }
828                 
829             @Override
830             public void handleVersionMismatch(Throwable caught) {
831                 super.handleVersionMismatch(caught);
832                 KSBlockingProgressIndicator.removeTask(saving);
833             }
834 
835             public void onSuccess(DataSaveResult result) {
836                 KSBlockingProgressIndicator.removeTask(saving);
837 
838 				Application.getApplicationContext().clearValidationWarnings();
839 				Application.getApplicationContext().addValidationWarnings(result.getValidationResults());
840                 	
841 				if(ValidatorClientUtils.hasErrors(result.getValidationResults())){
842                 	isValid(result.getValidationResults(), false, true);
843                     saveActionEvent.setGotoNextView(false);
844                     saveActionEvent.doActionComplete();
845                     KSNotifier.add(new KSNotification("Save Failed. There were validation errors.", false, true, 5000));
846                 }else{
847                 		
848                 	saveActionEvent.setSaveSuccessful(true);
849                 	cluProposalModel.setRoot(result.getValue());
850                 	String title = getProposalTitle();
851     	            View currentView = getCurrentView();
852      				if (currentView instanceof SectionView){
853     					((SectionView)currentView).updateView(cluProposalModel);
854     					((SectionView) currentView).resetDirtyFlags();
855     	            }
856                     saveActionEvent.doActionComplete();
857 	                    
858     				ViewContext context = CourseProposalController.this.getViewContext();
859     				context.setId((String)cluProposalModel.get(proposalPath+"/id"));
860     				context.setIdType(IdType.KS_KEW_OBJECT_ID);
861     				
862     				//Always update the status after a save.
863     				if(workflowUtil != null){
864     					workflowUtil.refresh();
865     				}
866 	    				
867     				setHeaderTitle();
868     				setLastUpdated();
869     				HistoryManager.logHistoryChange();
870                		if(isNew){
871                			RecentlyViewedHelper.addDocument(getProposalTitle(), 
872         		        	HistoryManager.appendContext(AppLocations.Locations.COURSE_PROPOSAL.getLocation(), context)
873         		        		+ "/SUMMARY");
874                		}
875                		else if(!currentTitle.equals(title)){
876                			RecentlyViewedHelper.updateTitle(currentTitle, title, (String)cluProposalModel.get(proposalPath+"/id"));
877                		}
878                		isNew = false;
879 	    				
880     				if(saveActionEvent.gotoNextView()){
881     					CourseProposalController.this.showNextViewOnMenu();
882     				}
883 	    				
884     				if (ValidatorClientUtils.hasWarnings(result.getValidationResults())){
885     					if (!saveActionEvent.gotoNextView()){
886     						//Need to display warnings when view has not changed.
887     						isValid(result.getValidationResults(), false, true);
888     					}
889     					KSNotifier.show("Saved with Warnings");
890     				} else {
891     					KSNotifier.show("Save Successful");
892     				}  				
893                	}
894             }
895         });
896 
897     }
898 
899     public void setLastUpdated(){
900     	Date lastUpdated = (Date)cluProposalModel.get(UPDATED_KEY);
901     	if(lastUpdated != null){
902     		setContentInfo("Last Updated: " + df.format(lastUpdated));
903     	}
904     	else{
905     		setContentInfo("");
906     	}
907     }
908 
909     @Override
910 	public void beforeShow(final Callback<Boolean> onReadyCallback){
911     	Application.getApplicationContext().clearCrossConstraintMap(null);
912     	Application.getApplicationContext().clearPathToFieldMapping(null);
913     	Application.getApplicationContext().clearValidationWarnings();
914     	Application.getApplicationContext().setParentPath("");
915     	   	
916     	intializeView(onReadyCallback);
917 	}
918 
919     //Before show is called before the model is bound to the widgets. We need to update cross constraints and re-display 
920     // validation warnings after widget binding
921     //This gets called twice which is not optimal
922 	@Override
923 	public <V extends Enum<?>> void showView(final V viewType, final Callback<Boolean> onReadyCallback) {
924 		Callback<Boolean> finalizeView = new Callback<Boolean>(){
925 		    
926 			public void exec(Boolean result) {   // Called from at least CourseSumamryConfigurer.generateProposalSummarySection.verticalSection.beforeShow.exec [KSCM-250]
927 				
928 			    //Update cross constraints
929 				for(HasCrossConstraints crossConstraint:Application.getApplicationContext().getCrossConstraints(null)){
930 		        	crossConstraint.reprocessWithUpdatedConstraints();
931 		        }				
932 				
933 				//When showing summary section make sure data gets validated in case there are warnings.
934 				//TODO: Is it possible to cut down on this validation so it doesn't have to validate every time.
935 				if (viewType == CourseSections.SUMMARY){
936 				    
937 					KSBlockingProgressIndicator.addTask(initializingTask);
938 					
939 					courseServiceAsync.validate(cluProposalModel.getRoot(), new KSAsyncCallback<List<ValidationResultInfo>>(){ // server-side call
940 					    
941 						@Override
942 						public void onSuccess(List<ValidationResultInfo> result) {
943 						    
944 							Application.getApplicationContext().clearValidationWarnings();
945 							Application.getApplicationContext().addValidationWarnings(result);
946 							
947 							showWarnings();
948 							
949 							KSBlockingProgressIndicator.removeTask(initializingTask);
950 						}						
951 					});					
952 				} else {
953 				    
954 					showWarnings();					
955 				}
956 				
957 				onReadyCallback.exec(result);
958 			}
959         };
960         
961 		super.showView(viewType, finalizeView);
962 	}
963 	
964 
965    @Override
966    public void showDefaultView(Callback<Boolean> onReadyCallback) {
967 	   if(isNew){
968 		   super.showFirstView(onReadyCallback);
969 	   }
970 	   else{
971 		   super.showDefaultView(onReadyCallback);
972 	   }
973    }
974 
975 	@Override
976     public void setParentController(Controller controller) {
977         super.setParentController(controller);
978     }
979 
980 	@Override
981 	/**
982 	 * Override method to determine if user has access to screen and if they have permission to open or initiate the proposal.
983 	 * 
984 	 *  FIXME: This method should not require a permissionType as a parameter
985 	 */
986 	public void checkAuthorization(final PermissionType permissionType, final AuthorizationCallback authCallback) {
987 		GWT.log("Attempting Auth Check.", null);
988 
989 		//Get attributes required for permission check
990 		Map<String,String> attributes = new HashMap<String,String>();
991 		addPermissionAttributes(attributes);
992 
993 		//Note: Additional attributes required for permission check (eg. permission details and role qualifiers) will
994 		//be determined server side in the AbstractDataService.isAuthorized method. All that is required here is
995 		//id of the proposal object)
996 		cluProposalRpcServiceAsync.isAuthorized(permissionType, attributes, new KSAsyncCallback<Boolean>(){
997 
998 			@Override
999 			public void handleFailure(Throwable caught) {
1000 				authCallback.isNotAuthorized("Error checking authorization.");
1001 				GWT.log("Error checking proposal authorization.", caught);
1002                 Window.alert("Error Checking Proposal Authorization: "+caught.getMessage());
1003 			}
1004 
1005 			@Override
1006 			public void onSuccess(Boolean result) {
1007 				GWT.log("Succeeded checking auth for permission type '" + permissionType + "' with result: " + result, null);
1008 				if (Boolean.TRUE.equals(result)) {
1009 					authCallback.isAuthorized();
1010 				}
1011 				else {
1012 					authCallback.isNotAuthorized("User is not authorized: " + permissionType);
1013 				}
1014 			}
1015     	});
1016 	}
1017 
1018 	/**
1019 	 * This method adds any permission attributes required for checking permissions
1020 	 */
1021 	public void addPermissionAttributes(Map<String, String> attributes){
1022 		ViewContext viewContext = getViewContext();
1023 
1024 		//Get the id to use to check permissions, this could either be the proposal id or the workflow document id,
1025 		//will pass the id & id type as attributes to permission service.
1026 		if ( (viewContext.getId() != null) && (!"".equals(viewContext.getId())) ) {
1027 			attributes.put(viewContext.getIdType().toString(), viewContext.getId());
1028 		}
1029 		
1030 		//Determine the permission type being checked
1031     	if(viewContext.getId() != null && !viewContext.getId().isEmpty()){
1032     		if(viewContext.getIdType() != IdType.COPY_OF_OBJECT_ID && viewContext.getIdType() != IdType.COPY_OF_KS_KEW_OBJECT_ID){
1033     			//Id provided, and not a copy id, so opening an existing proposal
1034     			viewContext.setPermissionType(PermissionType.OPEN);
1035     			attributes.put(StudentIdentityConstants.DOCUMENT_TYPE_NAME, LUConstants.PROPOSAL_TYPE_COURSE_CREATE);
1036     		} else{
1037     			//Copy id provided, so creating a proposal for modification
1038     			viewContext.setPermissionType(PermissionType.INITIATE);
1039     			attributes.put(StudentIdentityConstants.DOCUMENT_TYPE_NAME, LUConstants.PROPOSAL_TYPE_COURSE_MODIFY);
1040     		}
1041     	} else{
1042     		//No id in view context, so creating new empty proposal
1043     		viewContext.setPermissionType(PermissionType.INITIATE);
1044 			attributes.put(StudentIdentityConstants.DOCUMENT_TYPE_NAME, LUConstants.PROPOSAL_TYPE_COURSE_CREATE);    		
1045     	}    	
1046 	}
1047 	
1048 	@Override
1049 	public boolean isAuthorizationRequired() {
1050 		return true;
1051 	}
1052 
1053 	@Override
1054 	public void setAuthorizationRequired(boolean required) {
1055 		throw new UnsupportedOperationException();
1056 	}
1057 
1058     protected void setHeaderTitle(){
1059     	String title;
1060     	if (cluProposalModel.get(cfg.getProposalTitlePath()) != null){
1061     		title = getProposalTitle();
1062     	}
1063     	else{
1064     		title = "New Course (Proposal)";
1065     	}
1066     	this.setContentTitle(title);
1067     	this.setName(title);
1068     	WindowTitleUtils.setContextTitle(title);
1069 		currentTitle = title;
1070     }
1071 
1072 	@Override
1073 	public WorkflowUtilities getWfUtilities() {
1074 		return workflowUtil;
1075 	}
1076 
1077 	@Override
1078 	public void beforeViewChange(final Enum<?> viewChangingTo, final Callback<Boolean> okToChange) {
1079 		//Make sure the course information data is bound before viewing any other sections for cross field constraints
1080 		final Callback<Boolean> reallyOkToChange = new Callback<Boolean>(){
1081 			@Override
1082 			public void exec(Boolean result) {
1083 				if(result){
1084 					if(CourseSections.GOVERNANCE.equals(viewChangingTo)){
1085 						getView(CourseSections.COURSE_INFO, new Callback<View>(){
1086 							@Override
1087 							public void exec(final View view) {
1088 								if(view!=null && view instanceof SectionView){
1089 									requestModel(new ModelRequestCallback<DataModel>(){
1090 										public void onModelReady(DataModel model) {
1091 											((SectionView)view).updateWidgetData(model);
1092 											okToChange.exec(true);
1093 										}
1094 										public void onRequestFail(Throwable cause) {
1095 											okToChange.exec(false);
1096 										}
1097 									});
1098 								}else{
1099 									okToChange.exec(true);
1100 								}
1101 							}});
1102 					} else
1103 						okToChange.exec(true);					
1104 				} else 	
1105 					okToChange.exec(false);
1106 			}
1107 		};
1108 		
1109 		//We do this check here because theoretically the subcontroller views
1110 		//will display their own messages to the user to give them a reason why the view
1111 		//change has been cancelled, otherwise continue to check for reasons not to change
1112 		//with this controller
1113 		super.beforeViewChange(viewChangingTo, new Callback<Boolean>(){
1114 
1115 			@Override
1116 			public void exec(Boolean result) {
1117 				if(result){
1118 					if(getCurrentView() instanceof SectionView && ((SectionView)getCurrentView()).isDirty()){
1119 						ButtonGroup<YesNoCancelEnum> buttonGroup = new YesNoCancelGroup();
1120 						final ButtonMessageDialog<YesNoCancelEnum> dialog = new ButtonMessageDialog<YesNoCancelEnum>("Warning", "You may have unsaved changes.  Save changes?", buttonGroup);
1121 						buttonGroup.addCallback(new Callback<YesNoCancelEnum>(){
1122 
1123 							@Override
1124 							public void exec(YesNoCancelEnum result) {
1125 								switch(result){
1126 									case YES:
1127 										dialog.hide();
1128 										final SaveActionEvent e = new SaveActionEvent();
1129 										e.setActionCompleteCallback(new ActionCompleteCallback(){
1130 
1131 											@Override
1132 											public void onActionComplete(ActionEvent action) {
1133 												if(e.isSaveSuccessful()){
1134 													reallyOkToChange.exec(true);
1135 												}
1136 												else{
1137 													reallyOkToChange.exec(false);
1138 												}
1139 											}
1140 											
1141 										});
1142 										fireApplicationEvent(e);
1143 										break;
1144 									case NO:
1145 										//Force a model request from server
1146 										getCurrentModel(new ModelRequestCallback<DataModel>(){
1147 
1148 											@Override
1149 											public void onModelReady(DataModel model) {
1150 												if (getCurrentView()instanceof Section){
1151 							    					((Section) getCurrentView()).resetFieldInteractionFlags();
1152 												}
1153 												reallyOkToChange.exec(true);
1154 												dialog.hide();
1155 											}
1156 
1157 											@Override
1158 											public void onRequestFail(Throwable cause) {
1159 												//TODO Is this correct... do we want to stop view change if we can't restore the data?  Possibly traps the user
1160 												//if we don't it messes up saves, possibly warn the user that it failed and continue?
1161 												reallyOkToChange.exec(false);
1162 												dialog.hide();
1163 												GWT.log("Unable to retrieve model for data restore on view change with no save", cause);
1164 											}},
1165 											NO_OP_CALLBACK);
1166 
1167 										break;
1168 									case CANCEL:
1169 										reallyOkToChange.exec(false);
1170 										dialog.hide();
1171 										// Because this event fires after the history change event we need to "undo" the history events. 
1172 										HistoryManager.logHistoryChange();  
1173 										break;
1174 								}
1175 							}
1176 						});
1177 						dialog.addCloseLinkClickHandler(new ClickHandler() {
1178                             
1179                             @Override
1180                             public void onClick(ClickEvent event) {
1181                                 okToChange.exec(false);
1182                                 dialog.hide();
1183                                 // Because this event fires after the history change event we need to "undo" the history events. 
1184                                 HistoryManager.logHistoryChange();  
1185                             }
1186                         });
1187 						dialog.show();
1188 					}
1189 					else{
1190 						reallyOkToChange.exec(true);
1191 					}
1192 				}
1193 				else{
1194 					reallyOkToChange.exec(false);
1195 				}
1196 			}
1197 		});
1198 	}
1199 	
1200     public KSButton getSaveButton(){
1201     	if(currentDocType != LUConstants.PROPOSAL_TYPE_COURSE_MODIFY && currentDocType != LUConstants.PROPOSAL_TYPE_COURSE_MODIFY_ADMIN){
1202 	        return new KSButton("Save and Continue", new ClickHandler(){
1203 	                    public void onClick(ClickEvent event) {
1204 	                    	CourseProposalController.this.fireApplicationEvent(new SaveActionEvent(true));
1205 	                    }
1206 	                });
1207     	}
1208     	else{
1209     		return new KSButton("Save", new ClickHandler(){
1210                 public void onClick(ClickEvent event) {
1211                     CourseProposalController.this.fireApplicationEvent(new SaveActionEvent(false));
1212                 }
1213             });
1214     	}
1215     }
1216     
1217     public KSButton getCancelButton(final Enum<?> summaryView){
1218     	
1219         return new KSButton("Cancel", ButtonStyle.ANCHOR_LARGE_CENTERED, new ClickHandler(){
1220                     public void onClick(ClickEvent event) {
1221                     	if(!isNew){
1222                     		CourseProposalController.this.showView(summaryView);
1223                     	}
1224                     	else{
1225                     		Application.navigate(AppLocations.Locations.CURRICULUM_MANAGEMENT.getLocation());
1226                     	}
1227                     }
1228                 });
1229 
1230     }
1231 	
1232 	@Override
1233 	public void onHistoryEvent(String historyStack) {
1234 		super.onHistoryEvent(historyStack);
1235 		//we dont want to add proposals that are brand new before saving, or copy addresses (as they will initiate
1236 		//the modify/copy logic again if called)
1237 		if(cluProposalModel.get(cfg.getProposalTitlePath()) != null && 
1238 				this.getViewContext().getIdType() != IdType.COPY_OF_OBJECT_ID){
1239 			RecentlyViewedHelper.addCurrentDocument(getProposalTitle());
1240 		}
1241 	}
1242 	
1243 	private String getProposalTitle(){
1244 		StringBuffer sb = new StringBuffer();
1245 		sb.append(cluProposalModel.get(cfg.getProposalTitlePath()));
1246 		sb.append(" (Proposal)");
1247 		return sb.toString();
1248 	}
1249 
1250     public String getCourseId(){
1251         return cluProposalModel.<String>get("id");
1252     }
1253 
1254     public String getCourseState(){
1255         return cluProposalModel.<String>get("state");
1256     }
1257 
1258     public boolean isNew() {
1259         return isNew;
1260     }
1261 
1262     public CourseRequirementsDataModel getReqDataModel() {
1263         return reqDataModel;
1264     }
1265 
1266     public CourseRequirementsDataModel getReqDataModelComp() {
1267         return reqDataModelComp;
1268     }
1269 
1270     @Override
1271     public DataModel getExportDataModel() {
1272         return cluProposalModel;
1273     }
1274     
1275     /**
1276      * 
1277      * @see org.kuali.student.common.ui.client.reporting.ReportExport#getExportTemplateName()
1278      */
1279     @Override
1280     public String getExportTemplateName() {
1281         if (LUConstants.PROPOSAL_TYPE_COURSE_CREATE.equals(currentDocType)){
1282             return "base.template";
1283         }
1284         return "proposal.template";
1285     }
1286     
1287     @Override
1288     public List<ExportElement> getExportElementsFromView() {
1289         List<ExportElement> exportElements = new ArrayList<ExportElement>();
1290         if (this.getCurrentViewEnum().equals(CourseSections.SUMMARY)) {      
1291             SummaryTableSection tableSection = this.cfg.getSummaryConfigurer().getTableSection();
1292             ExportElement heading = new ExportElement();
1293             heading.setFieldLabel("");
1294             heading.setFieldValue(cluProposalModel.getModelName());
1295             heading.setFieldValue2(comparisonModel.getModelName());
1296             exportElements.add(heading);
1297             exportElements.addAll(ExportUtils.getDetailsForWidget(tableSection.getSummaryTable()));
1298         }
1299         return exportElements;
1300     }
1301     
1302     @Override
1303     public boolean isExportButtonActive() {
1304         if (this.getCurrentViewEnum() != null && this.getCurrentViewEnum().equals(CourseSections.SUMMARY)) {   
1305             return true;
1306         } else {
1307             return false;
1308         }
1309             
1310     }
1311     
1312     /**
1313      * This method is used to determine which state the dto will be when making the save call. The information
1314      * is used by the metadata service (along with workflow node) to determine the appropriate required indicators
1315      * for field elements.
1316      */
1317     protected String getStateforSaveAction(DataModel model){
1318 		//The state for existing course proposal will be the state set on existing course
1319     	String state = (String)model.get(CreditCourseConstants.STATE);
1320 		
1321     	//If null, this means it is a new course proposal, in which case we will default to configurer state
1322     	//which should be DRAFT
1323     	if (state == null){
1324 			state = cfg.getState();
1325 		}
1326     	
1327 		return state;
1328     }
1329     
1330     public String getMessage(String courseMessageKey) {
1331     	String msg = Application.getApplicationContext().getMessage(MSG_GROUP, courseMessageKey);
1332     	if (msg == null) {
1333     		msg = courseMessageKey;
1334     	}
1335     	return msg;
1336     }
1337 
1338     /**
1339      * This method exists to allow the save/get implementations defined in this CourseProposalController
1340      * in this controller to be reused in the CourseAdminWithoutVersion controller. This is in an attempt
1341      * prevent duplication of a large chunk of code in the CourseAdminWithoutVersion controller. Rather than
1342      * have a save wrapped with proposal information, the  CourseAdminWithoutVersion will override this method
1343      * and return the standard course rpc service which does not use filters for proposal data. 
1344      * 
1345      * @return the course rpc service to use
1346      */
1347     protected  BaseDataOrchestrationRpcServiceAsync getCourseProposalRpcService(){
1348     	return cluProposalRpcServiceAsync;
1349     }
1350     
1351     public CourseProposalConfigurer getCourseProposalConfigurer() {
1352     	return cfg;
1353     }
1354 }