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 }