View Javadoc

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