1 include(bbq.gui.GUIWidget);
  2 include(bbq.util.Log);
  3 
  4 bbq.gui.form.FormField = new Class.create(bbq.gui.GUIWidget, /** @lends bbq.gui.form.FormField.prototype */ {
  5 	_behaviours: null,
  6 	_preTransformValidators: null,
  7 	_postTransformValidators: null,
  8 	_transformer: null,
  9 
 10 	/**
 11 	 * Dispatches the following notifications:
 12 	 *
 13 	 * onError
 14 	 *
 15 	 * @constructs
 16 	 * @extends bbq.gui.GUIWidget
 17 	 * @param {Object} options
 18 	 * @param {Object} [options.value] An initial value
 19 	 * @param {Function} [options.onChange] A callback method invoked when the value of this field changes
 20 	 * @param {Object} [options.valueTransformer]
 21 	 * @param {Function} [options.valueTransformer.transform] Takes one argument and transforms it to a different value e.g. 1 to true
 22 	 * @param {String} [options.name] The name to be set on the input field
 23 	 */
 24 	initialize: function($super, options) {
 25 		try {
 26 			$super(options);
 27 
 28 			this._behaviours = [];
 29 			this._preTransformValidators = [];
 30 			this._postTransformValidators = [];
 31 
 32 			this.setRootNode("input");
 33 			this.addClass("FormField");
 34 
 35 			if(this.options.onChange) {
 36 				this.registerListener("onChange", this.options.onChange);
 37 			}
 38 
 39 			if(this.options.valueTransformer) {
 40 				this.setValueTransformer(this.options.valueTransformer);
 41 			}
 42 
 43 			if(this.options.name) {
 44 				this.setAttribute("name", this.options.name);
 45 			}
 46 
 47 			this.registerListener("onFocus", function() {
 48 				FocusWatcher.setKeypressCallbackObject(this);
 49 			}.bind(this));
 50 
 51 			this.registerListener("onBlur", function() {
 52 				FocusWatcher.setKeypressCallbackObject(null);
 53 			}.bind(this));
 54 		} catch(e) {
 55 			Log.error("Error constructing FormField", e);
 56 		}
 57 	},
 58 
 59 	/**
 60 	 * @inheritDoc
 61 	 */
 62 	setRootNode: function($super, rootNode) {
 63 		$super(rootNode);
 64 
 65 		this.getRootNode().onfocus = this.notifyListeners.bind(this, "onFocus");
 66 		this.getRootNode().onblur = this.notifyListeners.bind(this, "onBlur");
 67 		this.getRootNode().onchange = this.notifyListeners.bind(this, "onChange");
 68 
 69 		if(!Object.isUndefined(this.options.value)) {
 70 			this._setRawValue(this.options.value);
 71 		}
 72 	},
 73 
 74 	_getRawValue: function() {
 75 		return this.getRootNode().value;
 76 	},
 77 
 78 	_setRawValue: function(value) {
 79 		if(this._transformer && this._transformer.deTransform) {
 80 			value = this._transformer.deTransform(value);
 81 		}
 82 
 83 		this.getRootNode().value = value;
 84 	},
 85 
 86 	/**
 87 	 * Returns the value contained within this field.  The value will be
 88 	 * validated - if the value is found to be invalid an exception
 89 	 * will be thrown.
 90 	 *
 91 	 * @throws {Error} The error has two fields {String} error for language translations and {bbq.gui.form.FormField} field which is the field which caused the error.
 92 	 * @returns {Object}
 93 	 */
 94 	getValue: function() {
 95 		var value = this._getRawValue();
 96 
 97 		return this._validateAndTransform(value);
 98 	},
 99 
100 	/**
101 	 * Returns the name of this field.
102 	 *
103 	 * @returns {String}
104 	 */
105 	getName: function() {
106 		return this.options.name;
107 	},
108 
109 	/**
110 	 * Returns the current value of the field without first validating it (which could
111 	 * cause an exception to be thrown).
112 	 *
113 	 * @returns {Object}
114 	 */
115 	getUnvalidatedValue: function() {
116 		var value = this._getRawValue();
117 
118 		return this._transform(value);
119 	},
120 
121 	_validateAndTransform: function(value) {
122 		this.removeClass("FormField_error");
123 
124 		// run pre-transform validators
125 		this._preValidate(value);
126 
127 		// transform the value if necessary
128 		value = this._transform(value);
129 
130 		// run post transform validators
131 		this._postValidate(value);
132 
133 		// return our value
134 		return value;
135 	},
136 
137 	_transform: function(value) {
138 		// if we have a value transformer, transform the value
139 		if (this._transformer) {
140 			value = this._transformer.transform(value);
141 		}
142 
143 		return value;
144 	},
145 
146 	_preValidate: function(value) {
147 		this._preTransformValidators.each(function(validator) {
148 			var result = validator.validate(value);
149 
150 			if (result) {
151 				var error = {error: result, field: this};
152 
153 				this.addClass("FormField_error");
154 				this.notifyListeners("onError", error);
155 
156 				throw error;
157 			}
158 		}.bind(this));
159 	},
160 
161 	_postValidate: function(value) {
162 		this._postTransformValidators.each(function(validator) {
163 			var result = validator.validate(value);
164 
165 			if (result) {
166 				var error = {error: result, field: this};
167 
168 				this.addClass("FormField_error");
169 				this.notifyListeners("onError", error);
170 
171 				throw error;
172 			}
173 		}.bind(this));
174 	},
175 
176 	/**
177 	 * Sets the value of the field
178 	 * @param value
179 	 */
180 	setValue: function(value) {
181 		this._setRawValue(value);
182 
183 		this._validateAndTransform(value);
184 	},
185 
186 	/**
187 	 * Adds a validator to ensure certain criteria are met.
188 	 *
189 	 * @param validator
190 	 * @see bbq.gui.form.validator.EmailValidator
191 	 * @see bbq.gui.form.validator.MustEqualFieldValidator
192 	 * @see bbq.gui.form.validator.NotNullValidatorValidator
193 	 * @see bbq.gui.form.validator.URLValidator
194 	 */
195 	addValidator: function(validator) {
196 		if(!validator) {
197 			Log.error("Invalid validator!");
198 
199 			return;
200 		}
201 
202 		if(!validator.validate) {
203 			Log.error("Validator should implement a validate method");
204 
205 			return;
206 		}
207 
208 		if (validator.isPostTransformValidator && validator.isPostTransformValidator()) {
209 			this._postTransformValidators.push(validator);
210 		} else {
211 			this._preTransformValidators.push(validator);
212 		}
213 	},
214 
215 	/**
216 	 * Adds a behaviour modifier
217 	 *
218 	 * @param behaviour
219 	 * @see bbq.gui.form.behaviour.PlaceholderTextBehaviour
220 	 * @see bbq.gui.form.behaviour.ValidateOnBlurBehaviour
221 	 */
222 	addBehaviour: function(behaviour) {
223 		if (!behaviour) {
224 			Log.error("Invalid behaviour!");
225 		}
226 
227 		if (!behaviour.setField) {
228 			Log.error("Behaviour should implement a setField method");
229 
230 			return;
231 		}
232 
233 		behaviour.setField(this);
234 
235 		this._behaviours.push(behaviour);
236 	},
237 
238 	/**
239 	 * Sets the value transformer which constrains the value of the field to certain values.
240 	 *
241 	 * @param transformer
242 	 * @see bbq.gui.form.transformer.BooleanValueTransformer
243 	 * @see bbq.gui.form.transformer.StrinkTokeniserTransformer
244 	 */
245 	setValueTransformer: function(transformer) {
246 		if (!transformer) {
247 			Log.error("Invalid transformer!");
248 		}
249 
250 		if (!transformer.transform) {
251 			Log.error("Transformer should implement a transform method");
252 
253 			return;
254 		}
255 		
256 		this._transformer = transformer;
257 	},
258 
259 	/**
260 	 * Removes the CSS class denoting that this field has focus
261 	 */
262 	loseFocus: function() {
263 		this.removeClass("FormField_focused");
264 	},
265 
266 	/**
267 	 * Adds the CSS class denoting that this field has focus
268 	 */
269 	acceptFocus: function() {
270 		this.addClass("FormField_focused");
271 	}
272 });
273