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