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