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  package org.kuali.student.common.ui.client.configurable.mvc;
17  
18  
19  import org.kuali.student.common.ui.client.application.Application;
20  import org.kuali.student.common.ui.client.configurable.mvc.binding.ModelWidgetBinding;
21  import org.kuali.student.common.ui.client.configurable.mvc.binding.MultiplicityCompositeBinding;
22  import org.kuali.student.common.ui.client.configurable.mvc.multiplicity.MultiplicityComposite;
23  import org.kuali.student.common.ui.client.mvc.Callback;
24  import org.kuali.student.common.ui.client.mvc.HasCrossConstraints;
25  import org.kuali.student.common.ui.client.mvc.HasDataValue;
26  import org.kuali.student.common.ui.client.widgets.KSCheckBox;
27  import org.kuali.student.common.ui.client.widgets.KSTextBox;
28  import org.kuali.student.common.ui.client.widgets.RichTextEditor;
29  import org.kuali.student.common.ui.client.widgets.field.layout.element.FieldElement;
30  import org.kuali.student.common.ui.client.widgets.field.layout.element.MessageKeyInfo;
31  import org.kuali.student.common.ui.client.widgets.list.KSSelectItemWidgetAbstract;
32  import org.kuali.student.r1.common.assembly.data.Metadata;
33  import org.kuali.student.r1.common.assembly.data.MetadataInterrogator;
34  
35  import com.google.gwt.user.client.ui.HasText;
36  import com.google.gwt.user.client.ui.HasValue;
37  import com.google.gwt.user.client.ui.Widget;
38  
39  /**
40   * This is a field descriptor that defines ui fields.
41   * A field descriptor is defined by its fieldKey and its metadata, the field key is the key that is used
42   * to identify it during validation and save processing and maps directly to a field which exists in the
43   * model definition for the model it relates to.  The fieldKey must match something within the model to
44   * be a valid field.  <br>The metadata is its piece of information in the model definition which is used to
45   * generate the field's widget and determine what the validation on this field is (also if this field
46   * is backed by a search it will use the lookup metadata defined in its metadata to generate the appropriate
47   * search widget).  It also determines if a requiredness indicator appears (constraint minOccurs > 1).
48   * <br><br>
49   * A field descriptor can be fully customized to any need, the widget can be chosen to be
50   * defined directly instead of using one that is auto generated by the metadata - if this is done make sure
51   * the data type it is using works with what is in the metadata and that it implements one of these standard 
52   * interfaces:<br>
53   * HasText<br>
54   * KSSelectItemWidgetAbstract<br>
55   * HasDataValue<br>
56   * HasValue<br>
57   * or, you MUST override the ModelWidgetBinding by calling setModelWidgetBinding
58   * <br>
59   * Setting the ModelWidgetBinding allows you to manipulate the widget's data and model data as you see fit
60   * before a save when model data is loaded into the widget.
61   * <br>
62   * <br>
63   * General layout of a field generated by FieldDescriptor, all elements are conditional based on the field's
64   * configuration: <br>
65   * <b>[label][requiredness indicator][help]<br>
66   * [input widget]<br>
67   * [constraint text]<br></b>
68   * <br>
69   * The messageKeyInfo passed in is used to generate the messages needed for a field these include:<br>
70   * Field Label<br>
71   * Help text<br>
72   * Instructions<br>
73   * Example text<br>
74   * Constraint text<br>
75   * Watermark text - only if the widget one that accepts text input<br><br>
76   * These are generated by a single key, the additional text is determined by special suffixes on keys within
77   * the messages data - example - you pass in "sampleField" for the message key - it is automatically determined
78   * that if there is a message in the message data named "sampleField-instruct", instructions will be added to the field
79   * in the appropriate location.<br>
80   * List of the appended keys for use in messages data:<br>
81   * "-help" for help text<br>
82   * "-instruct" for instructions<br>
83   * "-examples" for examples<br>
84   * "-constraints" for constraint text<br>
85   * "-watermark" for watermark text<br>
86   * 
87   * @author Kuali Student Team
88   * @see FieldElement
89   * @see Section
90   * @see BaseSection
91   * @see Configurer
92   */
93  public class FieldDescriptor {
94      protected String fieldKey;
95  	protected Metadata metadata;
96      @SuppressWarnings("unchecked")
97  	private ModelWidgetBinding modelWidgetBinding;
98      private Callback<Boolean> validationRequestCallback;
99      private boolean dirty = false;
100     private boolean hasHadFocus = false;
101     private final FieldElement fieldElement;
102     private String modelId;
103     private MessageKeyInfo messageKey;
104     private boolean optional = false;
105     private boolean ignoreShowRequired = false; 
106 
107     /**
108      * @param fieldKey - key for this field which matches a field in the overall model definition that this
109      * field will be used for
110      * @param messageKey - key object used for determing field labels
111      * @param metadata - metadata used to determine requiredness, validation, and autogenerated widget
112      */
113     public FieldDescriptor(String fieldKey, MessageKeyInfo messageKey, Metadata metadata) {
114     	this.fieldKey = fieldKey;
115     	this.metadata = metadata;
116     	if(messageKey == null){
117     		messageKey = new MessageKeyInfo("");
118     	}
119     	setMessageKey(messageKey);
120     	fieldElement = new FieldElement(fieldKey, messageKey, createFieldWidget(), metadata);
121     	setupField();
122     	
123     	//Add mapping from path to field definition
124     	if((getFieldWidget() instanceof HasDataValue || getFieldWidget() instanceof KSTextBox || getFieldWidget() instanceof HasValue)&&!(this instanceof FieldDescriptorReadOnly)){
125     		Application.getApplicationContext().putPathToFieldMapping(null, Application.getApplicationContext().getParentPath()+fieldKey, this);
126 		}
127 
128     	//Add cross constraints
129     	if(fieldElement.getFieldWidget() instanceof HasCrossConstraints){
130     		HasCrossConstraints crossConstraintWidget = (HasCrossConstraints) fieldElement.getFieldWidget();
131     		if(crossConstraintWidget!=null&&crossConstraintWidget.getCrossConstraints()!=null){
132     			for(String path:crossConstraintWidget.getCrossConstraints()){
133     		    	Application.getApplicationContext().putCrossConstraint(null, path, crossConstraintWidget);
134     			}
135     		}
136     	}
137     }
138 
139     /**
140      * @param fieldKey - key for this field which matches a field in the overall model definition that this
141      * field will be used for
142      * @param messageKey - key object used for determing field labels
143      * @param metadata - metadata used to determine requiredness and validation
144      * @param fieldWidget - widget to use instead of an automatically determined one
145      */
146     public FieldDescriptor(String fieldKey, MessageKeyInfo messageKey, Metadata metadata, Widget fieldWidget){
147     	this.fieldKey = fieldKey;
148     	this.metadata = metadata;
149     	if(messageKey == null){
150     		messageKey = new MessageKeyInfo("");
151     	}
152         setMessageKey(messageKey);
153     	addStyleToWidget(fieldWidget);
154     	fieldElement = new FieldElement(fieldKey, messageKey, fieldWidget, metadata);
155         setupField();
156     	
157     	//Add mapping from path to field definition if the definition has a data value
158     	if((fieldWidget instanceof HasDataValue || fieldWidget instanceof KSTextBox) &&!(this instanceof FieldDescriptorReadOnly)){
159     		Application.getApplicationContext().putPathToFieldMapping(null, Application.getApplicationContext().getParentPath()+fieldKey, this);
160 		}
161     	
162     	//Add cross constraints
163     	if(fieldElement.getFieldWidget() instanceof HasCrossConstraints){
164     		HasCrossConstraints crossConstraintWidget = (HasCrossConstraints) fieldElement.getFieldWidget();
165     		if(crossConstraintWidget!=null&&crossConstraintWidget.getCrossConstraints()!=null){
166     			for(String path:crossConstraintWidget.getCrossConstraints()){
167     		    	Application.getApplicationContext().putCrossConstraint(null, path, crossConstraintWidget);
168     			}
169     		}
170     	}
171     	
172     }
173 
174     protected void addStyleToWidget(Widget w){
175     	if(fieldKey != null && !fieldKey.isEmpty() && w != null){
176     		String style = this.fieldKey.replaceAll("/", "-");
177     		w.addStyleName(style);
178     	}
179     }
180 
181     protected void setupField() {
182     	if(metadata != null){
183     		if(MetadataInterrogator.isRequired(metadata)){
184     			fieldElement.setRequiredString("requiredMarker", "ks-form-module-elements-required");
185     		}
186     		else if(MetadataInterrogator.isRequiredForNextState(metadata)){
187     			String nextState = MetadataInterrogator.getNextState(metadata);
188     			if(nextState != null){
189     				if(nextState.equalsIgnoreCase("SUBMITTED")){
190     					fieldElement.setRequiredString("requiredOnSubmit", "ks-form-required-for-submit");
191     				}
192     				else if(nextState.equalsIgnoreCase("APPROVED")){
193     					fieldElement.setRequiredString("reqApproval", "ks-form-required-for-submit");
194     				}
195 					else if(nextState.equalsIgnoreCase("ACTIVE")){
196 						fieldElement.setRequiredString("reqActivate", "ks-form-required-for-submit");
197     				}
198 					else if(nextState.equalsIgnoreCase("SUSPENDED")){
199 						fieldElement.setRequiredString("reqDeactivate", "ks-form-required-for-submit");
200 					}
201 					else if(nextState.equalsIgnoreCase("RETIRED")){
202                         fieldElement.setRequiredString("requiredOnSubmit", "ks-form-required-for-submit");
203                     }
204 					else {
205 						fieldElement.setRequiredString("requiredOnSubmit", "ks-form-required-for-submit");
206 					}
207 
208     			}
209     		} else{
210                 fieldElement.clearRequiredText();
211             }
212     	}
213     }
214 
215     /**
216      * @see FieldElement#hideLabel()
217      */
218     public void hideLabel(){
219     	fieldElement.hideLabel();
220     }
221 
222     public boolean isLabelShown(){
223     	return fieldElement.isLabelShown();
224     }
225 
226     public FieldElement getFieldElement(){
227     	return fieldElement;
228     }
229 
230 	public String getFieldKey() {
231         return fieldKey;
232     }
233 
234     public void setFieldKey(String fieldKey) {
235 		this.fieldKey = fieldKey;
236 	}
237 
238     public String getFieldLabel() {
239         return fieldElement.getFieldName();
240     }
241 
242     public Widget getFieldWidget(){
243         if (fieldElement.getFieldWidget() == null){
244             Widget w = createFieldWidget();
245             fieldElement.setWidget(w);
246         }
247         return fieldElement.getFieldWidget();
248     }
249 
250     protected Widget createFieldWidget() {
251     	if (metadata == null) {
252     		// backwards compatibility for old ModelDTO code
253 	    	// for now, default to textbox if not specified
254 	    	Widget result = new KSTextBox();
255 	    	addStyleToWidget(result);
256 	    	return result;
257     	} else {
258     		Widget result = DefaultWidgetFactory.getInstance().getWidget(this);
259     		addStyleToWidget(result);
260     		return result;
261     	}
262     }
263 
264     public ModelWidgetBinding<?> getModelWidgetBinding() {
265         if(modelWidgetBinding == null){
266             if(fieldElement.getFieldWidget() instanceof RichTextEditor){
267             	modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.RichTextBinding.INSTANCE;
268             } else if (fieldElement.getFieldWidget() instanceof KSCheckBox){
269             	modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.HasValueBinding.INSTANCE;
270             } else if(fieldElement.getFieldWidget() instanceof MultiplicityComposite){
271         		modelWidgetBinding = MultiplicityCompositeBinding.INSTANCE;
272         	} else if (fieldElement.getFieldWidget()instanceof HasText) {
273         	    modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.HasTextBinding.INSTANCE;
274             } else if (fieldElement.getFieldWidget() instanceof KSSelectItemWidgetAbstract){
275                 modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.SelectItemWidgetBinding.INSTANCE;
276             } else if (fieldElement.getFieldWidget() instanceof HasDataValue){
277             	modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.HasDataValueBinding.INSTANCE;
278             } else if (fieldElement.getFieldWidget() instanceof HasValue){
279             	modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.HasValueBinding.INSTANCE;
280             }
281         }
282         return modelWidgetBinding;
283     }
284 
285     /**
286      * Allows additional processing to happen when a validation check is being processed when the input
287      * widget loses focus defined in the callback
288      * @param callback
289      */
290     public void setValidationCallBack(Callback<Boolean> callback){
291         validationRequestCallback = callback;
292     }
293 
294     public Callback<Boolean> getValidationRequestCallback(){
295         return validationRequestCallback;
296     }
297 
298 	/**
299 	 * Returns true if this field is marked as dirty.  In KS, dirty is when the field has been changed
300 	 * in some way - however not completely accurate
301 	 * @return
302 	 */
303 	public boolean isDirty() {
304 		return dirty;
305 	}
306 
307 	public void setDirty(boolean dirty) {
308 		this.dirty = dirty;
309 	}
310 
311 	/**
312 	 * Return true if the field has been touched by the user in some fashion, this is set by the field's section
313 	 * @return
314 	 */
315 	public boolean hasHadFocus() {
316 		return hasHadFocus;
317 	}
318 
319 	public void setHasHadFocus(boolean hasHadFocus) {
320 		this.hasHadFocus = hasHadFocus;
321 	}
322 
323     public Metadata getMetadata() {
324 		return metadata;
325 	}
326 
327     public void setMetadata(Metadata metadata) {
328 		this.metadata = metadata;
329         setupField();
330 	}
331 
332     public void setFieldWidget(Widget fieldWidget) {
333 		this.fieldElement.setWidget(fieldWidget);
334 	}
335 
336 	public String getModelId() {
337 		return modelId;
338 	}
339 
340 	public void setModelId(String modelId) {
341 		this.modelId = modelId;
342 	}
343 
344     /**
345      * Sets the ModelWidgetBinding for this field.  Changing this changes the way data from the server and
346      * passed to the server is processed with the widget.  Set this when some special processing or handling
347      * has to happen with the data in either phase.
348      * @param widgetBinding
349      */
350     public void setWidgetBinding(ModelWidgetBinding widgetBinding) {
351         this.modelWidgetBinding = widgetBinding;
352     }
353 
354     public MessageKeyInfo getMessageKey() {
355         return messageKey;
356     }
357 
358     public void setMessageKey(MessageKeyInfo messageKey) {
359         this.messageKey = messageKey;
360     }
361 
362 	/**
363 	 * Sets the optional flag
364 	 * Fields that are optional should not be displayed if there is no data in some cases,
365 	 * it is up to the section implementation whether or not to honor this flag
366 	 * @param optional
367 	 */
368 	public void setOptional(boolean optional){
369 		this.optional = optional;
370 	}
371 
372 	/**
373 	 * Fields that are optional should not be displayed if there is no data in some cases,
374 	 * it is up to the section implementation whether or not to honor this flag
375 	 */
376 	public boolean isOptional(){
377 		return optional;
378 	}
379 
380 	/**
381 	 * @return true if this field is visible to the user
382 	 */
383 	public boolean isVisible() {
384 		if (metadata != null){
385 			return metadata.isCanView();
386 		} else {
387 			return true;
388 		}
389 	}
390 
391 	/**
392 	 * Reset the requiredness of the field descriptor. Note doing this will also dynamically change
393 	 * the underlying metadata so ui validation for requiredness works as well.
394 	 * 
395 	 */
396 	public void setRequired(Boolean isRequired){ 
397 		fieldElement.setRequiredString("requiredMarker", "ks-form-module-elements-required");
398 		fieldElement.setRequired(isRequired);
399 		
400 		//FIXME: This could be problematic if minOccurs should be something other than 1
401 		if (isRequired){
402 			getMetadata().getConstraints().get(0).setMinOccurs(1);
403 		} else {
404 			getMetadata().getConstraints().get(0).setMinOccurs(0);
405 		}
406 	}
407 
408     public boolean isIgnoreShowRequired() {
409         return ignoreShowRequired;
410     }
411 
412     public void setIgnoreShowRequired(boolean ignoreShowRequired) {
413         this.ignoreShowRequired = ignoreShowRequired;
414     }
415 	
416 }