View Javadoc

1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   */
15  
16  /*
17   * Copyright 2009 The Kuali Foundation Licensed under the
18   * Educational Community License, Version 2.0 (the "License"); you may
19   * not use this file except in compliance with the License. You may
20   * obtain a copy of the License at
21   *
22   * http://www.osedu.org/licenses/ECL-2.0
23   *
24   * Unless required by applicable law or agreed to in writing,
25   * software distributed under the License is distributed on an "AS IS"
26   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
27   * or implied. See the License for the specific language governing
28   * permissions and limitations under the License.
29   */
30  package org.kuali.student.lum.lu.ui.course.client.configuration;
31  
32  import com.google.gwt.core.client.GWT;
33  import com.google.gwt.event.dom.client.ClickEvent;
34  import com.google.gwt.event.dom.client.ClickHandler;
35  import com.google.gwt.event.logical.shared.ValueChangeEvent;
36  import com.google.gwt.event.logical.shared.ValueChangeHandler;
37  import com.google.gwt.user.client.ui.Widget;
38  import org.kuali.student.common.ui.client.application.Application;
39  import org.kuali.student.common.ui.client.configurable.mvc.FieldDescriptor;
40  import org.kuali.student.common.ui.client.configurable.mvc.SectionTitle;
41  import org.kuali.student.common.ui.client.configurable.mvc.binding.HasDataValueBinding;
42  import org.kuali.student.common.ui.client.configurable.mvc.binding.ListOfStringBinding;
43  import org.kuali.student.common.ui.client.configurable.mvc.binding.ModelWidgetBinding;
44  import org.kuali.student.common.ui.client.configurable.mvc.binding.ModelWidgetBindingSupport;
45  import org.kuali.student.common.ui.client.configurable.mvc.multiplicity.CompositeConditionOperator;
46  import org.kuali.student.common.ui.client.configurable.mvc.multiplicity.MultiplicityConfiguration;
47  import org.kuali.student.common.ui.client.configurable.mvc.multiplicity.MultiplicityFieldConfiguration;
48  import org.kuali.student.common.ui.client.configurable.mvc.multiplicity.MultiplicityFieldWidgetInitializer;
49  import org.kuali.student.common.ui.client.configurable.mvc.multiplicity.SwapCompositeCondition;
50  import org.kuali.student.common.ui.client.configurable.mvc.multiplicity.SwapCompositeConditionFieldConfig;
51  import org.kuali.student.common.ui.client.configurable.mvc.multiplicity.SwapCondition;
52  import org.kuali.student.common.ui.client.configurable.mvc.sections.BaseSection;
53  import org.kuali.student.common.ui.client.configurable.mvc.sections.CollapsableSection;
54  import org.kuali.student.common.ui.client.configurable.mvc.sections.GroupSection;
55  import org.kuali.student.common.ui.client.configurable.mvc.sections.MultiplicitySection;
56  import org.kuali.student.common.ui.client.configurable.mvc.sections.Section;
57  import org.kuali.student.common.ui.client.configurable.mvc.sections.SwapEventHandler;
58  import org.kuali.student.common.ui.client.configurable.mvc.sections.SwapSection;
59  import org.kuali.student.common.ui.client.configurable.mvc.sections.VerticalSection;
60  import org.kuali.student.common.ui.client.configurable.mvc.views.SectionView;
61  import org.kuali.student.common.ui.client.configurable.mvc.views.VerticalSectionView;
62  import org.kuali.student.common.ui.client.mvc.Controller;
63  import org.kuali.student.common.ui.client.mvc.DataModel;
64  import org.kuali.student.common.ui.client.mvc.DataModelDefinition;
65  import org.kuali.student.common.ui.client.mvc.HasDataValue;
66  import org.kuali.student.common.ui.client.mvc.View;
67  import org.kuali.student.common.ui.client.widgets.KSButton;
68  import org.kuali.student.common.ui.client.widgets.KSButtonAbstract.ButtonStyle;
69  import org.kuali.student.common.ui.client.widgets.KSCharCount;
70  import org.kuali.student.common.ui.client.widgets.KSCheckBox;
71  import org.kuali.student.common.ui.client.widgets.KSDropDown;
72  import org.kuali.student.common.ui.client.widgets.ListOfStringWidget;
73  import org.kuali.student.common.ui.client.widgets.field.layout.element.MessageKeyInfo;
74  import org.kuali.student.common.ui.client.widgets.field.layout.element.SpanPanel;
75  import org.kuali.student.common.ui.client.widgets.field.layout.layouts.FieldLayoutComponent;
76  import org.kuali.student.common.ui.client.widgets.list.KSLabelList;
77  import org.kuali.student.common.ui.client.widgets.list.KSSelectItemWidgetAbstract;
78  import org.kuali.student.common.ui.client.widgets.list.KSSelectedList;
79  import org.kuali.student.common.ui.client.widgets.list.impl.SimpleListItems;
80  import org.kuali.student.common.ui.client.widgets.search.KSPicker;
81  import org.kuali.student.core.comments.ui.client.widgets.commenttool.CommentTool;
82  import org.kuali.student.core.comments.ui.client.widgets.decisiontool.DecisionPanel;
83  import org.kuali.student.core.document.ui.client.widgets.documenttool.DocumentTool;
84  import org.kuali.student.core.workflow.ui.client.views.CollaboratorSectionView;
85  import org.kuali.student.lum.common.client.lo.LOBuilder;
86  import org.kuali.student.lum.common.client.lo.LOBuilderBinding;
87  import org.kuali.student.lum.common.client.lo.LOPicker;
88  import org.kuali.student.lum.common.client.lo.OutlineNode;
89  import org.kuali.student.lum.common.client.lu.LUUIConstants;
90  import org.kuali.student.lum.lu.assembly.data.client.constants.base.RichTextInfoConstants;
91  import org.kuali.student.lum.lu.assembly.data.client.constants.orch.CreditCourseActivityConstants;
92  import org.kuali.student.lum.lu.assembly.data.client.constants.orch.CreditCourseConstants;
93  import org.kuali.student.lum.lu.assembly.data.client.constants.orch.CreditCourseJointsConstants;
94  import org.kuali.student.lum.lu.ui.course.client.controllers.CourseProposalController;
95  import org.kuali.student.lum.lu.ui.course.client.requirements.CourseRequirementsViewController;
96  import org.kuali.student.r1.common.assembly.data.Data;
97  import org.kuali.student.r1.common.assembly.data.Data.Value;
98  import org.kuali.student.r1.common.assembly.data.Metadata;
99  import org.kuali.student.r1.common.assembly.data.QueryPath;
100 import org.kuali.student.r1.core.statement.dto.StatementTypeInfo;
101 import org.kuali.student.r2.common.dto.DtoConstants;
102 
103 import java.util.ArrayList;
104 import java.util.Arrays;
105 import java.util.HashMap;
106 import java.util.List;
107 import java.util.Map;
108 
109 
110 /**
111  * This is the configuration factory class for creating a proposal.
112  *
113  * @author Kuali Student Team
114  */
115 public class CourseProposalConfigurer extends AbstractCourseConfigurer {
116 
117     protected boolean WITH_DIVIDER = true;
118     protected boolean NO_DIVIDER = false;
119 
120     public static final String PROPOSAL_PATH = "proposal";
121     public static final String PROPOSAL_TITLE_PATH = "proposal/name";
122     public static final String COURSE_TITLE_PATH = "/courseTitle";
123 
124 
125     protected DocumentTool documentTool;
126     protected CourseSummaryConfigurer summaryConfigurer;
127 
128     protected List<StatementTypeInfo> stmtTypes;
129 
130     public static final String COURSE = "";
131 
132     public enum CourseSections {
133         CLU_BEGIN, PEOPLE_PERMISSONS, SUMMARY, AUTHORS_RATIONALE, GOVERNANCE, COURSE_LOGISTICS, COURSE_INFO, LEARNING_OBJECTIVES,
134         COURSE_REQUISITES, ACTIVE_DATES, FINANCIALS, ATTACHMENTS, COMMENTS, DECISIONS, DOCUMENTS,
135         PROGRAM_INFO, ASSEMBLER_TEST, WF_APPROVE_DIALOG
136     }
137 
138     public void setStatementTypes(List<StatementTypeInfo> stmtTypes) {
139         this.stmtTypes = stmtTypes;
140     }
141 
142     /**
143      * Sets up all the views, sections, and views of the CourseProposalController.  This should be called
144      * once for initialization and setup per CourseProposalController instance.
145      *
146      * @param layout
147      */
148     public void configure(final CourseProposalController layout) {
149         type = "course";
150         state = DtoConstants.STATE_DRAFT;
151         groupName = LUUIConstants.COURSE_GROUP_NAME;
152 
153         if (modelDefinition.getMetadata().isCanEdit()) {
154             addCluStartSection(layout);
155             String sections = getLabel(LUUIConstants.COURSE_SECTIONS);
156 
157             //ProposalInformation
158             //layout.addSection(new String[] {editTabLabel, getLabel(LUConstants.PROPOSAL_INFORMATION_LABEL_KEY)}, generateAuthorsRationaleSection());
159 
160             layout.addMenu(sections);
161 
162 
163             //Course Content
164             layout.addMenuItem(sections, (SectionView) generateCourseInfoSection(initSectionView(CourseSections.COURSE_INFO, LUUIConstants.INFORMATION_LABEL_KEY)));
165             layout.addMenuItem(sections, (SectionView) generateGovernanceSection(initSectionView(CourseSections.GOVERNANCE, LUUIConstants.GOVERNANCE_LABEL_KEY)));
166             layout.addMenuItem(sections, (SectionView) generateCourseLogisticsSection(initSectionView(CourseSections.COURSE_LOGISTICS, LUUIConstants.LOGISTICS_LABEL_KEY)));
167             layout.addMenuItem(sections, generateLearningObjectivesSection());
168 
169             //Student Eligibility
170             layout.addMenuItem(sections, generateCourseRequisitesSection(layout, true));
171 
172             //Administrative
173             layout.addMenuItem(sections, (SectionView) generateActiveDatesSection(initSectionView(CourseSections.ACTIVE_DATES, LUUIConstants.ACTIVE_DATES_LABEL_KEY)));
174             layout.addMenuItem(sections, (SectionView) generateFinancialsSection(initSectionView(CourseSections.FINANCIALS, LUUIConstants.FINANCIALS_LABEL_KEY)));
175 
176             //Authors & Collaborators
177             layout.addMenuItem(sections, new CollaboratorSectionView(CourseSections.PEOPLE_PERMISSONS, getLabel(LUUIConstants.SECTION_AUTHORS_AND_COLLABORATORS), COURSE_PROPOSAL_MODEL));
178 
179             //Documents
180             documentTool = new DocumentTool(LUUIConstants.REF_DOC_RELATION_PROPOSAL_TYPE, CourseSections.DOCUMENTS, getLabel(LUUIConstants.TOOL_DOCUMENTS_LABEL_KEY));
181             documentTool.setModelDefinition((DataModelDefinition) modelDefinition);
182             layout.addMenuItem(sections, documentTool);
183 
184             //Summary
185             summaryConfigurer = GWT.create(CourseSummaryConfigurer.class);
186             summaryConfigurer.init(type, state, groupName, (DataModelDefinition) modelDefinition, stmtTypes, (Controller) layout, COURSE_PROPOSAL_MODEL);
187             layout.addSpecialMenuItem(summaryConfigurer.generateProposalSummarySection(true), "Review and Submit");
188 
189             //Add common buttons to sections except for sections with specific button behavior
190             List<Enum<?>> excludedViews = new ArrayList<Enum<?>>();
191             excludedViews.add(CourseSections.DOCUMENTS);
192             excludedViews.add(CourseSections.COURSE_REQUISITES);
193             layout.addCommonButton(LUUIConstants.COURSE_SECTIONS, layout.getSaveButton(), excludedViews);
194             layout.addCommonButton(LUUIConstants.COURSE_SECTIONS, layout.getCancelButton(CourseSections.SUMMARY), excludedViews);
195 
196             //Specific buttons for certain views
197             //TODO people and permissions will use a different button than continue
198             layout.addButtonForView(CourseSections.DOCUMENTS, getContinueButton(layout));
199         } else {
200             summaryConfigurer = GWT.create(CourseSummaryConfigurer.class);
201             summaryConfigurer.init(type, state, groupName, (DataModelDefinition) modelDefinition, stmtTypes, (Controller) layout, COURSE_PROPOSAL_MODEL);
202             layout.removeMenuNavigation();
203             layout.addView(summaryConfigurer.generateProposalSummarySection(false));
204         }
205         layout.showPrint(true);
206         layout.setDefaultView(CourseSections.SUMMARY);
207         layout.addContentWidget(layout.getWfUtilities().getProposalStatusLabel());
208         final CommentTool commentTool = new CommentTool(CourseSections.COMMENTS, getLabel(LUUIConstants.TOOL_COMMENTS_LABEL_KEY), "kuali.comment.type.generalRemarks", "Proposal Comments");
209         commentTool.setController(layout);
210 
211         layout.addContentWidget(new KSButton("Comments", ButtonStyle.DEFAULT_ANCHOR, new ClickHandler() {
212 
213             @Override
214             public void onClick(ClickEvent event) {
215                 commentTool.show();
216             }
217         }));
218 
219 
220         final DecisionPanel decisionPanel = new DecisionPanel(CourseSections.DECISIONS, getLabel(LUUIConstants.TOOL_DECISION_LABEL_KEY), "kuali.comment.type.generalRemarks");
221         layout.addView(decisionPanel);
222         layout.addContentWidget(new KSButton("Decisions", ButtonStyle.DEFAULT_ANCHOR, new ClickHandler() {
223 
224             @Override
225             public void onClick(ClickEvent event) {
226                 decisionPanel.show();
227             }
228         }));
229 
230     }
231 
232     protected KSButton getContinueButton(final CourseProposalController layout) {
233         return new KSButton("Continue", new ClickHandler() {
234             public void onClick(ClickEvent event) {
235                 layout.showNextViewOnMenu();
236             }
237         });
238     }
239 
240     public void addCluStartSection(CourseProposalController layout) {
241         VerticalSectionView section = initSectionView(CourseSections.CLU_BEGIN, LUUIConstants.START_LABEL_KEY);
242         section.setController(layout);
243         addField(section, PROPOSAL_TITLE_PATH, generateMessageInfo(LUUIConstants.PROPOSAL_TITLE_LABEL_KEY));
244         addField(section, COURSE + "/" + COURSE_TITLE, generateMessageInfo(LUUIConstants.COURSE_TITLE_LABEL_KEY));        layout.addStartViewPopup(section);
245         layout.getStartPopup().setMaxHeight(600);
246     }
247 
248     protected View generateCourseRequisitesSection(Controller layout, boolean showSaveButtons) {
249         return new CourseRequirementsViewController(layout, getLabel(LUUIConstants.REQUISITES_LABEL_KEY), CourseSections.COURSE_REQUISITES, false, showSaveButtons);
250     }
251 
252     protected Section generateActiveDatesSection(Section section) {
253         //Add this field and hide it so it is available for cross field validation 
254         FieldDescriptor fd = addField(section, PROPOSAL_PATH + "/" + PREV_START_TERM, generateMessageInfo(LUUIConstants.PROPOSAL_PREV_START_TERM));
255         fd.getFieldWidget().setVisible(false);
256         fd.hideLabel();
257 
258         addField(section, COURSE + "/" + START_TERM, generateMessageInfo(LUUIConstants.START_TERM_LABEL_KEY));
259 
260         addField(section, COURSE + "/" + PILOT_COURSE, generateMessageInfo(LUUIConstants.PILOT_COURSE_LABEL_KEY), new KSCheckBox(getLabel(LUUIConstants.PILOT_COURSE_TEXT_LABEL_KEY))).setIgnoreShowRequired(true);
261         addField(section, COURSE + "/" + END_TERM, generateMessageInfo(LUUIConstants.END_TERM_LABEL_KEY)).setIgnoreShowRequired(true);
262 
263         return section;
264     }
265 
266     protected VerticalSection generateActiveDateEndSection() {
267         VerticalSection endDate = initSection(getH3Title(LUUIConstants.END_DATE_LABEL_KEY), WITH_DIVIDER);
268         addField(endDate, COURSE + "/" + EXPIRATION_DATE, generateMessageInfo(LUUIConstants.EXPIRATION_DATE_LABEL_KEY));
269         return endDate;
270     }
271 
272     protected VerticalSection generateActiveDateStartSection() {
273         VerticalSection startDate = initSection(getH3Title(LUUIConstants.START_DATE_LABEL_KEY), WITH_DIVIDER);
274         addField(startDate, COURSE + "/" + CreditCourseConstants.EFFECTIVE_DATE, generateMessageInfo(LUUIConstants.EFFECTIVE_DATE_LABEL_KEY));
275         return startDate;
276     }
277 
278     protected Section generateGovernanceSection(Section section) {
279         addField(section, COURSE + "/" + CAMPUS_LOCATIONS, generateMessageInfo(LUUIConstants.CAMPUS_LOCATION_LABEL_KEY));
280         addField(section, COURSE + "/" + CURRICULUM_OVERSIGHT_ORGS_, generateMessageInfo(LUUIConstants.ACADEMIC_SUBJECT_ORGS_KEY));
281         addField(section, COURSE + "/" + ADMIN_ORGS, generateMessageInfo(LUUIConstants.ADMIN_ORG_LABEL_KEY));
282 
283         return section;
284     }
285 
286     public Section generateCourseInfoSection(Section section) {
287         addField(section, PROPOSAL_TITLE_PATH, generateMessageInfo(LUUIConstants.PROPOSAL_TITLE_LABEL_KEY));
288         addField(section, COURSE + "/" + COURSE_TITLE, generateMessageInfo(LUUIConstants.COURSE_TITLE_LABEL_KEY));
289         addField(section, COURSE + "/" + TRANSCRIPT_TITLE, generateMessageInfo(LUUIConstants.SHORT_TITLE_LABEL_KEY), new KSCharCount(getMetaData(COURSE + "/" + TRANSCRIPT_TITLE)));
290         section.addSection(generateCourseNumberSection());
291         FieldDescriptor instructorsFd = addField(section, COURSE + "/" + INSTRUCTORS, generateMessageInfo(LUUIConstants.INSTRUCTORS_LABEL_KEY));
292         instructorsFd.setWidgetBinding(new KeyListModelWigetBinding("personId"));
293 
294         section.addSection(generateDescriptionRationaleSection());
295 
296         return section;
297     }
298 
299 
300     protected GroupSection generateCourseNumberSection() {
301 
302         //COURSE NUMBER
303         GroupSection courseNumber = new GroupSection(getH4Title(""));
304         courseNumber.addStyleName(LUUIConstants.STYLE_SECTION);
305         courseNumber.addStyleName(LUUIConstants.STYLE_SECTION_DIVIDER);
306         addField(courseNumber, COURSE + "/" + SUBJECT_AREA, generateMessageInfo(LUUIConstants.SUBJECT_CODE_LABEL_KEY));
307         addField(courseNumber, COURSE + "/" + COURSE_NUMBER_SUFFIX, generateMessageInfo(LUUIConstants.COURSE_NUMBER_LABEL_KEY));
308 //        addField(courseNumber, COURSE + "/" + SUBJECT_AREA);
309 //        addField(courseNumber, COURSE + "/" + COURSE_NUMBER_SUFFIX);
310 
311         courseNumber.addSection(generateCrossListed_Ver_Joint_Section());
312 
313         return courseNumber;
314     }
315 
316     protected CollapsableSection generateCrossListed_Ver_Joint_Section() {
317         CollapsableSection result = new CollapsableSection(getLabel(LUUIConstants.CL_V_J_LABEL_KEY));
318 
319 //        addField(result, COURSE + "/" + CROSS_LISTINGS, null, new CrossListedList(COURSE + "/" + CROSS_LISTINGS));
320 //        addField(result, COURSE + "/" + JOINTS, null, new OfferedJointlyList(COURSE + "/" + JOINTS));
321 //        addField(result, COURSE + "/" + VERSIONS, null, new VersionCodeList(COURSE + "/" + VERSIONS));
322         SpanPanel crslabelpan = new SpanPanel();
323         crslabelpan.setStyleName("ks-multiplicity-section-label");
324         crslabelpan.setHTML("Cross Listed Courses");
325         crslabelpan.setVisible(true);
326         result.addWidget(crslabelpan);
327         addMultiplicityFields(result, COURSE + QueryPath.getPathSeparator() + CROSS_LISTINGS,
328                 LUUIConstants.ADD_CROSS_LISTED_LABEL_KEY,
329                 LUUIConstants.CROSS_LISTED_ITEM_LABEL_KEY,
330                 Arrays.asList(
331                         new MultiplicityFieldConfig(
332                                 SUBJECT_AREA,
333                                 LUUIConstants.SUBJECT_CODE_LABEL_KEY, null, null, true),
334                         new MultiplicityFieldConfig(
335                                 COURSE_NUMBER_SUFFIX,
336                                 LUUIConstants.COURSE_NUMBER_LABEL_KEY, null, null, true)),
337                 null,
338                 null, 0);
339         SpanPanel jntlabelpan = new SpanPanel();
340         jntlabelpan.setStyleName("ks-multiplicity-section-label");
341         jntlabelpan.setHTML("Jointly Offered Courses");
342         jntlabelpan.setVisible(true);
343         result.addWidget(jntlabelpan);
344         addMultiplicityFields(result, COURSE + QueryPath.getPathSeparator() + JOINTS,
345                 LUUIConstants.ADD_EXISTING_LABEL_KEY,
346                 LUUIConstants.JOINT_OFFER_ITEM_LABEL_KEY,
347                 Arrays.asList(
348                         new MultiplicityFieldConfig(
349                                 CreditCourseJointsConstants.COURSE_ID,
350                                 LUUIConstants.COURSE_NUMBER_OR_TITLE_LABEL_KEY, null, null, true)),
351                 null,
352                 null, 0);
353         SpanPanel vsnlabelpan = new SpanPanel();
354         vsnlabelpan.setStyleName("ks-multiplicity-section-label");
355         vsnlabelpan.setHTML("Version Codes");
356         vsnlabelpan.setVisible(true);
357         result.addWidget(vsnlabelpan);
358         addMultiplicityFields(result, COURSE + QueryPath.getPathSeparator() + VERSIONS,
359                 LUUIConstants.ADD_VERSION_CODE_LABEL_KEY,
360                 LUUIConstants.VERSION_CODE_LABEL_KEY,
361                 Arrays.asList(
362                         new MultiplicityFieldConfig(
363                                 "variationCode",
364                                 LUUIConstants.VERSION_CODE_LABEL_KEY, null, null, true),
365                         new MultiplicityFieldConfig(
366                                 "variationTitle",
367                                 LUUIConstants.TITLE_LABEL_KEY, null, null, true)
368                 ),
369                 null,
370                 null, 0);
371         if (this.getClass().getName().contains("CourseProposalConfigurer"))
372             result.getLayout().setVisible(true);
373         else {
374             result.getLayout().setVisible(false);
375         }
376         return result;
377     }
378 
379     protected void addFeeMultiplicityFields(Section section,
380                                             String path, String addItemlabelMessageKey,
381                                             String itemLabelMessageKey, List<MultiplicityFieldConfig> fieldConfigs,
382                                             Map<SwapCompositeCondition, List<SwapCompositeConditionFieldConfig>> swappableFieldsDefinition,
383                                             List<String> deletionParentKeys) {
384         MultiplicityConfiguration config = setupMultiplicityConfig(
385                 MultiplicityConfiguration.MultiplicityType.GROUP,
386                 MultiplicityConfiguration.StyleType.TOP_LEVEL_GROUP,
387                 path, addItemlabelMessageKey, itemLabelMessageKey,
388                 fieldConfigs, swappableFieldsDefinition, deletionParentKeys);
389         MultiplicitySection ms = null;
390         ms = new MultiplicitySection(config, swappableFieldsDefinition, deletionParentKeys);
391         section.addSection(ms);
392 
393     }
394 
395     protected MultiplicityConfiguration setupMultiplicityConfig(
396             MultiplicityConfiguration.MultiplicityType multiplicityType,
397             MultiplicityConfiguration.StyleType styleType,
398             String path, String addItemlabelMessageKey,
399             String itemLabelMessageKey, List<MultiplicityFieldConfig> fieldConfigs,
400             Map<SwapCompositeCondition, List<SwapCompositeConditionFieldConfig>> swappableFieldsDefinition,
401             List<String> deletionParentKeys) {
402         QueryPath parentPath = QueryPath.concat(path);
403         MultiplicityConfiguration config = GWT.create(MultiplicityConfiguration.class);
404         config.init(multiplicityType, styleType, getMetaData(parentPath.toString()));
405         config.setAddItemLabel(getLabel(addItemlabelMessageKey));
406         config.setItemLabel(getLabel(itemLabelMessageKey));
407         config.setUpdateable(true);
408 
409         FieldDescriptor parentFd = buildMultiplicityParentFieldDescriptor(path, getLabel(itemLabelMessageKey), null);
410         config.setParent(parentFd);
411 
412         if (fieldConfigs != null) {
413             for (MultiplicityFieldConfig fieldConfig : fieldConfigs) {
414                 MultiplicityFieldConfiguration fc = buildMultiplicityFD(fieldConfig.getFieldKey(),
415                         fieldConfig.getLabelKey(), parentPath.toString());
416                 config.addFieldConfiguration(fc);
417                 if (fieldConfig.isNextLine()) {
418                     config.nextLine();
419                 }
420             }
421         }
422         return config;
423     }
424 
425     protected MultiplicitySection addMultiplicityFields(Section section,
426                                                         String path, String addItemlabelMessageKey,
427                                                         String itemLabelMessageKey, List<MultiplicityFieldConfig> fieldConfigs,
428                                                         Map<SwapCompositeCondition, List<SwapCompositeConditionFieldConfig>> swappableFieldsDefinition,
429                                                         List<String> deletionParentKeys, int defaultItemsCreated) {
430         MultiplicityConfiguration config = setupMultiplicityConfig(
431                 MultiplicityConfiguration.MultiplicityType.GROUP,
432                 MultiplicityConfiguration.StyleType.TOP_LEVEL_GROUP,
433                 path, addItemlabelMessageKey, itemLabelMessageKey,
434                 fieldConfigs, swappableFieldsDefinition, deletionParentKeys);
435         config.setDefaultItemsCreated(defaultItemsCreated);
436         MultiplicitySection ms = null;
437         ms = new MultiplicitySection(config, swappableFieldsDefinition, deletionParentKeys);
438         section.addSection(ms);
439         return ms;
440     }
441 
442     protected Metadata getMetaData(String fieldKey) {
443         return modelDefinition.getMetadata(QueryPath.concat(fieldKey));
444     }
445 
446     protected MultiplicityFieldConfiguration buildMultiplicityFD(
447             String fieldKey, String labelKey, String parentPath) {
448 
449         QueryPath fieldPath = QueryPath.concat(parentPath, QueryPath.getWildCard(), fieldKey);
450         Metadata meta = modelDefinition.getMetadata(fieldPath);
451 
452         MultiplicityFieldConfiguration fd = new MultiplicityFieldConfiguration(
453                 fieldPath.toString(), generateMessageInfo(labelKey), meta, null);
454 
455 
456         return fd;
457 
458     }
459 
460     protected FieldDescriptor buildMultiplicityParentFieldDescriptor(String fieldKey, String messageKey, String parentPath) {
461         QueryPath path = QueryPath.concat(parentPath, fieldKey);
462         Metadata meta = modelDefinition.getMetadata(path);
463 
464         FieldDescriptor fd = new FieldDescriptor(path.toString(), generateMessageInfo(messageKey), meta);
465         fd.hideLabel();
466         return fd;
467     }
468 
469     protected VerticalSection generateCourseInfoShortTitleSection() {
470         VerticalSection shortTitle = initSection(getH3Title(LUUIConstants.SHORT_TITLE_LABEL_KEY), WITH_DIVIDER);
471         addField(shortTitle, COURSE + "/" + TRANSCRIPT_TITLE, null);
472         return shortTitle;
473     }
474 
475     protected VerticalSection generateLongTitleSection() {
476         VerticalSection longTitle = initSection(getH3Title(LUUIConstants.TITLE_LABEL_KEY), WITH_DIVIDER);
477         addField(longTitle, COURSE + "/" + COURSE_TITLE, null);
478         return longTitle;
479     }
480 
481     protected VerticalSection generateDescriptionRationaleSection() {
482         SectionTitle title = getH4Title(LUUIConstants.PROPOSAL_TITLE_SECTION_LABEL_KEY);
483         VerticalSection description = initSection(title, !WITH_DIVIDER);
484         title.setStyleName("cluProposalTitleSection");
485         //FIXME [KSCOR-225] Temporary fix til we have a real rich text editor
486         //addField(description, COURSE + "/" + DESCRIPTION, null);
487         addField(description, COURSE + "/" + DESCRIPTION + "/" + RichTextInfoConstants.PLAIN, generateMessageInfo(LUUIConstants.DESCRIPTION_LABEL_KEY));
488         addField(description, PROPOSAL_PATH + "/" + PROPOSED_RATIONALE + "/" + RichTextInfoConstants.PLAIN, generateMessageInfo(LUUIConstants.PROPOSAL_RATIONALE_LABEL_KEY),
489                 new KSCharCount(modelDefinition.getMetadata(QueryPath.parse(PROPOSAL_PATH + "/" + PROPOSED_RATIONALE + "/" + RichTextInfoConstants.PLAIN))));
490 
491         return description;
492     }
493 
494     public Section generateCourseLogisticsSection(Section section) {
495         if (section instanceof SectionView) {
496             ((SectionView) section).setInstructions(getLabel(LUUIConstants.LOGISTICS_LABEL_KEY + "-instruct") + "<br><br>");
497         }
498 
499         section.addSection(generateSchedulingSection());
500         section.addSection(generateDurationSection());
501         section.addSection(generateLearningResultsSection());
502         section.addSection(generateCourseFormatsSection());
503 
504         return section;
505     }
506 
507     protected Section generateLearningResultsSection() {
508         VerticalSection learningResults = initSection(getH3Title(LUUIConstants.LEARNING_RESULTS_LABEL_KEY), WITH_DIVIDER);
509         learningResults.setInstructions(getLabel(LUUIConstants.LEARNING_RESULTS_LABEL_KEY + "-instruct") + "<br><br><br>");
510 
511         learningResults.addSection(generateGradesAssessmentsSection());
512         learningResults.addSection(generateStudentRegistrationOptionsSection());
513         learningResults.addSection(generateFinalExamSection());
514         learningResults.addSection(generateOutcomesSection());
515 
516         return learningResults;
517     }
518 
519     protected Section generateOutcomesSection() {
520 
521         String path = COURSE + QueryPath.getPathSeparator() + CREDIT_OPTIONS;
522         QueryPath creditTypeFullPath = QueryPath.concat(path, QueryPath.getWildCard(), CreditCourseConstants.TYPE);
523         QueryPath creditOptionFixedFullPath = QueryPath.concat(path, QueryPath.getWildCard(), CREDIT_OPTION_FIXED_CREDITS);
524         QueryPath creditOptionMinFullPath = QueryPath.concat(path, QueryPath.getWildCard(), CREDIT_OPTION_MIN_CREDITS);
525         QueryPath creditOptionMaxFullPath = QueryPath.concat(path, QueryPath.getWildCard(), CREDIT_OPTION_MAX_CREDITS);
526 
527         QueryPath creditResultValuesFullPath = QueryPath.concat(path, QueryPath.getWildCard(), "resultValueKeys");
528 
529         VerticalSection courseOutcomes = initSection(getH3Title(LUUIConstants.LEARNING_RESULT_OUTCOME_LABEL_KEY), WITH_DIVIDER);
530         Map<SwapCompositeCondition, List<SwapCompositeConditionFieldConfig>> swappableFieldsDefinition =
531                 new HashMap<SwapCompositeCondition, List<SwapCompositeConditionFieldConfig>>();
532         SwapCompositeCondition fixedCreditCondition = new SwapCompositeCondition(
533                 CompositeConditionOperator.AND);
534         fixedCreditCondition.getChildrenConditions().add(
535                 makeCondition(creditTypeFullPath, LUUIConstants.LEARNING_RESULT_OUTCOME_TYPE_LABEL_KEY, "kuali.result.values.group.type.fixed")
536         );
537         fixedCreditCondition.setConditionId("1");
538         SwapCompositeCondition multipleCreditCondition = new SwapCompositeCondition(
539                 CompositeConditionOperator.AND);
540         multipleCreditCondition.getChildrenConditions().add(
541                 makeCondition(creditTypeFullPath, LUUIConstants.LEARNING_RESULT_OUTCOME_TYPE_LABEL_KEY, "kuali.result.values.group.type.multiple")
542         );
543         multipleCreditCondition.setConditionId("2");
544         SwapCompositeCondition variableCreditCondition = new SwapCompositeCondition(
545                 CompositeConditionOperator.AND);
546         variableCreditCondition.getChildrenConditions().add(
547                 makeCondition(creditTypeFullPath, LUUIConstants.LEARNING_RESULT_OUTCOME_TYPE_LABEL_KEY, "kuali.result.values.group.type.range")
548         );
549         variableCreditCondition.setConditionId("3");
550 
551         swappableFieldsDefinition.put(fixedCreditCondition,
552                 Arrays.asList(
553                         new SwapCompositeConditionFieldConfig(
554                                 new MultiplicityFieldConfiguration(
555                                         creditOptionFixedFullPath.toString(),
556                                         generateMessageInfo(LUUIConstants.CREDIT_OPTION_FIXED_CREDITS_LABEL_KEY),
557                                         modelDefinition.getMetadata(creditOptionFixedFullPath),
558                                         null),
559                                 null
560                         )
561                 )
562         );
563         MultiplicityFieldWidgetInitializer multipleCreditInitializer =
564                 new MultiplicityFieldWidgetInitializer() {
565                     @Override
566                     public ModelWidgetBinding<?> getModelWidgetBindingInstance() {
567                         return new ListOfStringBinding();
568                     }
569 
570                     @Override
571                     public Widget getNewWidget() {
572                         return new ListOfStringWidget("Add Item");
573                     }
574                 };
575 
576         swappableFieldsDefinition.put(multipleCreditCondition,
577                 Arrays.asList(
578                         new SwapCompositeConditionFieldConfig(
579                                 new MultiplicityFieldConfiguration(
580                                         creditResultValuesFullPath.toString(),
581                                         generateMessageInfo(LUUIConstants.CREDIT_OPTION_FIXED_CREDITS_LABEL_KEY),
582                                         modelDefinition.getMetadata(creditResultValuesFullPath),
583                                         multipleCreditInitializer),
584                                 null
585                         )
586                 )
587         );
588         swappableFieldsDefinition.put(variableCreditCondition,
589                 Arrays.asList(
590                         new SwapCompositeConditionFieldConfig(
591                                 new MultiplicityFieldConfiguration(
592                                         creditOptionMinFullPath.toString(),
593                                         generateMessageInfo(LUUIConstants.CREDIT_OPTION_MIN_CREDITS_LABEL_KEY),
594                                         modelDefinition.getMetadata(creditOptionMinFullPath),
595                                         null),
596                                 null
597                         ),
598                         new SwapCompositeConditionFieldConfig(
599                                 new MultiplicityFieldConfiguration(
600                                         creditOptionMaxFullPath.toString(),
601                                         generateMessageInfo(LUUIConstants.CREDIT_OPTION_MAX_CREDITS_LABEL_KEY),
602                                         modelDefinition.getMetadata(creditOptionMaxFullPath),
603                                         null),
604                                 null
605                         )
606                 )
607         );
608 
609         MultiplicitySection ms = addMultiplicityFields(
610                 courseOutcomes,
611                 path,
612                 LUUIConstants.LEARNING_RESULT_OUTCOME_LABEL_KEY,
613                 LUUIConstants.LEARNING_RESULT_OUTCOME_LABEL_KEY,
614                 Arrays.asList(
615                         new MultiplicityFieldConfig(
616                                 CreditCourseConstants.TYPE,
617                                 LUUIConstants.LEARNING_RESULT_OUTCOME_TYPE_LABEL_KEY,
618                                 null, null, true)
619                 ), swappableFieldsDefinition, null, 1);
620         //Set the required panel
621         courseOutcomes.setRequired(ms.getConfig().getParentFd().getFieldElement().getRequiredPanel());
622         return courseOutcomes;
623 
624     }
625 
626     protected Section generateStudentRegistrationOptionsSection() {
627         VerticalSection studentRegistrationOptionsSection = initSection(getH3Title(LUUIConstants.LEARNING_RESULTS_STUDENT_REGISTRATION_LABEL_KEY), WITH_DIVIDER);
628 
629         addField(studentRegistrationOptionsSection, COURSE + "/" + AUDIT, generateMessageInfo(LUUIConstants.LEARNING_RESULT_AUDIT_LABEL_KEY), new KSCheckBox(getLabel(LUUIConstants.LEARNING_RESULT_AUDIT_TEXT_LABEL_KEY)));
630         addField(studentRegistrationOptionsSection, COURSE + "/" + PASS_FAIL, generateMessageInfo(LUUIConstants.LEARNING_RESULT_PASS_FAIL_LABEL_KEY), new KSCheckBox(getLabel(LUUIConstants.LEARNING_RESULT_PASS_FAIL_TEXT_LABEL_KEY)));
631 
632         return studentRegistrationOptionsSection;
633     }
634 
635     protected Section generateGradesAssessmentsSection() {
636         VerticalSection gradesAssessments = initSection(getH3Title(LUUIConstants.LEARNING_RESULTS_GRADES_ASSESSMENTS_LABEL_KEY), WITH_DIVIDER);
637 
638         addField(gradesAssessments, COURSE + "/" + GRADING_OPTIONS, generateMessageInfo(LUUIConstants.LEARNING_RESULT_ASSESSMENT_SCALE_LABEL_KEY));
639 
640         return gradesAssessments;
641     }
642 
643     protected VerticalSection generateCourseFormatsSection() {
644         //COURSE FORMATS
645         VerticalSection courseFormats = initSection(getH3Title(LUUIConstants.FORMATS_LABEL_KEY), WITH_DIVIDER);
646         courseFormats.setHelp(getLabel(LUUIConstants.FORMATS_LABEL_KEY + "-help"));
647         courseFormats.setInstructions(getLabel(LUUIConstants.FORMATS_LABEL_KEY + "-instruct"));
648         MultiplicityConfiguration courseFormatConfig = setupMultiplicityConfig(
649                 MultiplicityConfiguration.MultiplicityType.GROUP,
650                 MultiplicityConfiguration.StyleType.TOP_LEVEL_GROUP,
651                 COURSE + "/" + FORMATS, LUUIConstants.COURSE_ADD_FORMAT_LABEL_KEY,
652                 LUUIConstants.FORMAT_LABEL_KEY,
653                 null, null, null);
654         courseFormatConfig.setDefaultItemsCreated(1);
655         MultiplicityConfiguration activitiesConfig = setupMultiplicityConfig(
656                 MultiplicityConfiguration.MultiplicityType.GROUP,
657                 MultiplicityConfiguration.StyleType.SUB_LEVEL_GROUP,
658                 COURSE + "/" + FORMATS + "/*/" + ACTIVITIES,
659                 LUUIConstants.ADD_ACTIVITY_LABEL_KEY,
660                 LUUIConstants.ACTIVITY_LITERAL_LABEL_KEY,
661                 Arrays.asList(
662                         new MultiplicityFieldConfig(
663                                 TYPE_KEY,
664                                 LUUIConstants.ACTIVITY_TYPE_LABEL_KEY,
665                                 null,
666                                 null,
667                                 true),
668                         new MultiplicityFieldConfig(
669                                 CONTACT_HOURS + "/" + "unitQuantity",
670                                 LUUIConstants.CONTACT_HOURS_LABEL_KEY,
671                                 null,
672                                 null,
673                                 false),
674                         new MultiplicityFieldConfig(
675                                 CONTACT_HOURS + "/" + "unitTypeKey",
676                                 LUUIConstants.CONTACT_HOURS_FREQUENCY_LABEL_KEY,
677                                 null,
678                                 null,
679                                 true),
680                         new MultiplicityFieldConfig(
681                                 CreditCourseActivityConstants.DURATION + "/" + "atpDurationTypeKey",
682                                 LUUIConstants.COURSE_FORMATS_DURATION_TYPE_LABEL_KEY,
683                                 null,
684                                 null,
685                                 false),
686                         new MultiplicityFieldConfig(
687                                 CreditCourseActivityConstants.DURATION + "/" + "timeQuantity",
688                                 LUUIConstants.DURATION_QUANTITY_LABEL_KEY,
689                                 null,
690                                 null,
691                                 true),
692                         new MultiplicityFieldConfig(
693                                 DEFAULT_ENROLLMENT_ESTIMATE,
694                                 LUUIConstants.CLASS_SIZE_LABEL_KEY,
695                                 null,
696                                 null,
697                                 true)
698                 ), null, null);
699         activitiesConfig.setDefaultItemsCreated(1);
700         courseFormatConfig.setNestedConfig(activitiesConfig);
701 
702 
703         MultiplicitySection ms = null;
704         ms = new MultiplicitySection(courseFormatConfig,
705                 null, null);
706         courseFormats.addSection(ms);
707         courseFormats.setRequired(courseFormatConfig.getParentFd().getFieldElement().getRequiredPanel());
708         return courseFormats;
709     }
710 
711     protected VerticalSection generateSchedulingSection() {
712         VerticalSection scheduling = initSection(getH3Title(LUUIConstants.SCHEDULING_LABEL_KEY), WITH_DIVIDER);
713         addField(scheduling, COURSE + "/" + TERMS_OFFERED, generateMessageInfo(LUUIConstants.TERMS_OFFERED_LABEL_KEY));
714         return scheduling;
715     }
716 
717     protected VerticalSection generateDurationSection() {
718         VerticalSection duration = initSection(getH3Title(LUUIConstants.DURATION_LITERAL_LABEL_KEY), WITH_DIVIDER);
719         duration.setInstructions(getLabel(LUUIConstants.DURATION_LITERAL_LABEL_KEY + "-instruct"));
720         GroupSection duration_group = new GroupSection();
721         addField(duration_group, COURSE + "/" + CreditCourseConstants.DURATION + "/" + "atpDurationTypeKey", generateMessageInfo(LUUIConstants.DURATION_TYPE_LABEL_KEY));
722         addField(duration_group, COURSE + "/" + CreditCourseConstants.DURATION + "/" + "timeQuantity", generateMessageInfo(LUUIConstants.DURATION_QUANTITY_LABEL_KEY));
723 
724         duration.addSection(duration_group);
725         return duration;
726     }
727 
728     protected VerticalSection generateFinalExamSection() {
729         VerticalSection finalExam = initSection(getH3Title(LUUIConstants.FINAL_EXAM_LABEL_KEY), WITH_DIVIDER);
730         GroupSection finalExam_group = new GroupSection();
731         GroupSection finalExamRationale_group = new GroupSection();
732         GroupSection finalExamRationale_group2 = new GroupSection();
733 
734         FieldDescriptor field = addField(finalExam_group, COURSE + "/" + CreditCourseConstants.FINAL_EXAM, generateMessageInfo(LUUIConstants.FINAL_EXAM_STATUS_LABEL_KEY));
735 
736         if (field.isVisible()) {
737             KSSelectItemWidgetAbstract picker = (KSSelectItemWidgetAbstract) (((KSPicker) field.getFieldWidget()).getInputWidget());
738             final FieldDescriptor rationaleField = addField(finalExamRationale_group, COURSE + "/" + CreditCourseConstants.FINAL_EXAM_RATIONALE, generateMessageInfo(LUUIConstants.FINAL_EXAM_RATIONALE_LABEL_KEY));
739             rationaleField.setIgnoreShowRequired(true);
740             final FieldDescriptor rationaleField2 = addField(finalExamRationale_group2, COURSE + "/" + CreditCourseConstants.FINAL_EXAM_RATIONALE, generateMessageInfo(LUUIConstants.FINAL_EXAM_RATIONALE_LABEL_KEY));
741             rationaleField2.setIgnoreShowRequired(true);
742 
743             // Create SwapSection.
744             final String altKey = "ALT";
745             final String noneKey = "None";
746             SwapSection swapSection = new SwapSection(picker);
747             swapSection.addSection(finalExamRationale_group, altKey);
748             swapSection.addSection(finalExamRationale_group2, noneKey);
749             swapSection.setSwapEventHandler(new SwapEventHandler() {
750 
751                 @Override
752                 public void onShowSwappableSection(String key, Section section) {
753                     progressiveEnableAndRequireSection(true, section);
754                 }
755 
756                 @Override
757                 public void onRemoveSwappableSection(String key, Section section) {
758                     progressiveEnableAndRequireSection(false, section);
759                 }
760             });
761             finalExam.addSection(finalExam_group);
762 
763             finalExam.addSection(swapSection);
764             return finalExam;
765         } else {
766             return new VerticalSection();
767         }
768 
769     }
770 
771     protected void progressiveEnableAndRequireSection(boolean enableAndRequire, Section section) {
772         if (section != null) {
773             List<FieldDescriptor> fields = section.getFields();
774             if ((fields != null) && (fields.size() > 0)) {
775                 BaseSection.progressiveEnableAndRequireFields(enableAndRequire, fields.toArray(new FieldDescriptor[fields.size()]));
776             }
777         }
778     }
779 
780     protected VerticalSection generateInstructorsSection() {
781         VerticalSection instructors = initSection(getH3Title(LUUIConstants.INSTRUCTOR_LABEL_KEY), WITH_DIVIDER);
782         addField(instructors, COURSE + "/" + PRIMARY_INSTRUCTOR + "/personId");
783         return instructors;
784     }
785 
786     protected SectionView generateLearningObjectivesSection() {
787         VerticalSectionView section = initSectionView(CourseSections.LEARNING_OBJECTIVES, LUUIConstants.LEARNING_OBJECTIVES_LABEL_KEY);
788         section.setInstructions(getLabel(LUUIConstants.LEARNING_OBJECTIVES_LABEL_KEY + "-instruct", QueryPath.concat(COURSE, COURSE_SPECIFIC_LOS, "*", "loInfo", "descr", "plain").toString()));
789         section.addSection(generateLearningObjectivesNestedSection());
790         return section;
791     }
792 
793     protected VerticalSection generateLearningObjectivesNestedSection() {
794         final VerticalSection los = initSection(null, NO_DIVIDER);
795 
796         QueryPath path = QueryPath.concat(COURSE, COURSE_SPECIFIC_LOS, "*", "loInfo", "descr");
797         Metadata meta = modelDefinition.getMetadata(path);
798 
799         LOBuilder loBuilder = new LOBuilder(type, state, groupName, "kuali.loRepository.key.singleUse", COURSE_SPECIFIC_LOS, meta);
800         final FieldDescriptor fd = addField(los, CreditCourseConstants.COURSE_SPECIFIC_LOS, null, loBuilder, COURSE);
801 
802         loBuilder.addValueChangeHandler(new ValueChangeHandler<List<OutlineNode<LOPicker>>>() {
803             @Override
804             public void onValueChange(ValueChangeEvent<List<OutlineNode<LOPicker>>> event) {
805                 los.setIsDirty(true);
806             }
807         });
808 
809         // have to do this here, because decision on binding is done in ks-core,
810         // and we obviously don't want ks-core referring to LOBuilder
811         fd.setWidgetBinding(LOBuilderBinding.INSTANCE);
812 
813         los.addStyleName("KS-LUM-Section-Divider");
814         return los;
815     }
816 
817     public class PersonList extends KSDropDown {
818         final SimpleListItems people = new SimpleListItems();
819 
820         public PersonList() {
821             final PersonList us = this;
822             final String userId = Application.getApplicationContext().getSecurityContext().getUserId();
823 
824             //FIXME: [KSCOR-225] Commented out search code to display drop down with only current user, and disable select
825             people.addItem(userId, userId);
826             us.setListItems(people);
827             us.selectItem(userId);
828             us.setBlankFirstItem(false);
829             this.setEnabled(false);
830 
831             /*
832                 SearchRpcServiceAsync searchRpcServiceAsync = GWT.create(SearchRpcService.class);
833                 SearchRequestInfo searchRequest = new SearchRequestInfo();
834                 searchRequest.setSearchKey("person.search.personQuickViewByGivenName");
835                 searchRequest.setSortColumn("person.resultColumn.GivenName");
836                 searchRequest.setSortDirection(SortDirection.ASC);
837                 searchRpcServiceAsync.search(searchRequest, new KSAsyncCallback<SearchResultInfo>() {
838 
839                     @Override
840                     public void onSuccess(SearchResultInfo result) {
841                         for (SearchResultRowInfo r : result.getRows()) {
842                             people.addItem(r.getCells().get(0).getValue(), r.getCells().get(1).getValue());
843                         }
844                         us.setListItems(people);
845                         us.selectItem(userId);
846                     }
847 
848                     @Override
849                     public void handleFailure(Throwable caught) {
850                         Window.alert("Unable to contact the SearchService for the list of users");
851                         people.addItem(userId, userId);
852                         us.setListItems(people);
853                         us.selectItem(userId);
854                     }
855                 });
856              */
857         }
858 
859         @Override
860         public boolean isMultipleSelect() {
861             return true;
862         }
863     }
864 
865     public class ProposerPersonList extends KSLabelList {
866         public ProposerPersonList() {
867             SimpleListItems list = new SimpleListItems();
868 
869             super.setListItems(list);
870         }
871     }
872 
873     protected VerticalSection generateShortTitleSection() {
874         VerticalSection shortTitle = initSection(getH3Title(LUUIConstants.SHORT_TITLE_LABEL_KEY), WITH_DIVIDER);
875         addField(shortTitle, "cluInfo/officialIdentifier/shortName", null);
876         return shortTitle;
877     }
878 
879     protected VerticalSectionView initSectionView(Enum<?> viewEnum, String labelKey) {
880         VerticalSectionView section = new VerticalSectionView(viewEnum, getLabel(labelKey), this.getModelId());
881         section.addStyleName(LUUIConstants.STYLE_SECTION);
882         return section;
883     }
884 
885 
886     protected VerticalSection initSection(SectionTitle title, boolean withDivider) {
887         VerticalSection section;
888         if (title != null) {
889             section = new VerticalSection(title);
890         } else {
891             section = new VerticalSection();
892         }
893         section.addStyleName(LUUIConstants.STYLE_SECTION);
894         if (withDivider)
895             section.addStyleName(LUUIConstants.STYLE_SECTION_DIVIDER);
896         return section;
897     }
898 
899     @Override
900     public MessageKeyInfo generateMessageInfo(String labelKey) {
901         return new MessageKeyInfo(groupName, type, state, labelKey);
902     }
903 
904 
905     protected Section generateFinancialsSection(Section section) {
906 
907         VerticalSection justiFee = initSection(getH3Title(LUUIConstants.COURSE_FEE_TITLE), WITH_DIVIDER);
908         SpanPanel courseFeeInstruction = new SpanPanel();
909         courseFeeInstruction.setStyleName("ks-form-module-elements-instruction");
910         courseFeeInstruction.setHTML(getLabel(LUUIConstants.COURSE_FEE_TITLE + FieldLayoutComponent.INSTRUCT_MESSAGE_KEY));
911         courseFeeInstruction.setVisible(true);
912         justiFee.addWidget(courseFeeInstruction);
913 
914 //        addField(description, COURSE + "/" + PROPOSAL_DESCRIPTION + "/" + RichTextInfoConstants.PLAIN, generateMessageInfo(LUConstants.DESCRIPTION_LABEL_KEY));
915 
916         addField(justiFee, COURSE + "/" + "feeJustification" + "/" + RichTextInfoConstants.PLAIN, generateMessageInfo(LUUIConstants.JUSTIFICATION_FEE));
917         section.addSection(justiFee);
918         Map<SwapCompositeCondition, List<SwapCompositeConditionFieldConfig>> swappableFieldsDefinition =
919                 new HashMap<SwapCompositeCondition, List<SwapCompositeConditionFieldConfig>>();
920 
921         // condition: 
922         //    if rateType field is Variable Rate Fee
923         //    if rateType field is Fixed Rate Fee
924         //    if rateType field is Multiple Rate Fee
925         //    if rateType field is Per Credit Fee
926 //        String feesPathString = COURSE + QueryPath.getPathSeparator() + FEES;
927         QueryPath feesPath = QueryPath.concat(COURSE, FEES);
928         QueryPath rateTypeFieldPath = QueryPath.concat(feesPath.toString(), QueryPath.getWildCard(), "rateType");
929 //        fees/*/feeAmounts/currencyQuantity
930         QueryPath deletionPath = QueryPath.concat(feesPath.toString(), QueryPath.getWildCard(), "feeAmounts");
931         QueryPath singularFeeAmountFieldPath = QueryPath.concat(feesPath.toString(), QueryPath.getWildCard(), "feeAmounts", "0", "currencyQuantity");
932         QueryPath minFeeAmountFieldPath = QueryPath.concat(feesPath.toString(), QueryPath.getWildCard(), "feeAmounts", "0", "currencyQuantity");
933         QueryPath maxFeeAmountFieldPath = QueryPath.concat(feesPath.toString(), QueryPath.getWildCard(), "feeAmounts", "1", "currencyQuantity");
934         Metadata feeAmountFieldMeta = modelDefinition.getMetadata(singularFeeAmountFieldPath);
935 
936         SwapCompositeCondition variableRateCondition = new SwapCompositeCondition(
937                 CompositeConditionOperator.AND);
938         variableRateCondition.getChildrenConditions().add(
939                 makeCondition(rateTypeFieldPath, "Rate Type", "variableRateFee")
940         );
941         variableRateCondition.setConditionId("0");
942 
943         SwapCompositeCondition fixedRateCondition = new SwapCompositeCondition(
944                 CompositeConditionOperator.AND);
945         fixedRateCondition.getChildrenConditions().add(
946                 makeCondition(rateTypeFieldPath, "Rate Type", "fixedRateFee")
947         );
948         fixedRateCondition.setConditionId("1");
949 
950         SwapCompositeCondition perCreditRateCondition = new SwapCompositeCondition(
951                 CompositeConditionOperator.AND);
952         perCreditRateCondition.getChildrenConditions().add(
953                 makeCondition(rateTypeFieldPath, "Rate Type", "perCreditFee")
954         );
955         perCreditRateCondition.setConditionId("2");
956 
957         SwapCompositeCondition multipleRateCondition = new SwapCompositeCondition(
958                 CompositeConditionOperator.AND);
959         multipleRateCondition.getChildrenConditions().add(
960                 makeCondition(rateTypeFieldPath, "Rate Type", "multipleRateFee")
961         );
962         multipleRateCondition.setConditionId("3");
963 
964         swappableFieldsDefinition.put(variableRateCondition,
965                 Arrays.asList(
966                         new SwapCompositeConditionFieldConfig(
967                                 new MultiplicityFieldConfiguration(
968                                         minFeeAmountFieldPath.toString(),
969                                         new MessageKeyInfo("Mininum Amount"), feeAmountFieldMeta,
970                                         null),
971                                 null
972                         ),
973                         new SwapCompositeConditionFieldConfig(
974                                 new MultiplicityFieldConfiguration(
975                                         maxFeeAmountFieldPath.toString(),
976                                         new MessageKeyInfo("Maximum Amount"), feeAmountFieldMeta,
977                                         null),
978                                 null
979                         ))
980         );
981 
982         swappableFieldsDefinition.put(fixedRateCondition,
983                 Arrays.asList(
984                         new SwapCompositeConditionFieldConfig(
985                                 new MultiplicityFieldConfiguration(
986                                         singularFeeAmountFieldPath.toString(),
987                                         new MessageKeyInfo("Amount"), feeAmountFieldMeta,
988                                         null),
989                                 null))
990         );
991 
992         swappableFieldsDefinition.put(perCreditRateCondition,
993                 Arrays.asList(
994                         new SwapCompositeConditionFieldConfig(
995                                 new MultiplicityFieldConfiguration(
996                                         singularFeeAmountFieldPath.toString(),
997                                         new MessageKeyInfo("Amount"), feeAmountFieldMeta,
998                                         null),
999                                 null))
1000         );
1001 
1002         MultiplicityConfiguration multipleFeesConfig = setupMultiplicityConfig(
1003                 MultiplicityConfiguration.MultiplicityType.GROUP,
1004                 MultiplicityConfiguration.StyleType.BORDERLESS_TABLE,
1005                 COURSE + QueryPath.getPathSeparator() + FEES + QueryPath.getPathSeparator() +
1006                         QueryPath.getWildCard() + QueryPath.getPathSeparator() + "feeAmounts",
1007                 LUUIConstants.ADD_ANOTHER_FEE,
1008                 LUUIConstants.FEE,
1009                 Arrays.asList(
1010                         new MultiplicityFieldConfig(
1011                                 "currencyQuantity",
1012                                 "Amount", null, null, true)),
1013                 null,
1014                 null);
1015         swappableFieldsDefinition.put(multipleRateCondition,
1016                 Arrays.asList(
1017                         new SwapCompositeConditionFieldConfig(
1018                                 null, multipleFeesConfig
1019                         ))
1020         );
1021 
1022         addFeeMultiplicityFields(justiFee,
1023                 COURSE + QueryPath.getPathSeparator() + FEES,
1024                 LUUIConstants.ADD_A_FEE,
1025                 LUUIConstants.FEE,
1026                 Arrays.asList(
1027                         new MultiplicityFieldConfig(
1028                                 "feeType",
1029                                 "Fee Type", null, null, true),
1030                         new MultiplicityFieldConfig(
1031                                 "rateType",
1032                                 "Rate Type", null, null, true)),
1033                 swappableFieldsDefinition,
1034                 Arrays.asList(
1035                         deletionPath.toString()));
1036 
1037         section.addSection(justiFee);
1038 
1039 
1040         VerticalSection financialSection = initSection(getH3Title(LUUIConstants.FINANCIAL_INFORMATION), WITH_DIVIDER);
1041         SpanPanel financialInfoInstruction = new SpanPanel();
1042         financialInfoInstruction.setStyleName("ks-form-module-elements-instruction");
1043         financialInfoInstruction.setHTML(getLabel(LUUIConstants.FINANCIAL_INFORMATION + FieldLayoutComponent.INSTRUCT_MESSAGE_KEY));
1044         financialInfoInstruction.setVisible(true);
1045         financialSection.addWidget(financialInfoInstruction);
1046         SpanPanel revenuepan = new SpanPanel();
1047         revenuepan.setStyleName("ks-multiplicity-section-label");
1048         revenuepan.setHTML("<br>Revenue");
1049         revenuepan.setVisible(true);
1050         financialSection.addWidget(revenuepan);
1051         setupRevenueSection(financialSection);
1052         SpanPanel expendpan = new SpanPanel();
1053         expendpan.setStyleName("ks-multiplicity-section-label");
1054         expendpan.setHTML("<br>Expenditures");
1055         expendpan.setVisible(true);
1056         financialSection.addWidget(expendpan);
1057         setupExpenditureSection(financialSection);
1058         section.addSection(financialSection);
1059 
1060         return section;
1061     }
1062 
1063     protected void setupRevenueSection(Section parentSection) {
1064         // TODO customize multiplicity and change "Percentage" label into LUConstants.AMOUNT
1065         QueryPath revenuePath = QueryPath.concat(COURSE, "revenues");
1066         QueryPath affiliatedOrgIdSubPath = QueryPath.concat("affiliatedOrgs", "0", "orgId");
1067         QueryPath percentageSubPath = QueryPath.concat("affiliatedOrgs", "0", "percentage");
1068         addMultiplicityFields(parentSection,
1069                 revenuePath.toString(),
1070                 LUUIConstants.ADD_ANOTHER_ORGANIZATION,
1071                 LUUIConstants.REVENUE,
1072                 Arrays.asList(
1073                         new MultiplicityFieldConfig(
1074                                 affiliatedOrgIdSubPath.toString(),
1075                                 LUUIConstants.REVENUE, null, null, true),
1076                         new MultiplicityFieldConfig(
1077                                 percentageSubPath.toString(),
1078                                 "Percentage", null, null, true)
1079                 ),
1080                 null,
1081                 null,
1082                 0);
1083     }
1084 
1085     protected void setupExpenditureSection(Section parentSection) {
1086         // TODO customize multiplicity and change "Percentage" label into LUConstants.AMOUNT
1087         QueryPath expenditureAffiliatedOrgPath = QueryPath.concat(COURSE, "expenditure", "affiliatedOrgs");
1088         QueryPath affiliatedOrgIdSubPath = QueryPath.concat("orgId");
1089         QueryPath percentageSubPath = QueryPath.concat("percentage");
1090         addMultiplicityFields(parentSection,
1091                 expenditureAffiliatedOrgPath.toString(),
1092                 LUUIConstants.ADD_ANOTHER_ORGANIZATION,
1093                 LUUIConstants.EXPENDITURE,
1094                 Arrays.asList(
1095                         new MultiplicityFieldConfig(
1096                                 affiliatedOrgIdSubPath.toString(),
1097                                 LUUIConstants.EXPENDITURE, null, null, true),
1098                         new MultiplicityFieldConfig(
1099                                 percentageSubPath.toString(),
1100                                 "Percentage", null, null, true)
1101                 ),
1102                 null,
1103                 null,
1104                 0);
1105     }
1106 
1107     protected SwapCondition makeCondition(QueryPath fieldPath, String messageLabelKey,
1108                                           String value) {
1109         SwapCondition swapCondition = new SwapCondition();
1110         swapCondition.setFd(new FieldDescriptor(
1111                 fieldPath.toString(),
1112                 new MessageKeyInfo(messageLabelKey),
1113                 modelDefinition.getMetadata(fieldPath)));
1114         swapCondition.setValue(value);
1115         return swapCondition;
1116     }
1117 
1118 
1119     @Override
1120     public String getCourseTitlePath() {
1121         return COURSE_TITLE_PATH;
1122     }
1123 
1124     @Override
1125     public String getProposalPath() {
1126         return PROPOSAL_PATH;
1127     }
1128 
1129     @Override
1130     public String getProposalTitlePath() {
1131         return PROPOSAL_TITLE_PATH;
1132     }
1133 
1134     @Override
1135     public Class<? extends Enum<?>> getViewsEnum() {
1136         return CourseProposalConfigurer.CourseSections.class;
1137     }
1138 
1139 
1140     @Override
1141     public String getSectionTitle(DataModel model) {
1142 
1143         StringBuilder sb = new StringBuilder();
1144         sb.append("Modify Course: ");
1145         sb.append(model.get("courseCode"));
1146         sb.append(" - ");
1147         sb.append(model.get("transcriptTitle"));
1148 
1149         return sb.toString();
1150     }
1151 
1152     @Override
1153     public String getProposalHeaderTitle(DataModel model) {
1154       StringBuilder sb = new StringBuilder();
1155         if (model.get("copyOfCourseId") != null) {
1156             sb.append("Modify Course: ");
1157             sb.append(model.get("courseCode"));
1158             sb.append(" - ");
1159             sb.append(model.get("transcriptTitle"));
1160         } else {
1161             sb.append("New Course: ");
1162             sb.append(model.get(getCourseTitlePath()));
1163         }
1164 
1165         return sb.toString();
1166     }
1167 
1168     public CourseSummaryConfigurer getSummaryConfigurer() {
1169         return summaryConfigurer;
1170     }
1171 
1172     public static class KeyListModelWigetBinding extends ModelWidgetBindingSupport<HasDataValue> {
1173         protected String key;
1174         HasDataValueBinding hasDataValueBinding = HasDataValueBinding.INSTANCE;
1175 
1176         public KeyListModelWigetBinding(String key) {
1177             this.key = key;
1178         }
1179 
1180         @Override
1181         public void setModelValue(HasDataValue widget, DataModel model, String path) {
1182             // convert from the structure path/0/<id> into path/0/<key>/<id>
1183             hasDataValueBinding.setModelValue(widget, model, path);
1184 
1185             QueryPath qPath = QueryPath.parse(path);
1186             Value value = ((KSSelectedList) widget).getValueWithTranslations();
1187 
1188             Data idsData = null;
1189             Data idsDataStruct = null;
1190 
1191             if (value != null) {
1192                 idsData = value.get();
1193             }
1194             if (idsData != null) {
1195                 for (Data.Property p : idsData) {
1196                     if (!"_runtimeData".equals(p.getKey())) {
1197                         String id = p.getValue();
1198                         // old translation path path/_runtimeData/0/id-translation
1199                         QueryPath translationPath = new QueryPath();
1200                         translationPath.add(new Data.StringKey(qPath.toString()));
1201                         translationPath.add(new Data.StringKey("_runtimeData"));
1202                         translationPath.add(new Data.IntegerKey((Integer) p.getKey()));
1203                         translationPath.add(new Data.StringKey("id-translation"));
1204 
1205                         Data idItem = new Data();
1206                         String translation = model.get(translationPath.toString());
1207                         Data idItemRuntime = new Data();
1208                         Data idItemTranslation = new Data();
1209                         idsDataStruct = (idsDataStruct == null) ? new Data() : idsDataStruct;
1210                         idItem.set(this.key, id);
1211                         // new translation path/0/_runtimeData/<key>/id-translation
1212                         idItemTranslation.set("id-translation", translation);
1213                         idItemRuntime.set(this.key, idItemTranslation);
1214                         idItem.set("_runtimeData", idItemRuntime);
1215                         idsDataStruct.add(idItem);
1216                     }
1217                 }
1218             }
1219 
1220             model.set(qPath, idsDataStruct);
1221         }
1222 
1223         @Override
1224         public void setWidgetValue(HasDataValue widget, DataModel model, String path) {
1225             DataModel middleManModel = new DataModel();
1226             if (model != null && model.getRoot() != null) {
1227                 middleManModel = new DataModel(model.getDefinition(), model.getRoot().copy());
1228             }
1229             // convert from the structure path/0/<key>/<id> into path/0/<id>
1230             QueryPath qPath = QueryPath.parse(path);
1231             Object value = null;
1232             Data idsData = null;
1233             Data newIdsData = null;
1234             Data newIdsRuntimeData = null;
1235 
1236             if (middleManModel != null) {
1237                 value = middleManModel.get(qPath);
1238             }
1239 
1240             if (value != null) {
1241                 idsData = (Data) value;
1242                 if (idsData != null) {
1243                     for (Data.Property p : idsData) {
1244                         if (!"_runtimeData".equals(p.getKey())) {
1245                             Data idItem = p.getValue();
1246                             String id = idItem.get(key);
1247                             Data runtimeData = idItem.get("_runtimeData");
1248                             // KSLAB-1790 - sometime runtimeData isn't there; no idea why
1249                             Data translationData = null != runtimeData ? ((Data) runtimeData.get(key)) : new Data();
1250                             newIdsData = (newIdsData == null) ? new Data() : newIdsData;
1251                             newIdsData.add(id);
1252                             newIdsRuntimeData = (newIdsRuntimeData == null) ? new Data() : newIdsRuntimeData;
1253                             newIdsRuntimeData.add(translationData);
1254                         }
1255                     }
1256                 }
1257             }
1258             if (newIdsData != null) {
1259                 newIdsData.set("_runtimeData", newIdsRuntimeData);
1260                 middleManModel.set(qPath, newIdsData);
1261                 hasDataValueBinding.setWidgetValue(widget, middleManModel, path);
1262             }
1263         }
1264     }
1265 
1266 
1267     public static class MultiplicityFieldConfig {
1268         protected String fieldKey;
1269         protected String labelKey;
1270         boolean nextLine;
1271 
1272         public MultiplicityFieldConfig() {
1273         }
1274 
1275         public MultiplicityFieldConfig(String fieldKey, String labelKey,
1276                                        Widget fieldWidget, ModelWidgetBinding<?> modelWidgetBinding, boolean nextLine) {
1277             setFieldKey(fieldKey);
1278             setLabelKey(labelKey);
1279             setNextLine(nextLine);
1280         }
1281 
1282         public String getFieldKey() {
1283             return fieldKey;
1284         }
1285 
1286         public void setFieldKey(String fieldKey) {
1287             this.fieldKey = fieldKey;
1288         }
1289 
1290         public String getLabelKey() {
1291             return labelKey;
1292         }
1293 
1294         public void setLabelKey(String labelKey) {
1295             this.labelKey = labelKey;
1296         }
1297 
1298         public boolean isNextLine() {
1299             return nextLine;
1300         }
1301 
1302         public void setNextLine(boolean nextLine) {
1303             this.nextLine = nextLine;
1304         }
1305     }
1306 }
1307 
1308 
1309 
1310