View Javadoc

1   package org.kuali.student.lum.lu.ui.course.client.requirements;
2   
3   import java.util.HashMap;
4   import java.util.HashSet;
5   import java.util.LinkedHashMap;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.Set;
9   
10  import org.kuali.student.r2.common.dto.RichTextInfo;
11  import org.kuali.student.r1.core.statement.dto.ReqCompFieldInfo;
12  import org.kuali.student.r1.core.statement.dto.ReqComponentInfo;
13  import org.kuali.student.r1.core.statement.dto.StatementTreeViewInfo;
14  import org.kuali.student.r1.core.statement.dto.StatementTypeInfo;
15  import org.kuali.student.common.ui.client.application.Application;
16  import org.kuali.student.common.ui.client.configurable.mvc.SectionTitle;
17  import org.kuali.student.common.ui.client.configurable.mvc.views.SectionView;
18  import org.kuali.student.common.ui.client.configurable.mvc.views.VerticalSectionView;
19  import org.kuali.student.common.ui.client.mvc.Callback;
20  import org.kuali.student.common.ui.client.mvc.Controller;
21  import org.kuali.student.common.ui.client.mvc.DataModel;
22  import org.kuali.student.common.ui.client.mvc.Model;
23  import org.kuali.student.common.ui.client.mvc.ModelRequestCallback;
24  import org.kuali.student.common.ui.client.mvc.View;
25  import org.kuali.student.common.ui.client.widgets.KSButton;
26  import org.kuali.student.common.ui.client.widgets.KSButtonAbstract;
27  import org.kuali.student.common.ui.client.widgets.buttongroups.ButtonEnumerations;
28  import org.kuali.student.common.ui.client.widgets.dialog.ConfirmationDialog;
29  import org.kuali.student.common.ui.client.widgets.field.layout.button.ActionCancelGroup;
30  import org.kuali.student.common.ui.client.widgets.field.layout.element.AbbrButton;
31  import org.kuali.student.common.ui.client.widgets.field.layout.element.LabelPanel;
32  import org.kuali.student.common.ui.client.widgets.field.layout.element.SpanPanel;
33  import org.kuali.student.common.ui.client.widgets.field.layout.element.AbbrButton.AbbrButtonType;
34  import org.kuali.student.core.statement.ui.client.widgets.rules.RulesUtil;
35  import org.kuali.student.core.statement.ui.client.widgets.rules.SubrulePreviewWidget;
36  import org.kuali.student.lum.common.client.widgets.AppLocations;
37  import org.kuali.student.lum.common.client.widgets.CluSetDetailsWidget;
38  import org.kuali.student.lum.common.client.widgets.CluSetRetriever;
39  import org.kuali.student.lum.common.client.widgets.CluSetRetrieverImpl;
40  import org.kuali.student.lum.lu.ui.course.client.configuration.AbstractCourseConfigurer;
41  import org.kuali.student.lum.lu.ui.course.client.configuration.CourseProposalConfigurer;
42  import org.kuali.student.lum.lu.ui.course.client.controllers.CourseProposalController;
43  
44  import com.google.gwt.core.client.GWT;
45  import com.google.gwt.event.dom.client.ClickEvent;
46  import com.google.gwt.event.dom.client.ClickHandler;
47  import com.google.gwt.user.client.Window;
48  import com.google.gwt.user.client.ui.FlowPanel;
49  import com.google.gwt.user.client.ui.HorizontalPanel;
50  import com.google.gwt.user.client.ui.VerticalPanel;
51  import com.google.gwt.user.client.ui.Widget;
52  
53  public class CourseRequirementsSummaryView extends VerticalSectionView {
54  
55      private static CluSetRetriever cluSetRetriever = new CluSetRetrieverImpl();
56  
57      //view's widgets
58      protected FlowPanel layout = new FlowPanel();
59      protected ActionCancelGroup actionCancelButtons = new ActionCancelGroup(
60              ButtonEnumerations.SaveContinueCancelEnum.SAVE_CONTINUE, ButtonEnumerations.SaveContinueCancelEnum.CANCEL);
61      protected Map<String, Widget> addButtonsList = new HashMap<String, Widget>();
62  
63      //view's data
64      protected CourseRequirementsViewController parentController;
65      protected CourseRequirementsDataModel rules;
66      protected boolean isReadOnly;
67      protected boolean showSaveButtons;
68      private static int tempProgReqInfoID = 9999;
69      public static final String NEW_STMT_TREE_ID = "NEWSTMTTREE";
70      public static final String NEW_REQ_COMP_ID = "NEWREQCOMP";    
71  
72      protected Map<String, SpanPanel> perCourseRequisiteTypePanel = new LinkedHashMap<String, SpanPanel>();
73      
74      protected boolean displayInitialized = false;
75  
76      public CourseRequirementsSummaryView() {
77          super();
78      }
79  
80      public CourseRequirementsSummaryView(final CourseRequirementsViewController parentController, Enum<?> viewEnum, String name,
81                                                              String modelId, CourseRequirementsDataModel rulesData, boolean isReadOnly, boolean showSaveButtons) {
82          super(viewEnum, name, modelId);
83          this.parentController = parentController;
84          rules = rulesData;
85          rules.setInitialized(false);
86          this.isReadOnly = isReadOnly;
87          this.showSaveButtons = showSaveButtons;
88          if (!isReadOnly && showSaveButtons) {
89              setupSaveCancelButtons();
90          }
91      }
92  
93      public void init(final CourseRequirementsViewController parentController, Enum<?> viewEnum, String name,
94              String modelId, CourseRequirementsDataModel rulesData, boolean isReadOnly, boolean showSaveButtons) {
95          super.init(viewEnum, name, modelId, true);
96          this.parentController = parentController;
97          rules = rulesData;
98          rules.setInitialized(false);
99          this.isReadOnly = isReadOnly;
100         this.showSaveButtons = showSaveButtons;
101         if (!isReadOnly && showSaveButtons) {
102             setupSaveCancelButtons();
103         }
104     }
105 
106     @Override
107     public boolean isDirty() {
108         return rules.isDirty();
109     }
110 
111     @Override
112     public void beforeShow(final Callback<Boolean> onReadyCallback) {
113 
114         //only when user wants to see rules then load requirements from database if they haven't been loaded yet
115         if (!rules.isInitialized()) {
116             rules.retrieveCourseRequirements(AbstractCourseConfigurer.COURSE_PROPOSAL_MODEL, new Callback<Boolean>() {
117                 @Override
118                 public void exec(Boolean result) {
119                     if (result) {
120                         displayRules();
121                     }
122                     onReadyCallback.exec(result);
123                 }
124             });
125             return;
126         }
127 
128         //for read-only view, we don't need to worry about rules being added or modified
129         if (isReadOnly) {
130             displayRules();            
131             onReadyCallback.exec(true);
132             return;
133         }
134         
135         //Check if the display was ever drawn (it's being set to false in ()
136         if(!displayInitialized){
137             displayRules();
138         }
139 
140         //see if we need to update a rule if user is returning from rule manage screen
141         parentController.getView(CourseRequirementsViewController.CourseRequirementsViews.MANAGE, new Callback<View>(){
142 			@Override
143 			public void exec(View result) {
144 				CourseRequirementsManageView manageView = (CourseRequirementsManageView) result;
145                 
146 				//return if user did not added or updated a rule
147                 if (!manageView.isDirty() || !manageView.isUserClickedSaveButton()) {
148                     rules.removeEmptyRules();
149                     onReadyCallback.exec(true);
150                     return;
151                 }
152 
153                 //update the rule because user added or edited the rule
154                 ((SectionView)parentController.getCurrentView()).setIsDirty(false);
155                 manageView.setUserClickedSaveButton(false);
156 
157                 //if rule storage updated successfully, update the display as well
158                 StatementTreeViewInfo affectedRule = rules.updateRules(manageView.getRuleTree(), manageView.getInternalCourseReqID(), manageView.isNewRule());
159                 updateRequirementWidgets(affectedRule);
160                 
161 		        onReadyCallback.exec(true);
162 			}
163 		});        
164     }
165 
166     public void revertRuleChanges() {
167         rules.revertRuleChanges();
168         displayRules();
169     }
170 
171     protected void updateRequirementWidgets(StatementTreeViewInfo rule) {
172         if (rule != null) {
173             StatementTypeInfo affectedStatementTypeInfo = rules.getStmtTypeInfo(rule.getType());
174             SpanPanel reqPanel = perCourseRequisiteTypePanel.get(affectedStatementTypeInfo.getId());
175 
176             //don't show 'add rule' button if we have one rule already
177             addButtonsList.get(rule.getType()).setVisible(false);
178 
179             //if this is a new rule then add it to the panel
180             if (reqPanel.getWidgetCount() == 0) {
181                 SubrulePreviewWidget newRulePreviewWidget = addCourseRequisite(reqPanel, rule);
182                 reqPanel.add(newRulePreviewWidget);
183                 return;
184             }
185 
186             for (int i = 0; i < reqPanel.getWidgetCount(); i++) {
187                 SubrulePreviewWidget subrulePreviewWidget = (SubrulePreviewWidget)reqPanel.getWidget(i);
188                 if (!rule.getStatements().isEmpty() || !rule.getReqComponents().isEmpty()) {
189                     SubrulePreviewWidget newRulePreviewWidget = addCourseRequisite(reqPanel, rule);
190                     reqPanel.insert(newRulePreviewWidget, i);
191                 }   
192                 reqPanel.remove(subrulePreviewWidget);
193                 if (rule.getStatements().isEmpty() && rule.getReqComponents().isEmpty())                 
194                     addButtonsList.get(rule.getType()).setVisible(true);                    
195                 break; //there should be only one rule per requisite type
196             }
197         }
198     }    
199 
200     public void displayRules() {       
201         remove(layout);
202         layout.clear();
203 
204         //display 'Course Requirements' page title (don't add if read only because the section itself will display the title)
205         if (!isReadOnly) {
206             SectionTitle pageTitle = SectionTitle.generateH2Title("Course Requisites");
207             pageTitle.addStyleName("ks-layout-header");  //make the header orange
208             layout.add(pageTitle);
209         }
210 
211         //iterate and display rules for each Course Requirement type e.g. Pre-Requisites, Co-Requisites, Anti-Requisites
212         boolean firstSubHeader = true;
213         for (StatementTypeInfo stmtTypeInfo : rules.getStmtTypes()) {
214 
215             //Show only headers for top statement types
216             if (isTopStatement(stmtTypeInfo)) {
217                 SectionTitle title = SectionTitle.generateH3Title(stmtTypeInfo.getName());
218                 title.addStyleName("KS-Course-Requisites-Top-Stmt-Header");
219                 layout.add(title);
220                 firstSubHeader = true;
221                 continue;
222             }
223 
224             //create and display one type of Course Requisites section
225             SpanPanel requirementsPanel = new SpanPanel();
226             perCourseRequisiteTypePanel.put(stmtTypeInfo.getId(), requirementsPanel);
227             displayRequirementSectionForGivenType(requirementsPanel, stmtTypeInfo, firstSubHeader);
228             firstSubHeader = false;
229 
230             //now display each requirement for this Course Requisites type; should be only one for courses
231             for (StatementTreeViewInfo ruleInfo : rules.getCourseReqInfo(stmtTypeInfo.getId())) {
232                 if (!ruleInfo.getStatements().isEmpty() || !ruleInfo.getReqComponents().isEmpty()) {
233                     SubrulePreviewWidget subrulePreviewWidget = addCourseRequisite(requirementsPanel, ruleInfo);
234                     requirementsPanel.add(subrulePreviewWidget);
235                 }  
236             }
237         }
238 
239         //save and cancel buttons
240         if (!isReadOnly && showSaveButtons) {
241             layout.add(actionCancelButtons);
242         }
243 
244         addWidget(layout);
245         
246         displayInitialized = true;
247     }
248 
249     protected void displayRequirementSectionForGivenType(final SpanPanel requirementsPanel,
250             final StatementTypeInfo stmtTypeInfo, boolean firstSubHeader) {
251 
252         //display header for this Course Requisites type e.g. Enrollment Eligibility
253         SectionTitle title = SectionTitle.generateH3Title(stmtTypeInfo.getName());
254         title.setStyleName((firstSubHeader ? "KS-Course-Requisites-Preview-Rule-Type-First-Header" : "KS-Course-Requisites-Preview-Rule-Type-Header"));
255         layout.add(title);
256 
257         LabelPanel labelExamples = new LabelPanel(stmtTypeInfo.getDescr());
258         labelExamples.getElement().setAttribute("style", "font-weight: normal; width: 80%;");
259         
260         String examplesHtml = Application.getApplicationContext().getMessage(stmtTypeInfo.getId());
261         
262         if (examplesHtml != null && !examplesHtml.equals("")) {
263         	AbbrButton examples = new AbbrButton(AbbrButtonType.EXAMPLES);
264         	examples.setVisible(true);
265         	examples.setHoverHTML(examplesHtml);
266         	examples.getHoverPopup().addStyleName("ks-example-popup");
267         	labelExamples.add(examples);
268         }
269         
270         HorizontalPanel spacer0 = new HorizontalPanel();
271         spacer0.addStyleName("KS-Course-Requisites-Button-Spacer");
272         labelExamples.add(spacer0);
273  
274         layout.add(labelExamples);
275         
276         //display "Add Rule" button if user is in 'edit' mode OR a rule is already defined
277         final String stmtId = stmtTypeInfo.getId();
278         if (!isReadOnly) { // && rules.getCourseReqInfo(stmtId).isEmpty()) {
279             KSButton addCourseReqButton = new KSButton("Add " + stmtTypeInfo.getName(), KSButtonAbstract.ButtonStyle.FORM_SMALL);
280             addCourseReqButton.addClickHandler(new ClickHandler(){
281                 public void onClick(ClickEvent event) {
282 
283                     storeRules(false, new Callback<Boolean>() {
284                         @Override
285                         public void exec(Boolean courseExists) {
286                             if (courseExists) {
287                                 final StatementTreeViewInfo newRule = new StatementTreeViewInfo();
288                                 newRule.setId(generateStatementTreeId());
289                                 newRule.setType(stmtId);
290                                 RichTextInfo text = new RichTextInfo();
291                                 text.setPlain("");
292                                 newRule.setDesc(text);
293                                 parentController.getView(
294                                         CourseRequirementsViewController.CourseRequirementsViews.MANAGE,
295                                         new Callback<View>() {
296                                     @Override
297                                     public void exec(View result) {
298                                         rules.addRule(newRule);
299                                         ((CourseRequirementsManageView) result).setRuleTree(newRule, true, rules.getInternalCourseReqID(newRule));
300                                         parentController.showView(CourseRequirementsViewController.CourseRequirementsViews.MANAGE);
301                                     }
302                                 });
303                             }
304                         }
305                     });
306                 };
307             });
308             if (!rules.getCourseReqInfo(stmtId).isEmpty()) {
309                 boolean checkReqs = true;
310                 for(StatementTreeViewInfo ruleInfo : rules.getCourseReqInfo(stmtId)) {
311                     if (!ruleInfo.getStatements().isEmpty() || !ruleInfo.getReqComponents().isEmpty()) {
312                         checkReqs = false;
313                         break;
314                     }
315                 }    
316                 addCourseReqButton.setVisible(checkReqs);
317             } else
318                 addCourseReqButton.setVisible(rules.getCourseReqInfo(stmtId).isEmpty());
319             addButtonsList.put(stmtId, addCourseReqButton);
320             layout.add(addCourseReqButton);
321             VerticalPanel spacer = new VerticalPanel();
322             spacer.addStyleName("KS-Course-Requisites-Button-Spacer");            
323             layout.add(spacer);
324         }
325 
326         layout.add(requirementsPanel);
327     }
328 
329     protected SubrulePreviewWidget addCourseRequisite(final SpanPanel requirementsPanel,
330             final StatementTreeViewInfo rule) {
331 
332         Integer internalProgReqID =  rules.getInternalCourseReqID(rule);
333         String stmtTypeId = rule.getType();
334         
335         final SubrulePreviewWidget rulePreviewWidget = new SubrulePreviewWidget(rule, isReadOnly, getCluSetWidgetList(rule));
336 
337         addRulePreviewWidgetHandlers(requirementsPanel, rulePreviewWidget, stmtTypeId, internalProgReqID);
338         return rulePreviewWidget;
339     }
340 
341     protected void addRulePreviewWidgetHandlers(final SpanPanel requirementsPanel, final SubrulePreviewWidget subRuleWidget, final String stmtTypeId, final Integer internalProgReqID) {
342 
343         subRuleWidget.addEditButtonClickHandler(new ClickHandler() {
344             public void onClick(ClickEvent event) {
345                 parentController.getView(CourseRequirementsViewController.CourseRequirementsViews.MANAGE, new Callback<View>(){
346                     @Override
347                     public void exec(View result) {
348                         ((CourseRequirementsManageView) result).setRuleTree(rules.getRule(internalProgReqID), false, internalProgReqID);
349                         parentController.showView(CourseRequirementsViewController.CourseRequirementsViews.MANAGE);
350                     }
351                 });                
352             }
353         });
354 		
355         subRuleWidget.addDeleteButtonClickHandler(new ClickHandler() {
356             public void onClick(ClickEvent event) {
357                 //deleting subrule does not delete the requirement (rule) itself
358                 rules.markRuleAsEdited(internalProgReqID);
359                 //remove rule from display and storage
360                 rules.deleteRule(internalProgReqID);
361                 requirementsPanel.remove(subRuleWidget);
362                 addButtonsList.get(stmtTypeId).setVisible(true);
363             }
364         });		   
365     }
366 
367     static public boolean isTopStatement(StatementTypeInfo stmtInfo) {
368         return ((stmtInfo.getAllowedStatementTypes() != null) && !stmtInfo.getAllowedStatementTypes().isEmpty());
369     }
370 
371     static public Map<String, Widget> getCluSetWidgetList(StatementTreeViewInfo rule) {
372         Map<String, Widget> widgetList = new HashMap<String, Widget>();
373         Set<String> cluSetIds = new HashSet<String>();
374         findCluSetIds(rule, cluSetIds);
375         for (String clusetId : cluSetIds) {
376             widgetList.put(clusetId, new CluSetDetailsWidget(clusetId, cluSetRetriever));
377         }
378 
379         return widgetList;
380     }
381 
382     static private void findCluSetIds(StatementTreeViewInfo rule, Set<String> list) {
383 
384         List<StatementTreeViewInfo> statements = rule.getStatements();
385         List<ReqComponentInfo> reqComponentInfos = rule.getReqComponents();
386 
387         if ((statements != null) && (statements.size() > 0)) {
388             // retrieve all statements
389             for (StatementTreeViewInfo statement : statements) {
390                 findCluSetIds(statement, list); // inside set the children of this statementTreeViewInfo
391             }
392         } else if ((reqComponentInfos != null) && (reqComponentInfos.size() > 0)) {
393             // retrieve all req. component LEAFS
394             for (ReqComponentInfo reqComponent : reqComponentInfos) {
395                 List<ReqCompFieldInfo> fieldInfos = reqComponent.getReqCompFields();
396                 for (ReqCompFieldInfo fieldInfo : fieldInfos) {
397                     if (RulesUtil.isCluSetWidget(fieldInfo.getType())) {
398                         list.add(fieldInfo.getValue());
399                     }
400                 }
401             }
402         }
403     }
404 
405     private void setupSaveCancelButtons() {
406         actionCancelButtons.addStyleName("KS-Course-Requisites-Save-Button");
407         actionCancelButtons.addCallback(new Callback<ButtonEnumerations.ButtonEnum>(){
408              @Override
409             public void exec(ButtonEnumerations.ButtonEnum result) {
410                 if (result == ButtonEnumerations.SaveContinueCancelEnum.SAVE_CONTINUE) {
411                     storeRules(true, new Callback<Boolean>() {
412                         @Override
413                         public void exec(Boolean result) {
414                             if (result) {
415                                 CourseProposalController courseController = ((CourseProposalController) parentController.getParentController());
416                                 courseController.showNextViewOnMenu();
417                                 //courseController.getReqDataModel().retrieveStatementTypes(courseController.getCourseId(), Controller.NO_OP_CALLBACK);
418                             }
419                         }
420                     });                    
421                 } else {
422                     if(! ((CourseProposalController)parentController.getController()).isNew()){
423                         (parentController.getController()).showView(CourseProposalConfigurer.CourseSections.SUMMARY);
424                     }
425                     else{
426                         Application.navigate(AppLocations.Locations.CURRICULUM_MANAGEMENT.getLocation());
427                     }
428                 }
429             }
430         });
431     }
432 
433     public void storeRules(final boolean storeRules, final Callback<Boolean> callback) {
434         parentController.requestModel(CourseRequirementsViewController.COURSE_PROPOSAL_MODEL, new ModelRequestCallback<DataModel>() {
435             @Override
436             public void onRequestFail(Throwable cause) {
437                 Window.alert(cause.getMessage());
438                 GWT.log("Unable to retrieve model for course requirements view", cause);
439                 callback.exec(false);
440             }
441             @Override
442             public void onModelReady(DataModel model) {
443                 String courseId = model.getRoot().get("id");
444                 String courseState = model.getRoot().get("stateKey");
445                 if (courseId == null) {
446                     final ConfirmationDialog dialog = new ConfirmationDialog("Submit Course Title", "Before saving rules please submit course proposal title");
447                     dialog.getConfirmButton().addClickHandler(new ClickHandler(){
448                         @Override
449                         public void onClick(ClickEvent event) {
450                             dialog.hide();
451                         }
452                     });
453                     dialog.show();
454                     callback.exec(false);
455                 } else {
456                     if (storeRules) {
457                         rules.updateCourseRequisites(courseId, courseState, new Callback<List<StatementTreeViewInfo>>() {
458                             @Override
459                             public void exec(List<StatementTreeViewInfo> rules) {
460                                 for (StatementTreeViewInfo rule : rules) {
461                                     updateRequirementWidgets(rule);
462                                 }
463                                 callback.exec(true);                                
464                             }
465                         });
466                     } else {
467                         callback.exec(true);                         
468                     }
469                 }
470             }
471         });
472     }
473     
474     static public String generateStatementTreeId() {
475         return (NEW_STMT_TREE_ID + Integer.toString(tempProgReqInfoID++));
476     }
477 }