View Javadoc

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