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