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