View Javadoc
1   package org.kuali.student.lum.lu.ui.course.client.controllers;
2   
3   import com.google.gwt.core.client.GWT;
4   import com.google.gwt.event.dom.client.ClickEvent;
5   import com.google.gwt.event.dom.client.ClickHandler;
6   import com.google.gwt.user.client.DOM;
7   import com.google.gwt.user.client.Element;
8   import com.google.gwt.user.client.ui.Widget;
9   import org.kuali.student.common.ui.client.application.Application;
10  import org.kuali.student.common.ui.client.application.ViewContext;
11  import org.kuali.student.common.ui.client.configurable.mvc.FieldDescriptor;
12  import org.kuali.student.common.ui.client.configurable.mvc.sections.Section;
13  import org.kuali.student.common.ui.client.configurable.mvc.views.VerticalSectionView;
14  import org.kuali.student.common.ui.client.event.ActionEvent;
15  import org.kuali.student.common.ui.client.event.SaveActionEvent;
16  import org.kuali.student.common.ui.client.mvc.ActionCompleteCallback;
17  import org.kuali.student.common.ui.client.mvc.Callback;
18  import org.kuali.student.common.ui.client.mvc.DataModel;
19  import org.kuali.student.common.ui.client.mvc.DataModelDefinition;
20  import org.kuali.student.common.ui.client.mvc.ModelRequestCallback;
21  import org.kuali.student.common.ui.client.util.WindowTitleUtils;
22  import org.kuali.student.common.ui.client.widgets.KSButton;
23  import org.kuali.student.common.ui.client.widgets.menus.KSMenuItemData;
24  import org.kuali.student.common.ui.client.widgets.notification.KSNotification;
25  import org.kuali.student.common.ui.client.widgets.notification.KSNotifier;
26  import org.kuali.student.common.ui.shared.IdAttributes.IdType;
27  import org.kuali.student.core.workflow.ui.client.widgets.WorkflowUtilities;
28  import org.kuali.student.lum.common.client.lu.LUUIConstants;
29  import org.kuali.student.lum.common.client.widgets.AppLocations;
30  import org.kuali.student.lum.lu.assembly.data.client.constants.orch.CreditCourseConstants;
31  import org.kuali.student.lum.lu.ui.course.client.configuration.CourseAdminConfigurer;
32  import org.kuali.student.lum.lu.ui.course.client.configuration.CourseProposalConfigurer;
33  import org.kuali.student.lum.lu.ui.course.client.configuration.CourseProposalConfigurer.CourseSections;
34  import org.kuali.student.r1.common.assembly.data.Metadata;
35  import org.kuali.student.r1.common.assembly.data.QueryPath;
36  import org.kuali.student.r1.common.rice.StudentIdentityConstants;
37  import org.kuali.student.r1.common.rice.authorization.PermissionTypeGwt;
38  import org.kuali.student.r2.common.dto.DtoConstants;
39  import org.kuali.student.r2.common.dto.ValidationResultInfo;
40  import org.kuali.student.r2.lum.clu.CLUConstants;
41  
42  import java.util.ArrayList;
43  import java.util.List;
44  import java.util.Map;
45  
46  /**
47   * Controller for create/modify course with proposal wrapper admin screens. This controller uses a different
48   * configurer for admin screens and attempts to reuse as much of the validation, save & retreive logic coded
49   * in the CourseProposalController.  Also it reuses the menu from CourseProposalController and adds click
50   * handlers to button menu to navigate user b/w sections of the same view.
51   * 
52   * @author Will
53   *
54   */
55  public class CourseAdminController extends CourseProposalController{
56  	
57  	//Need to keep track of cancel buttons, so they can be enabled when course has been saved. 
58      protected List<KSButton> cancelButtons = new ArrayList<KSButton>();
59  	
60  	/**
61  	 * Override the intitailzeController method to use CourseAdminConfigurer 
62  	 */
63  	@Override
64  	protected void initializeController() {
65     		
66  		super.cfg = GWT.create(CourseAdminConfigurer.class);
67  		super.proposalPath = cfg.getProposalPath();
68          super.workflowUtil = GWT.create(WorkflowUtilities.class);
69          super.workflowUtil.init(CourseAdminController.this, proposalPath);
70  		
71     		cfg.setState(DtoConstants.STATE_DRAFT.toUpperCase());
72     		cfg.setNextState(DtoConstants.STATE_APPROVED.toUpperCase());
73     		super.setDefaultModelId(cfg.getModelId());
74     		super.registerModelsAndHandlers();
75     		super.addStyleName("ks-course-admin");
76     		currentDocType = CLUConstants.PROPOSAL_TYPE_COURSE_CREATE_ADMIN;
77          setViewContext(getViewContext());
78      }
79  
80  	/**
81  	 * Override the getSaveButton to provide a new set of buttons for the admin screens
82  	 */
83  	@Override
84  	public KSButton getSaveButton(){
85  		KSButton saveButton = new KSButton("Save", new ClickHandler(){
86              public void onClick(ClickEvent event) {
87              	handleButtonClick(DtoConstants.STATE_DRAFT);            	
88              }
89          });		
90  		
91  		saveButton.addStyleName("ks-button-spacing");
92  		return saveButton;
93  	}
94  		
95  	public KSButton getApproveAndActivateButton(){
96  		return new KSButton("Approve and Activate", new ClickHandler(){
97              public void onClick(ClickEvent event) {
98              	handleButtonClick(DtoConstants.STATE_ACTIVE);
99              }
100         });		
101 	}
102 
103 	public KSButton getCancelButton(){
104 		KSButton cancelButton = new KSButton("Cancel Proposal", new ClickHandler(){
105             public void onClick(ClickEvent event) {
106                 if (isNew) {
107                     Application.navigate(AppLocations.Locations.CURRICULUM_MANAGEMENT.getLocation());
108                 } else {
109                     //Cancel the proposal and navigate the user back to curriculum home if cancel was successful.
110                     workflowUtil.cancel(new Callback<Boolean>() {
111                         @Override
112                         public void exec(Boolean result) {
113                             if (result) {
114                                 Application.navigate(AppLocations.Locations.CURRICULUM_MANAGEMENT.getLocation());
115                             }
116                         }
117 
118                     });
119                 }
120             }
121         });
122 	
123 		cancelButton.addStyleName("ks-button-spacing");
124 		cancelButtons.add(cancelButton);
125 		return cancelButton;
126     }
127 	
128 	/**
129 	 * Processes the save, approve, or approve and activate button clicks. The action is determined
130 	 * by the value of the state parameter.
131 	 * 
132 	 * @param state The state to set on the course when saving course data. DRAFT=Save, APPROVED=Approve, and
133 	 * ACTIVE=Approve & Activate
134 	 */
135 	protected void handleButtonClick(final String state){
136 		
137     	//Set state on course before performing save action
138 		cluProposalModel.set(QueryPath.parse(CreditCourseConstants.STATE), state);
139     	
140     	final SaveActionEvent saveActionEvent = getSaveActionEvent(state);
141     	
142     	//Store the rules if save was called
143     	if((String)cluProposalModel.get(CreditCourseConstants.ID)!=null && cfg instanceof CourseAdminConfigurer){
144     		((CourseAdminConfigurer )cfg).getRequisitesSection().storeRules(new Callback<Boolean>(){
145     			public void exec(Boolean result) {
146 					if(result){
147 						doAdminSaveAction(saveActionEvent, state);
148 					}else{
149 						KSNotifier.show("Error saving rules.");
150 					}
151 				}
152     		});
153     	}else{
154             doAdminSaveAction(saveActionEvent, state);    		
155     	}
156 	}
157 		
158 	/**
159 	 * Returns a SaveActionEvent with the appropriate ActionCompleteCallback, which will take additional admin actions once
160 	 * save is complete. The action (i.e. button clicked) is determined by the value of the state parameter 
161 	 * 
162 	 * @param state The state to set on the course when saving course data. DRAFT=Save, ACTIVE=Approve & Activate
163 	 * @return the save event that will be fired based on the button click
164 	 */
165 	private SaveActionEvent getSaveActionEvent(final String state){
166     	final SaveActionEvent saveActionEvent = new SaveActionEvent(false);
167 		if (DtoConstants.STATE_ACTIVE.equalsIgnoreCase(state)){
168     		saveActionEvent.setActionCompleteCallback(new ActionCompleteCallback(){
169 				@Override
170 				public void onActionComplete(ActionEvent actionEvent) {
171 					if (saveActionEvent.isSaveSuccessful()){
172 						workflowUtil.blanketApprove(new Callback<Boolean>(){
173 							@Override
174 							public void exec(Boolean result) {
175 								
176 								final ViewContext viewContext = new ViewContext();
177 				                viewContext.setId((String)cluProposalModel.get(CreditCourseConstants.ID));
178 				                viewContext.setIdType(IdType.OBJECT_ID);															
179 								if (DtoConstants.STATE_ACTIVE.equalsIgnoreCase(state)){
180 									KSNotifier.show("Course approved and activated. It may take a minute or two for course status to be updated. Refresh to see latest status.");
181 									Application.navigate(AppLocations.Locations.VIEW_COURSE.getLocation(), viewContext);
182 								}
183 								
184 							}
185 						});
186 					}      
187 				}
188 	    	});
189     	} else {
190     		//User clicked Save button. When user clicks save, both document upload and cancel should be enabled
191     		saveActionEvent.setActionCompleteCallback(new ActionCompleteCallback(){
192 				@Override
193 				public void onActionComplete(ActionEvent action) {
194 					for (KSButton cancelButton:cancelButtons){
195 						cancelButton.setEnabled(true);
196 						((CourseAdminConfigurer )cfg).getDocumentTool().beforeShow(NO_OP_CALLBACK);
197 					}					
198 				}    			
199     		});
200     	}
201 		
202 		return saveActionEvent;
203 	}
204 	
205 	/**
206 	 * Fires the SaveActionEvent to be handled by the {@link CourseProposalController}. 
207 	 * 
208 	 *  @see CourseProposalController#registerModelsAndHandlers()
209 	 *  @param saveActionEvent SaveActionEvent to fire
210 	 *  @param state The state to set on the course when saving course data. DRAFT=Save, APPROVED=Approve, and
211 	 *  ACTIVE=Approve & Activate
212 	 */
213 	private void doAdminSaveAction(final SaveActionEvent saveActionEvent, String state){
214 		if (DtoConstants.STATE_APPROVED.equalsIgnoreCase(state) || DtoConstants.STATE_ACTIVE.equalsIgnoreCase(state)){
215 			//For Approved action, validate required fields for next (i.e.Approved) state before firing the save action
216 			cluProposalModel.validateNextState((new Callback<List<ValidationResultInfo>>() {
217 	            @Override
218 	            public void exec(List<ValidationResultInfo> result) {
219 	
220 	            	boolean isSectionValid = isValid(result, true);
221 	
222 	            	if(isSectionValid){
223 	            		CourseAdminController.this.fireApplicationEvent(saveActionEvent);            	}
224 	            	else{
225 	            		KSNotifier.add(new KSNotification("Unable to save, please check fields for errors.", false, true, 5000));
226 	            	}
227 	
228 	            }
229 	        }));
230 		} else {
231     		CourseAdminController.this.fireApplicationEvent(saveActionEvent);			
232 		} 		
233 	}
234 	
235 	/**
236 	 * 
237 	 * Override {@link CourseProposalController} because end term should always be editable 
238 	 * in admin screens.
239 	 */
240     protected void progressiveEnableFields() {
241         super.progressiveEnableFields();
242     }
243 	
244 	/**
245      * Override the setHeaderTitle to display proper header title for admin screens
246      */
247 	@Override
248 	protected void setHeaderTitle(){
249     	String title;
250     	if (cluProposalModel.get(cfg.getProposalTitlePath()) != null){
251     		title = getProposalTitle();
252     	}
253     	else{
254     		title = "New Course (Admin Proposal)";
255     	}
256     	this.setContentTitle(title);
257     	this.setName(title);
258     	WindowTitleUtils.setContextTitle(title);
259 		currentTitle = title;
260     }
261     
262 	private String getProposalTitle(){
263 		StringBuilder sb = new StringBuilder();
264 		sb.append(cluProposalModel.get(cfg.getProposalTitlePath()));
265 		sb.append(" (Admin Proposal)");
266 		return sb.toString();
267 	}
268 	
269 	/**
270 	 * This is a special method for CourseAdminController, which adds a menu item to the navigation menu and navigates
271 	 * a user to a section within the view rather than a different view.
272 	 * 
273 	 * @param parentMenu
274 	 * @param sectionName
275 	 * @param widgetId
276 	 * @param widget
277 	 */
278     public void addMenuItemSection(String parentMenu, final String sectionName, final String widgetId, final Widget widget) {    	
279         KSMenuItemData parentItem = null;
280         for (int i = 0; i < topLevelMenuItems.size(); i++) {
281             if (topLevelMenuItems.get(i).getLabel().equals(parentMenu)) {
282                 parentItem = topLevelMenuItems.get(i);
283                 break;
284             }
285         }
286 
287         KSMenuItemData item = new KSMenuItemData(sectionName);
288     	widget.getElement().setId(widgetId);
289     	item.setClickHandler(new ClickHandler(){
290 			@Override
291     	    public void onClick(ClickEvent event) {
292 			    Element element = DOM.getElementById(widgetId);
293 			    scrollToSection(element);
294 			}
295     	});
296 
297         if (parentItem != null) {
298             parentItem.addSubItem(item);
299         } else {
300             topLevelMenuItems.add(item);
301         }
302 
303         menu.refresh();
304     }
305 
306     public native void scrollToSection(Element element) /*-{
307         element.scrollIntoView();
308     }-*/;
309 
310 	@Override
311 	protected void configureScreens(DataModelDefinition modelDefinition, final Callback<Boolean> onReadyCallback) {
312 		super.configureScreens(modelDefinition, previousEndTermConfigurationCallback(onReadyCallback));
313 	}
314 
315 	/**
316 	 * This callback is used to configure the previous end term field after the screens have been configured.
317 	 * 
318 	 * @param onReadyCallback
319 	 * @return
320 	 */
321 	protected Callback<Boolean> previousEndTermConfigurationCallback(final Callback<Boolean> onReadyCallback){
322 		return new Callback<Boolean>(){
323 
324 			@Override
325 			public void exec(final Boolean result) {
326 				requestModel(new ModelRequestCallback<DataModel>(){
327 					@Override
328 					public void onModelReady(DataModel model) {
329 						//In admin screens add the previous end term field to the active dates section and update it's values   
330 						//ONLY when in edit mode. This way it doesn't attempt to retrieve non-existent "Course Info" section;
331 						//only the "Summary" section has been configured in non-edit mode.
332 						if (model.getDefinition().getMetadata().isCanEdit()){
333 							String versionedFromId = model.get("version/versionedFromId");
334 							if(versionedFromId!=null && !versionedFromId.isEmpty()){
335 								//Add the required field 
336 								//See why the required for next state is not set
337 								VerticalSectionView view = (VerticalSectionView) viewMap.get(CourseSections.COURSE_INFO);							
338 								Section activeDatesSection = view.getSection(LUUIConstants.ACTIVE_DATES_LABEL_KEY);
339 								Metadata meta = cfg.getModelDefinition().getMetadata(CourseProposalConfigurer.PROPOSAL_PATH + "/" + CreditCourseConstants.PREV_END_TERM);
340 								if(meta!=null&&meta.getConstraints().get(0)!=null){
341 									meta.getConstraints().get(0).setRequiredForNextState(true);
342 									meta.getConstraints().get(0).setNextState("ACTIVE");
343 								}
344 								FieldDescriptor fd = cfg.addField(activeDatesSection, CourseProposalConfigurer.PROPOSAL_PATH + "/" + CreditCourseConstants.PREV_END_TERM, cfg.generateMessageInfo(LUUIConstants.PROPOSAL_PREV_END_TERM));
345 								
346 								//FIXME: This static method should not live in WorkflowUtilities
347 								WorkflowUtilities.updateCrossField(fd, model);
348 							}
349 						}
350 						
351 						onReadyCallback.exec(result);
352 					}
353 					
354 					@Override
355 					public void onRequestFail(Throwable cause) {
356 						throw new RuntimeException("Error getting model",cause);
357 					}
358 				});
359 			}
360 		};
361 	}
362 	
363 	@Override
364     public void setViewContext(ViewContext viewContext) {
365         //Determine the permission type being checked
366         if (viewContext.getId() != null && !viewContext.getId().isEmpty()) {
367             if (viewContext.getIdType() != IdType.COPY_OF_OBJECT_ID
368                     && viewContext.getIdType() != IdType.COPY_OF_KS_KEW_OBJECT_ID) {
369                 //Id provided, and not a copy id, so opening an existing proposal
370                 viewContext.setPermissionTypeGwt(PermissionTypeGwt.OPEN);
371             } else {
372                 //Copy id provided, so creating a proposal for modification
373                 viewContext.setPermissionTypeGwt(PermissionTypeGwt.INITIATE);
374             }
375         } else {
376             //No id in view context, so creating new empty proposal
377             viewContext.setPermissionTypeGwt(PermissionTypeGwt.INITIATE);
378 
379         }
380         
381         context = viewContext; 
382     }
383 	
384 	/**
385 	 * This method adds any permission attributes required for checking admin permissions
386 	 */
387 	public void addPermissionAttributes(Map<String, String> attributes){
388 		super.addPermissionAttributes(attributes);
389 		
390 		ViewContext viewContext = getViewContext();
391 		
392 		//Determine the permission type being checked
393     	if(viewContext.getId() != null && !viewContext.getId().isEmpty()){
394     		if(viewContext.getIdType() != IdType.COPY_OF_OBJECT_ID && viewContext.getIdType() != IdType.COPY_OF_KS_KEW_OBJECT_ID){
395     			//Id provided, and not a copy id, so opening an existing proposal
396     			attributes.put(StudentIdentityConstants.DOCUMENT_TYPE_NAME, CLUConstants.PROPOSAL_TYPE_COURSE_CREATE_ADMIN);
397     		} else{
398     			//Copy id provided, so creating a proposal for modification
399     			attributes.put(StudentIdentityConstants.DOCUMENT_TYPE_NAME, CLUConstants.PROPOSAL_TYPE_COURSE_MODIFY_ADMIN);
400     		}
401     	} else{
402     		//No id in view context, so creating new empty proposal
403     		attributes.put(StudentIdentityConstants.DOCUMENT_TYPE_NAME, CLUConstants.PROPOSAL_TYPE_COURSE_CREATE_ADMIN);    		
404     	}    	
405 	}
406 	
407 }
408