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