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