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.assembly.data.Metadata;
20  import org.kuali.student.common.assembly.data.MetadataInterrogator;
21  import org.kuali.student.common.ui.client.application.Application;
22  import org.kuali.student.common.ui.client.configurable.mvc.binding.ModelWidgetBinding;
23  import org.kuali.student.common.ui.client.configurable.mvc.binding.MultiplicityCompositeBinding;
24  import org.kuali.student.common.ui.client.configurable.mvc.multiplicity.MultiplicityComposite;
25  import org.kuali.student.common.ui.client.mvc.Callback;
26  import org.kuali.student.common.ui.client.mvc.HasCrossConstraints;
27  import org.kuali.student.common.ui.client.mvc.HasDataValue;
28  import org.kuali.student.common.ui.client.widgets.KSCheckBox;
29  import org.kuali.student.common.ui.client.widgets.KSTextBox;
30  import org.kuali.student.common.ui.client.widgets.RichTextEditor;
31  import org.kuali.student.common.ui.client.widgets.field.layout.element.FieldElement;
32  import org.kuali.student.common.ui.client.widgets.field.layout.element.MessageKeyInfo;
33  import org.kuali.student.common.ui.client.widgets.list.KSSelectItemWidgetAbstract;
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   * Constraint text<br>
74   * Watermark text - only if the widget one that accepts text input<br><br>
75   * These are generated by a single key, the additional text is determined by special suffixes on keys within
76   * the messages data - example - you pass in "sampleField" for the message key - it is automatically determined
77   * that if there is a message in the message data named "sampleField-instruct", instructions will be added to the field
78   * in the appropriate location.<br>
79   * List of the appended keys for use in messages data:<br>
80   * "-help" for help text<br>
81   * "-instruct" for instructions<br>
82   * "-constraints" for constraint text<br>
83   * "-watermark" for watermark text<br>
84   * 
85   * @author Kuali Student Team
86   * @see FieldElement
87   * @see Section
88   * @see BaseSection
89   * @see Configurer
90   */
91  public class FieldDescriptor {
92      protected String fieldKey;
93  	protected Metadata metadata;
94      @SuppressWarnings("unchecked")
95  	private ModelWidgetBinding modelWidgetBinding;
96      private Callback<Boolean> validationRequestCallback;
97      private boolean dirty = false;
98      private boolean hasHadFocus = false;
99      private final FieldElement fieldElement;
100     private String modelId;
101     private MessageKeyInfo messageKey;
102     private boolean optional = false;
103 
104     /**
105      * @param fieldKey - key for this field which matches a field in the overall model definition that this
106      * field will be used for
107      * @param messageKey - key object used for determing field labels
108      * @param metadata - metadata used to determine requiredness, validation, and autogenerated widget
109      */
110     public FieldDescriptor(String fieldKey, MessageKeyInfo messageKey, Metadata metadata) {
111     	this.fieldKey = fieldKey;
112     	this.metadata = metadata;
113     	if(messageKey == null){
114     		messageKey = new MessageKeyInfo("");
115     	}
116     	setMessageKey(messageKey);
117     	fieldElement = new FieldElement(fieldKey, messageKey, createFieldWidget());
118     	setupField();
119     	
120     	//Add mapping from path to field definition
121     	if(getFieldWidget() instanceof HasDataValue &&!(this instanceof FieldDescriptorReadOnly)){
122     		Application.getApplicationContext().putPathToFieldMapping(null, Application.getApplicationContext().getParentPath()+fieldKey, this);
123 		}
124 
125     	//Add cross constraints
126     	if(fieldElement.getFieldWidget() instanceof HasCrossConstraints){
127     		HasCrossConstraints crossConstraintWidget = (HasCrossConstraints) fieldElement.getFieldWidget();
128     		if(crossConstraintWidget!=null&&crossConstraintWidget.getCrossConstraints()!=null){
129     			for(String path:crossConstraintWidget.getCrossConstraints()){
130     		    	Application.getApplicationContext().putCrossConstraint(null, path, crossConstraintWidget);
131     			}
132     		}
133     	}
134     }
135 
136     /**
137      * @param fieldKey - key for this field which matches a field in the overall model definition that this
138      * field will be used for
139      * @param messageKey - key object used for determing field labels
140      * @param metadata - metadata used to determine requiredness and validation
141      * @param fieldWidget - widget to use instead of an automatically determined one
142      */
143     public FieldDescriptor(String fieldKey, MessageKeyInfo messageKey, Metadata metadata, Widget fieldWidget){
144     	this.fieldKey = fieldKey;
145     	this.metadata = metadata;
146     	if(messageKey == null){
147     		messageKey = new MessageKeyInfo("");
148     	}
149         setMessageKey(messageKey);
150     	addStyleToWidget(fieldWidget);
151     	fieldElement = new FieldElement(fieldKey, messageKey, fieldWidget);
152     	setupField();
153     	
154     	//Add mapping from path to field definition if the definition has a data value
155     	if(fieldWidget instanceof HasDataValue &&!(this instanceof FieldDescriptorReadOnly)){
156     		Application.getApplicationContext().putPathToFieldMapping(null, Application.getApplicationContext().getParentPath()+fieldKey, this);
157 		}
158     	
159     	//Add cross constraints
160     	if(fieldElement.getFieldWidget() instanceof HasCrossConstraints){
161     		HasCrossConstraints crossConstraintWidget = (HasCrossConstraints) fieldElement.getFieldWidget();
162     		if(crossConstraintWidget!=null&&crossConstraintWidget.getCrossConstraints()!=null){
163     			for(String path:crossConstraintWidget.getCrossConstraints()){
164     		    	Application.getApplicationContext().putCrossConstraint(null, path, crossConstraintWidget);
165     			}
166     		}
167     	}
168     	
169     }
170 
171     protected void addStyleToWidget(Widget w){
172     	if(fieldKey != null && !fieldKey.isEmpty() && w != null){
173     		String style = this.fieldKey.replaceAll("/", "-");
174     		w.addStyleName(style);
175     	}
176     }
177 
178     protected void setupField() {
179     	if(metadata != null){
180     		if(MetadataInterrogator.isRequired(metadata)){
181     			fieldElement.setRequiredString("requiredMarker", "ks-form-module-elements-required");
182     		}
183     		else if(MetadataInterrogator.isRequiredForNextState(metadata)){
184     			String nextState = MetadataInterrogator.getNextState(metadata);
185     			if(nextState != null){
186     				if(nextState.equalsIgnoreCase("SUBMITTED")){
187     					fieldElement.setRequiredString("requiredOnSubmit", "ks-form-required-for-submit");
188     				}
189     				else if(nextState.equalsIgnoreCase("APPROVED")){
190     					fieldElement.setRequiredString("reqApproval", "ks-form-required-for-submit");
191     				}
192 					else if(nextState.equalsIgnoreCase("ACTIVE")){
193 						fieldElement.setRequiredString("reqActivate", "ks-form-required-for-submit");
194     				}
195 					else if(nextState.equalsIgnoreCase("INACTIVE") ||
196 							nextState.equalsIgnoreCase("RETIRED")){
197 						fieldElement.setRequiredString("reqDeactivate", "ks-form-required-for-submit");
198 					}
199 					else {
200 						fieldElement.setRequiredString("requiredOnSubmit", "ks-form-required-for-submit");
201 					}
202 
203     			}
204     		} else{
205                 fieldElement.clearRequiredText();
206             }
207     	}
208     }
209 
210     /**
211      * @see FieldElement#hideLabel()
212      */
213     public void hideLabel(){
214     	fieldElement.hideLabel();
215     }
216 
217     public boolean isLabelShown(){
218     	return fieldElement.isLabelShown();
219     }
220 
221     public FieldElement getFieldElement(){
222     	return fieldElement;
223     }
224 
225 	public String getFieldKey() {
226         return fieldKey;
227     }
228 
229     public void setFieldKey(String fieldKey) {
230 		this.fieldKey = fieldKey;
231 	}
232 
233     public String getFieldLabel() {
234         return fieldElement.getFieldName();
235     }
236 
237     public Widget getFieldWidget(){
238         if (fieldElement.getFieldWidget() == null){
239             Widget w = createFieldWidget();
240             fieldElement.setWidget(w);
241         }
242         return fieldElement.getFieldWidget();
243     }
244 
245     protected Widget createFieldWidget() {
246     	if (metadata == null) {
247     		// backwards compatibility for old ModelDTO code
248 	    	// for now, default to textbox if not specified
249 	    	Widget result = new KSTextBox();
250 	    	addStyleToWidget(result);
251 	    	return result;
252     	} else {
253     		Widget result = DefaultWidgetFactory.getInstance().getWidget(this);
254     		addStyleToWidget(result);
255     		return result;
256     	}
257     }
258 
259     public ModelWidgetBinding<?> getModelWidgetBinding() {
260         if(modelWidgetBinding == null){
261             if(fieldElement.getFieldWidget() instanceof RichTextEditor){
262             	modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.RichTextBinding.INSTANCE;
263             } else if (fieldElement.getFieldWidget() instanceof KSCheckBox){
264             	modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.HasValueBinding.INSTANCE;
265             } else if(fieldElement.getFieldWidget() instanceof MultiplicityComposite){
266         		modelWidgetBinding = MultiplicityCompositeBinding.INSTANCE;
267         	} else if (fieldElement.getFieldWidget()instanceof HasText) {
268         	    modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.HasTextBinding.INSTANCE;
269             } else if (fieldElement.getFieldWidget() instanceof KSSelectItemWidgetAbstract){
270                 modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.SelectItemWidgetBinding.INSTANCE;
271             } else if (fieldElement.getFieldWidget() instanceof HasDataValue){
272             	modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.HasDataValueBinding.INSTANCE;
273             } else if (fieldElement.getFieldWidget() instanceof HasValue){
274             	modelWidgetBinding = org.kuali.student.common.ui.client.configurable.mvc.binding.HasValueBinding.INSTANCE;
275             }
276         }
277         return modelWidgetBinding;
278     }
279 
280     /**
281      * Allows additional processing to happen when a validation check is being processed when the input
282      * widget loses focus defined in the callback
283      * @param callback
284      */
285     public void setValidationCallBack(Callback<Boolean> callback){
286         validationRequestCallback = callback;
287     }
288 
289     public Callback<Boolean> getValidationRequestCallback(){
290         return validationRequestCallback;
291     }
292 
293 	/**
294 	 * Returns true if this field is marked as dirty.  In KS, dirty is when the field has been changed
295 	 * in some way - however not completely accurate
296 	 * @return
297 	 */
298 	public boolean isDirty() {
299 		return dirty;
300 	}
301 
302 	public void setDirty(boolean dirty) {
303 		this.dirty = dirty;
304 	}
305 
306 	/**
307 	 * Return true if the field has been touched by the user in some fashion, this is set by the field's section
308 	 * @return
309 	 */
310 	public boolean hasHadFocus() {
311 		return hasHadFocus;
312 	}
313 
314 	public void setHasHadFocus(boolean hasHadFocus) {
315 		this.hasHadFocus = hasHadFocus;
316 	}
317 
318     public Metadata getMetadata() {
319 		return metadata;
320 	}
321 
322     public void setMetadata(Metadata metadata) {
323 		this.metadata = metadata;
324         setupField();
325 	}
326 
327     public void setFieldWidget(Widget fieldWidget) {
328 		this.fieldElement.setWidget(fieldWidget);
329 	}
330 
331 	public String getModelId() {
332 		return modelId;
333 	}
334 
335 	public void setModelId(String modelId) {
336 		this.modelId = modelId;
337 	}
338 
339     /**
340      * Sets the ModelWidgetBinding for this field.  Changing this changes the way data from the server and
341      * passed to the server is processed with the widget.  Set this when some special processing or handling
342      * has to happen with the data in either phase.
343      * @param widgetBinding
344      */
345     public void setWidgetBinding(ModelWidgetBinding widgetBinding) {
346         this.modelWidgetBinding = widgetBinding;
347     }
348 
349     public MessageKeyInfo getMessageKey() {
350         return messageKey;
351     }
352 
353     public void setMessageKey(MessageKeyInfo messageKey) {
354         this.messageKey = messageKey;
355     }
356 
357 	/**
358 	 * Sets the optional flag
359 	 * Fields that are optional should not be displayed if there is no data in some cases,
360 	 * it is up to the section implementation whether or not to honor this flag
361 	 * @param optional
362 	 */
363 	public void setOptional(boolean optional){
364 		this.optional = optional;
365 	}
366 
367 	/**
368 	 * Fields that are optional should not be displayed if there is no data in some cases,
369 	 * it is up to the section implementation whether or not to honor this flag
370 	 */
371 	public boolean isOptional(){
372 		return optional;
373 	}
374 
375 	/**
376 	 * @return true if this field is visible to the user
377 	 */
378 	public boolean isVisible() {
379 		if (metadata != null){
380 			return metadata.isCanView();
381 		} else {
382 			return true;
383 		}
384 	}
385 
386 }