001 package org.kuali.student.common.ui.client.widgets.rules; 002 003 import java.util.ArrayList; 004 import java.util.List; 005 import java.util.Map; 006 007 import org.kuali.student.common.ui.client.configurable.mvc.SectionTitle; 008 import org.kuali.student.common.ui.client.mvc.Callback; 009 import org.kuali.student.common.ui.client.widgets.KSButton; 010 import org.kuali.student.common.ui.client.widgets.KSButtonAbstract; 011 import org.kuali.student.common.ui.client.widgets.KSLabel; 012 import org.kuali.student.common.ui.client.widgets.dialog.ConfirmationDialog; 013 import org.kuali.student.common.ui.client.widgets.field.layout.element.SpanPanel; 014 import org.kuali.student.core.statement.dto.StatementOperatorTypeKey; 015 import org.kuali.student.core.statement.dto.StatementTreeViewInfo; 016 017 import com.google.gwt.event.dom.client.ClickEvent; 018 import com.google.gwt.event.dom.client.ClickHandler; 019 import com.google.gwt.user.client.ui.FlowPanel; 020 import com.google.gwt.user.client.ui.Widget; 021 022 public class RulePreviewWidget extends FlowPanel { 023 024 //Widgets 025 private SpanPanel rulePanel = new SpanPanel(); 026 private KSButton editButton = new KSButton("Edit", KSButtonAbstract.ButtonStyle.DEFAULT_ANCHOR); 027 private SpanPanel separator = new SpanPanel(" | "); 028 private KSButton deleteButton = new KSButton("Delete", KSButtonAbstract.ButtonStyle.DEFAULT_ANCHOR); 029 private KSButton addSubRuleBtn = new KSButton("Add a Rule", KSButtonAbstract.ButtonStyle.FORM_SMALL); 030 private List<SubrulePreviewWidget> subRulePreviewWidgets = new ArrayList<SubrulePreviewWidget>(); 031 private Map<String, Widget> clusetWidgets; 032 private SectionTitle progReqHeader = SectionTitle.generateH4Title(""); 033 private KSLabel ruleDescLabel = new KSLabel(); 034 private SpanPanel actions = new SpanPanel(); 035 036 //data 037 private StatementTreeViewInfo stmtTreeInfo; //program rule e.g. program completion rule 038 private boolean isReadOnly; 039 private boolean addRuleOperator = false; //first subrule does not have operator following it 040 private Integer internalProgReqID; 041 042 private Callback<SubRuleInfo> editSubRuleCallback; 043 private Callback<Integer> deleteSubRuleCallback; 044 045 public RulePreviewWidget(Integer internalProgReqID, String ruleName, String ruleCreditsText, String ruleDesc, StatementTreeViewInfo stmtTreeInfo, 046 Boolean isReadOnly, Map<String, Widget> clusetWidgets) { 047 super(); 048 this.internalProgReqID = internalProgReqID; 049 this.stmtTreeInfo = stmtTreeInfo; 050 this.isReadOnly = isReadOnly; 051 this.clusetWidgets = clusetWidgets; 052 053 //prepare widgets 054 this.addStyleName("KS-Rule-Preview-Box"); 055 progReqHeader.setStyleName("KS-Rule-Preview-header"); 056 ruleDescLabel.addStyleName("KS-Rule-Preview-Desc"); 057 if (!isReadOnly) { 058 buildEditActionsWidget(); 059 } 060 updateProgInfoFields(ruleName, ruleCreditsText, ruleDesc); 061 062 displayRule(); 063 } 064 065 public void updateProgInfoFields(String ruleName, String ruleCreditsText, String ruleDesc) { 066 progReqHeader.clear(); 067 progReqHeader.setHTML("<b>" + ruleName + "</b>" + " " + ruleCreditsText); 068 //do not show edit,delete etc. if user is only viewing the rule in non-edit mode 069 if (!isReadOnly) { 070 progReqHeader.add(actions); 071 } 072 ruleDescLabel.setText(ruleDesc); 073 } 074 075 private void displayRule() { 076 //start with a header for the entire rule 077 buildRuleHeader(); 078 079 //show sub-rules if we have any 080 if (stmtTreeInfo == null) { 081 displayNoRule(); 082 } 083 084 if ((stmtTreeInfo.getStatements() == null) || (stmtTreeInfo.getStatements().isEmpty())) { 085 if ((stmtTreeInfo.getReqComponents() == null) || (stmtTreeInfo.getReqComponents().isEmpty())) { 086 displayNoRule(); 087 } else { 088 addSubRule(stmtTreeInfo); 089 } 090 } else { 091 for (final StatementTreeViewInfo subTree : stmtTreeInfo.getStatements()) { 092 addSubRule(subTree); 093 } 094 } 095 096 //button below has to be spaced 097 SpanPanel spacer = new SpanPanel(); 098 spacer.addStyleName("KS-Rule-Last-Preview-Spacer"); 099 rulePanel.add(spacer); 100 101 //add a button for user to add additional subrule 102 if (!isReadOnly) { 103 addSubRuleBtn.addStyleName("KS-Rule-Preview-Add-Subrule"); 104 rulePanel.add(addSubRuleBtn); 105 } 106 107 this.add(rulePanel); 108 } 109 110 private void displayNoRule() { 111 KSLabel noRulesAddedYet = new KSLabel("No rules have been added yet."); 112 noRulesAddedYet.addStyleName("KS-Rule-Preview-NoRule"); 113 rulePanel.add(noRulesAddedYet); 114 } 115 116 private void addSubRule(final StatementTreeViewInfo subTree) { 117 //display AND/OR operator between subrules 118 if (addRuleOperator) { 119 buildANDOperator(stmtTreeInfo.getOperator()); 120 } 121 122 final SubrulePreviewWidget newSubRuleWidget = new SubrulePreviewWidget(subTree, isReadOnly, clusetWidgets); 123 subRulePreviewWidgets.add(newSubRuleWidget); 124 125 newSubRuleWidget.addEditButtonClickHandler(new ClickHandler(){ 126 public void onClick(ClickEvent event) { 127 SubRuleInfo subRuleInfo = new SubRuleInfo(); 128 subRuleInfo.setSubrule(subTree); 129 subRuleInfo.setInternalProgReqID(internalProgReqID); 130 editSubRuleCallback.exec(subRuleInfo); 131 } 132 }); 133 134 newSubRuleWidget.addDeleteButtonClickHandler(new ClickHandler(){ 135 public void onClick(ClickEvent event) { 136 final ConfirmationDialog dialog = new ConfirmationDialog("Delete Rule", "Are you sure you want to delete this rule?"); //TODO app context for labels 137 dialog.getConfirmButton().addClickHandler(new ClickHandler(){ 138 @Override 139 public void onClick(ClickEvent event) { 140 removeSubRule(newSubRuleWidget, subTree); 141 dialog.hide(); 142 deleteSubRuleCallback.exec(internalProgReqID); 143 } 144 }); 145 dialog.show(); 146 } 147 }); 148 149 rulePanel.add(newSubRuleWidget); 150 addRuleOperator = true; 151 } 152 153 //user selected to delete this particular requirement 154 public void removeSubRule(SubrulePreviewWidget subRuleWidget, StatementTreeViewInfo subTree) { 155 //update data 156 List<StatementTreeViewInfo> statements = stmtTreeInfo.getStatements(); 157 statements.remove(subTree); 158 subRulePreviewWidgets.remove(subRuleWidget); 159 160 //update display 161 rulePanel.remove(subRuleWidget); 162 163 //remove unnecessary operators 164 int numberOfOperators = 0; 165 boolean lastWidgetIsSubRule = true; 166 boolean foundSubRule = false; 167 int counter = 0; 168 Widget widget = null; 169 Widget widgetToRemove = null; 170 Widget lastOperatorWidget = null; 171 for (Object aRulePanel : rulePanel) { 172 counter++; 173 widget = (Widget) aRulePanel; 174 if (widget instanceof KSLabel) { 175 if (((KSLabel) widget).getText().equals("AND") || ((KSLabel) widget).getText().equals("OR")) { 176 lastWidgetIsSubRule = false; 177 lastOperatorWidget = widget; 178 numberOfOperators++; 179 //remove 'AND' above the first sub-rule or two repeated instances 180 if ((numberOfOperators > 1) || (counter == 1) || !foundSubRule) { 181 widgetToRemove = widget; 182 numberOfOperators--; 183 } 184 } 185 } else if (widget instanceof SubrulePreviewWidget) { 186 foundSubRule = true; 187 lastWidgetIsSubRule = true; 188 numberOfOperators = 0; 189 } 190 } 191 192 if (!lastWidgetIsSubRule) { 193 rulePanel.remove(lastOperatorWidget); 194 } 195 if (widgetToRemove != null) { 196 rulePanel.remove(widgetToRemove); 197 } 198 } 199 200 private void buildRuleHeader() { 201 rulePanel.clear(); 202 rulePanel.addStyleName("KS-Rule-Preview-Box1"); 203 rulePanel.add(progReqHeader); 204 rulePanel.add(ruleDescLabel); 205 206 //build subheading 207 SectionTitle header = SectionTitle.generateH6Title(""); 208 header.setHTML("Must meet <b>all of the following</b> rules"); 209 header.setStyleName("KS-Rule-Preview-header-Subrule"); 210 header.getElement().setAttribute("style", "font-weight: normal"); 211 rulePanel.add(header); 212 } 213 214 private void buildEditActionsWidget() { 215 actions.add(editButton); 216 actions.add(separator); 217 actions.add(deleteButton); 218 actions.addStyleName("KS-Rule-Preview-header-action"); 219 } 220 221 private void buildANDOperator(StatementOperatorTypeKey operator) { 222 KSLabel andLabel = new KSLabel((operator == StatementOperatorTypeKey.AND ? "AND" : "OR")); 223 andLabel.addStyleName("KS-Rule-Preview-Operator"); 224 rulePanel.add(andLabel); 225 } 226 227 public void addProgReqEditButtonClickHandler(ClickHandler handler) { 228 editButton.addClickHandler(handler); 229 } 230 231 public void addProgReqDeleteButtonClickHandler(ClickHandler handler) { 232 deleteButton.addClickHandler(handler); 233 } 234 235 public void addSubRuleAddButtonClickHandler(ClickHandler handler) { 236 addSubRuleBtn.addClickHandler(handler); 237 } 238 239 public void addSubRuleEditButtonClickHandler(Callback<SubRuleInfo> callback) { 240 this.editSubRuleCallback = callback; 241 } 242 243 public void addSubRuleDeleteCallback(Callback<Integer> callback) { 244 this.deleteSubRuleCallback = callback; 245 } 246 247 public StatementTreeViewInfo getStatementTreeViewInfo() { 248 return stmtTreeInfo; 249 } 250 251 public Integer getInternalProgReqID() { 252 return internalProgReqID; 253 } 254 255 public class SubRuleInfo { 256 private StatementTreeViewInfo subrule; 257 258 private Integer internalProgReqID; 259 260 public StatementTreeViewInfo getSubrule() { 261 return subrule; 262 } 263 264 public void setSubrule(StatementTreeViewInfo subrule) { 265 this.subrule = subrule; 266 } 267 268 public Integer getInternalProgReqID() { 269 return internalProgReqID; 270 } 271 272 public void setInternalProgReqID(Integer internalProgReqID) { 273 this.internalProgReqID = internalProgReqID; 274 } 275 } 276 }