1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 /**
 26  * Generic Property Editor Widget.
 27  *
 28  * @author Mihai Bazon
 29  *
 30  * See initProperties() below
 31  * 
 32  * @extends		DwtComposite
 33  * @private
 34  */
 35 DwtPropertyEditor = function(parent, useDwtInputField, className, posStyle, deferred) {
 36 	if (arguments.length > 0) {
 37 		if (!className)
 38 			className = "DwtPropertyEditor";
 39 		DwtComposite.call(this, {parent:parent, className:className, posStyle:posStyle, deferred:deferred});
 40 		this._useDwtInputField = useDwtInputField != null ? useDwtInputField : true;
 41 		this._schema = null;
 42 		this._init();
 43 	}
 44 };
 45 
 46 DwtPropertyEditor.MSG_TIMEOUT = 4000; // 4 seconds should be plenty
 47 
 48 DwtPropertyEditor.MSG = {
 49 	// Now these 2 are kind of pointless...
 50 	// We should allow a message in the prop. object.
 51 	mustMatch     : "This field does not match validators: REGEXP",
 52 	mustNotMatch  : "This field matches anti-validators: REGEXP" // LOL
 53 };
 54 
 55 DwtPropertyEditor.prototype = new DwtComposite;
 56 DwtPropertyEditor.prototype.constructor = DwtPropertyEditor;
 57 
 58 DwtPropertyEditor.prototype.toString = function() { return "DwtPropertyEditor"; }
 59 
 60 DwtPropertyEditor.prototype._init = function() {
 61 	var div = document.createElement("div");
 62 	div.id = this._relDivId = Dwt.getNextId();
 63 	div.style.position = "relative";
 64 	var table = document.createElement("table");
 65 	table.id = this._tableId = Dwt.getNextId();
 66 	table.cellSpacing = table.cellPadding = 0;
 67 	table.appendChild(document.createElement("tbody"));
 68 	div.appendChild(table);
 69 	this.getHtmlElement().appendChild(div);
 70 	this.maxLabelWidth = 0;
 71 	this.maxFieldWidth = 0;
 72 	this._setMouseEventHdlrs();
 73 	this._onMouseDown = new AjxListener(this, this._onMouseDown);
 74 	this.addListener(DwtEvent.ONMOUSEDOWN, this._onMouseDown);
 75 };
 76 
 77 DwtPropertyEditor.prototype.getRelDiv = function() {
 78 	return document.getElementById(this._relDivId);
 79 };
 80 
 81 DwtPropertyEditor.prototype.getTable = function() {
 82 	return document.getElementById(this._tableId);
 83 };
 84 
 85 DwtPropertyEditor.prototype._onMouseDown = function(event) {
 86 	var target = event.target;
 87 	var tag = target.tagName.toLowerCase();
 88 	if (tag == "input") {
 89 		event._stopPropagation = false;
 90 		event._returnValue = true;
 91 		return true;
 92 	}
 93 	if (this._currentInputField && !this._currentInputField.onblur()) {
 94 		event._stopPropagation = true;
 95 		event._returnValue = false;
 96 		return false;
 97 	}
 98 	try {
 99 		while (target && tag != "tr") {
100 			target = target.parentNode;
101 			tag = target.tagName.toLowerCase();
102 		}
103 		if (target && target.__msh_doMouseDown)
104 			target.__msh_doMouseDown(event);
105 	} catch(ex) {};
106 };
107 
108 /**
109  * Call this function to retrieve an object that contains all properties,
110  * indexed by name.  Any "struct" property will map to an object that contains
111  * its child properties.
112  *
113  * For the sample schema below (see comments on initProperties()), we would
114  * retrieve an object like this (dots represent the edited value, or the
115  * initial value if the property wasn't modified):
116  *
117  *  {
118  *    userName : ... ,
119  *    address  : {
120  *                  street   : ... ,
121  *                  country  : ... ,
122  *               },
123  *    age      : ... ,
124  *    birthday : ...
125  *  }
126  */
127 DwtPropertyEditor.prototype.getProperties = function() {
128 	if (this._currentInputField)
129 		// make sure we get the value
130 		this._currentInputField.onblur();
131 	function rec(schema) {
132 		var prop = {}, tmp, n = schema.length;
133 		for (var i = 0; i < n; ++i) {
134 			tmp = schema[i];
135 			if (tmp.type == "struct")
136 				prop[tmp.name] = rec(tmp.children);
137 			else
138 				prop[tmp.name] = tmp.value;
139 		}
140 		return prop;
141 	};
142 	return rec(this._schema);
143 };
144 
145 DwtPropertyEditor.prototype.validateData = function() {
146 	var valid = true;
147 	function rec(schema) {
148 		var tmp, n = schema.length;
149 		for (var i = 0; i < n; ++i) {
150 			tmp = schema[i];
151 			if (tmp.type == "struct")
152 				rec(tmp.children);
153 			else if (!tmp._validate())
154 				valid = false;
155 		}
156 	};
157 	rec(this._schema);
158 	return valid;
159 };
160 
161 /** This function will initialize the Property Editor with a given schema and
162  * property set.
163  *
164  *  @param schema - declares which properties/types are allowed; see below
165  *  @param parent - parent schema, for subproperties
166  *
167  * "schema" is an object that maps property names to property declaration.
168  * Here's an example of what I have in mind:
169  *
170  *  [
171  *    {
172  *      label        : "User Name",
173  *      id           : "userName",
174  *      type         : "string",
175  *      value        : "",
176  *      minLength    : 4,
177  *      maxLength    : 8,
178  *      mustMatch    : /^[a-z][a-z0-9_]+$/i,
179  *      mustNotMatch : /^(admin|root|guest)$/i
180  *    },
181  *    {
182  *      label     : "Address",
183  *      id        : "address",
184  *      type      : "struct",
185  *      children  : [ // this is a nested schema definition
186  *              { label : "Street", id: "street", type: "string" },
187  *              { label  : "Country",
188  *                id     : "country",
189  *                type   : "enum",
190  *                item : [
191  							{ label : "US", value : "US" },
192  							{ label : "UK", value : "UK" },
193  							{ label : "KR", value : "KR" }
194  						 ] 
195  				}
196  *      ]
197  *    },
198  *    {
199  *      label     : "Age",
200  *      id        : "age",
201  *      type      : "integer",
202  *      minValue  : 18,
203  *      maxValue  : 80
204  *    },
205  *    {
206  *      label     : "Birthday",
207  *      id        : "birthday",
208  *      type      : "date",
209  *      minValue  : "YYYY/MM/DD"  // can we restrict the DwtCalendar?
210  *    }
211  *  ]
212  *
213  * The types we will support for now are:
214  *
215  *   - "number" / "integer" : Allows floating point numbers or integers only.
216  *     Properties: "minValue", "maxValue".
217  *
218  *   - "string" : Allows any string to be inserted.  "minLength", "maxLength",
219  *     "mustMatch", "mustNotMatch".
220  *
221  *   - "password" : Same as "string", only it's not displayed.
222  *
223  *   - "enum" : One of the strings listed in the value.
224  *
225  *   - "boolean" : Checkbox that yields string value of "true" and "false".
226  *
227  *   - "struct" : Composite property; doesn't have a value by itself, but has
228  *     child properties (the "children" array) that are defined in the same way
229  *     as a toplevel property.
230  *
231  * All types except "struct" will allow a "value" property which is expected
232  * to be of a valid type that matches all validating properties (such as
233  * minLength, etc.).  The value of this property will be displayed initially
234  * when the widget is constructed.
235  *
236  * Also, all types will support a "readonly" property.
237  */
238 DwtPropertyEditor.prototype.initProperties = function(schema, parent) {
239 	if (parent == null) {
240 		this._schema = schema;
241 		parent = null;
242 	}
243 	for (var i = 0; i < schema.length; ++i)
244 		this._createProperty(schema[i], parent);
245 };
246 
247 DwtPropertyEditor.prototype._createProperty = function(prop, parent) {
248 	var level = parent ? parent._level + 1 : 0;
249 	var tr = this.getTable().firstChild.appendChild(document.createElement("tr"));
250 
251 	// Initialize the "prop" object with some interesting attributes...
252 	prop._parent = parent;
253 	prop._level = level;
254 	prop._rowElId = tr.id = Dwt.getNextId();
255 	prop._propertyEditor = this;
256 
257 	// ... and methods.
258 	for (var i in DwtPropertyEditor._prop_functions)
259 		prop[i] = DwtPropertyEditor._prop_functions[i];
260 
261 	prop._init();
262 
263 	// indent if needed
264 	tr.className = "level-" + level;
265 
266 	if (prop.visible == "false")
267 		tr.className += " invisible";
268 
269 	if (prop.readonly)
270 		tr.className += " readonly";
271 
272 	if (prop.type != "struct") {
273 		tr.className += " " + prop.type;
274 
275 		// this is a simple property, create a label and value cell.
276 		var tdLabel = document.createElement("td");
277 		tdLabel.className = "label";
278 		
279 		if(prop.type=="checkboxgroup"){
280 			tdLabel.className+=" grouplabel";
281 		}
282 		
283 		tr.appendChild(tdLabel);
284 		var html = AjxStringUtil.htmlEncode(prop.label || prop.name);
285 		if (prop.required)
286 			html += "<span class='DwtPropertyEditor-required'>*</span>";
287 		tdLabel.innerHTML = html;
288 		var tdField = document.createElement("td");
289 		tdField.className = "field";
290 		tr.appendChild(tdField);
291 
292 		switch (prop.type) {
293 			case "boolean" : this._createCheckbox(prop, tdField); break;
294 		    case "enum"    : this._createDropDown(prop, tdField); break;
295 		    case "date"    : this._createCalendar(prop, tdField); break;
296    		    case "checkboxgroup"	:	this._createCheckBoxGroup(prop,tdField); break;		    
297 		    default        :
298 			if (this._useDwtInputField)
299 				this._createInputField(prop, tdField);
300 			else {
301 				tdField.innerHTML = prop._makeDisplayValue();
302 				tr.__msh_doMouseDown = DwtPropertyEditor.simpleClosure(prop._edit, prop);
303 			}
304 			break;
305 		}
306 
307 		prop._fieldCellId = tdField.id = Dwt.getNextId();
308 		// prop._labelCellId = tdLabel.id = Dwt.getNextId();
309 
310 		if (tdLabel.offsetWidth > this.maxLabelWidth)
311 			this.maxLabelWidth = tdLabel.offsetWidth;
312 		if (tdField.offsetWidth > this.maxFieldWidth)
313 			this.maxFieldWidth = tdField.offsetWidth;
314 	} else {
315 		var td = document.createElement("td");
316 		td.colSpan = 2;
317 		tr.appendChild(td);
318 		td.className = "label";
319 		tr.className += " expander-collapsed";
320 		td.innerHTML = [ "<div>", AjxStringUtil.htmlEncode(prop.label), "</div>" ].join("");
321 		this.initProperties(prop.children, prop);
322 		tr.__msh_doMouseDown = DwtPropertyEditor.simpleClosure(prop._toggle, prop);
323 	}
324 
325 	// collapsed by default
326 	if (level > 0) {
327 		tr.style.display = "none";
328 		parent._hidden = true;
329 	}
330 };
331 
332 // <FIXME: this will create problems when the first property is a "struct">
333 DwtPropertyEditor.prototype.setFixedLabelWidth = function(w) {
334 	try {
335 		this.getTable().rows[0].cells[0].style.width = (w || this.maxLabelWidth) + "px";
336 	} catch(ex) {};
337 };
338 
339 DwtPropertyEditor.prototype.setFixedFieldWidth = function(w) {
340 	try {
341 		this.getTable().rows[0].cells[1].style.width = (w || this.maxFieldWidth) + "px";
342 	} catch(ex) {};
343 };
344 // </FIXME>
345 
346 DwtPropertyEditor.prototype._setCurrentMsgDiv = function(div) {
347 	this._currentMsgDiv = div;
348 	this._currentMsgDivTimer = setTimeout(
349 		DwtPropertyEditor.simpleClosure(this._clearMsgDiv, this),
350 		DwtPropertyEditor.MSG_TIMEOUT);
351 };
352 
353 DwtPropertyEditor.prototype._clearMsgDiv = function() {
354 	try {
355 		this._stopMsgDivTimer();
356 	} catch(ex) {};
357 	var div = this._currentMsgDiv;
358 	if (div) {
359 		div.parentNode.removeChild(div);
360 		this._currentMsgDiv = div = null;
361 		this._currentMsgDivTimer = null;
362 	}
363 };
364 
365 DwtPropertyEditor.prototype._stopMsgDivTimer = function() {
366 	if (this._currentMsgDivTimer) {
367 		clearTimeout(this._currentMsgDivTimer);
368 		this._currentMsgDivTimer = null;
369 	}
370 };
371 
372 // This is bad.  We're messing with internals.  I think there should be an
373 // option in DwtComposite to specify the element where to add the child, rather
374 // than simply getHtmlElement().appendChild(child).
375 DwtPropertyEditor.prototype.addChild = function(child) {
376 	if (!this._currentFieldCell)
377 		DwtComposite.prototype.addChild.call(this, child);
378 	else {
379 		this._children.add(child);
380 		this._currentFieldCell.appendChild(child.getHtmlElement());
381 	}
382 };
383 
384 DwtPropertyEditor.prototype._createCheckbox = function(prop, target) {
385 
386 	var checkbox = document.createElement("input");
387 	checkbox._prop = prop;
388 	checkbox.id = prop.name;
389 	checkbox.type = 'checkbox';
390 	
391 	if (checkbox.attachEvent) {
392 		checkbox.attachEvent("onclick",prop._onCheckboxChange);
393 	}
394     else if (checkbox.addEventListener) {
395 		checkbox.addEventListener("click", prop._onCheckboxChange, false);			
396 	}
397 	
398 	this._children.add(checkbox);
399 	target.appendChild(checkbox);
400     if (prop.value == 'true')
401 		checkbox.checked = prop.value;
402 };
403 
404 DwtPropertyEditor.prototype._createCheckBoxGroup = function(prop, target) {
405 	
406 	var div = document.createElement("div");
407 	div._prop = prop;
408 	div.id = prop.name;
409 	//div._prop._checkBox = [];
410 	prop._checkBox = [];
411 	div.appendChild(document.createTextNode(prop.value));
412 	
413 	var table = document.createElement("table");
414 	table.id = Dwt.getNextId();
415 	table.border=0;
416 	table.cellSpacing = table.cellPadding = 0;
417 	table.appendChild(document.createElement("tbody"));
418 	
419 	
420 	for(var i=0;i<prop.checkBox.length;i++){
421 	var tr = document.createElement("tr");
422 
423 	var tdField1 = document.createElement("td");
424 	tdField1.className = "field";	
425 	var checkBox = this._createCheckboxForGroup(prop,prop.checkBox[i],tdField1);
426 	tr.appendChild(tdField1);
427 	
428 	checkBox._label = prop.checkBox[i].label;
429 	
430 	var tdField2 = document.createElement("td");
431 	tdField2.className = "field";
432 	tdField2.appendChild(document.createTextNode(prop.checkBox[i].label));
433 	tr.appendChild(tdField2);
434 	
435 	table.firstChild.appendChild(tr);	
436 
437 //	div._prop._checkBox[i]=checkBox;
438 	prop._checkBox[i]=checkBox;
439 	}
440 	
441 	div.appendChild(table);
442 	
443 	this._children.add(div);
444 	target.appendChild(div);
445 	return div;
446 };
447 
448 DwtPropertyEditor.prototype._createCheckboxForGroup = function(parent_prop,prop, target) {
449 	var checkbox = document.createElement("input");
450 	checkbox._prop = parent_prop;
451 	checkbox.id = prop.name;
452 	checkbox.type = 'checkbox';
453 	if (prop.value == 'true')
454 		checkbox.checked = prop.value;
455 		if (checkbox.attachEvent) {
456 		    checkbox.attachEvent("onclick", parent_prop._onCheckboxGroupChange);
457 		}
458         else if (checkbox.addEventListener) {
459 		    checkbox.addEventListener("click", parent_prop._onCheckboxGroupChange, false);
460 		}
461 	this._children.add(checkbox);
462 	target.appendChild(checkbox);
463 	return checkbox;
464 };
465 
466 
467 DwtPropertyEditor.prototype._createDropDown = function(prop, target) {
468 	this._currentFieldCell = target;
469 	var item, sel,
470 		i       = 0,
471 		options = [],
472 		items   = prop.item;
473 	while (item = items[i])
474 		options[i++] = new DwtSelectOption(item.value,
475 						   item.value == prop.value,
476 						   item.label);
477 	prop._select = sel = new DwtSelect({parent:this, options:options});
478 	sel.addChangeListener(new AjxListener(prop, prop._onSelectChange));
479 	sel.addListener(DwtEvent.ONMOUSEDOWN, this._onMouseDown);
480 	this._currentFieldCell = null;
481 };
482 
483 DwtPropertyEditor.prototype._createCalendar = function(prop, target) {
484 	this._currentFieldCell = target;
485 	var btn = new DwtButton({parent:this});
486 	this._currentFieldCell = null;
487 
488 	btn.setText(prop._makeDisplayValue());
489 	var menu = new DwtMenu({parent:btn, style:DwtMenu.CALENDAR_PICKER_STYLE});
490 	menu.setAssociatedObj(btn);
491 	var cal = new DwtCalendar({parent:menu});
492 	var date = new Date();
493 	date.setTime(prop.value);
494 	cal.setDate(date);
495 	cal.setSize(150, "auto");
496 	cal.addSelectionListener(new AjxListener(prop, prop._onCalendarSelect));
497 	btn.setMenu(menu);
498 
499 	prop._dateButton = btn;
500 	prop._dateCalendar = cal;
501 };
502 
503 DwtPropertyEditor.DWT_INPUT_FIELD_TYPES = {
504 	"string"    : DwtInputField.STRING,
505 	"password"  : DwtInputField.PASSWORD,
506 	"integer"   : DwtInputField.INTEGER,
507 	"number"    : DwtInputField.FLOAT
508 };
509 
510 DwtPropertyEditor.prototype._createInputField = function(prop, target) {
511 	this._currentFieldCell = target;
512 	var type = DwtPropertyEditor.DWT_INPUT_FIELD_TYPES[prop.type]
513 		|| DwtInputField.STRING;
514 	var field = new DwtInputField({parent: this, type: type, initialValue: prop.value, maxLen: prop.maxLength, rows: prop.rows});
515 	if (type == DwtInputField.INTEGER || type == DwtInputField.FLOAT) {
516 		field.setValidNumberRange(prop.minValue || null,
517 					  prop.maxValue || null);
518 		if (prop.decimals != null)
519 			field.setNumberPrecision(prop.decimals);
520 	}
521 	if (type == DwtInputField.STRING || type == DwtInputField.PASSWORD)
522 		field.setValidStringLengths(prop.minLength, prop.maxLength);
523 	if (prop.required)
524 		field.setRequired(true);
525 	this._currentFieldCell = null;
526 	prop._inputField = field;
527 	field.setValue(prop.value);
528 	if (prop.readonly)
529 		field.setReadOnly(true);
530 	field.setValidationCallback(new AjxCallback(prop, prop._onDwtInputFieldValidated));
531 };
532 
533 // these will be merged to each prop object that comes in the schema
534 DwtPropertyEditor._prop_functions = {
535 
536 	_init : function() {
537 		this.type != null || (this.type = "string");
538 		this.value != null || (this.value = "");
539 		this._initialVal = this.value;
540 
541 		if (this.type == "date") {
542 			if (!this.value) {
543 // 				var tmp = new Date();
544 // 				tmp.setHours(0);
545 // 				tmp.setMinutes(0);
546 // 				tmp.setSeconds(0);
547 				this.value = new Date().getTime();
548 			}
549 			if (!this.format)
550 				this.format = AjxDateUtil.getSimpleDateFormat().toPattern();
551 		}
552 	},
553 
554 	_modified : function() {
555 		return this._initialVal != this.value;
556 	},
557 
558 	_getRowEl : function() {
559 		return document.getElementById(this._rowElId);
560 	},
561 
562 	_makeDisplayValue : function() {
563 		var val = this._getValue();
564 		switch (this.type) {
565 		    case "password" :
566 			val = val.replace(/./g, "*");
567 			break;
568 		    case "date" :
569 			var date = new Date();
570 			date.setTime(val);
571 			val = AjxDateFormat.format(this.format, date);
572 			break;
573 		}
574 		if (val == "")
575 			val = "<br />";
576 		else
577 			val = AjxStringUtil.htmlEncode(String(val));
578 		return val;
579 	},
580 
581 	_display : function(visible) {
582 		var
583 			c = this.children,
584 			d = visible ? "" : "none";
585 		if (c) {
586 			var i = c.length;
587 			while (--i >= 0) {
588 				c[i]._getRowEl().style.display = d;
589 				if (!visible)
590 					c[i]._display(false);
591 			}
592 			this._hidden = !visible;
593 
594 			// change the class name accordingly
595 			var tr = this._getRowEl();
596 			tr.className = tr.className.replace(
597 				/expander-[^\s]+/,
598 				visible ? "expander-expanded" : "expander-collapsed");
599 		}
600 	},
601 
602 	_toggle : function() { this._display(this._hidden); },
603 
604 	_edit : function() {
605 		// Depending on the type, this should probably create different
606 		// fields for editing.  For instance, in a "date" property we
607 		// would want a calendar, while in a "list" property we would
608 		// want a drop-down select box.
609 
610 		if (this.readonly)
611 			return;
612 
613 		switch (this.type) {
614 		    case "string" :
615 		    case "number" :
616 		    case "integer" :
617 		    case "password" :
618 			setTimeout(
619 				DwtPropertyEditor.simpleClosure(
620 					this._createInputField, this), 50);
621 			break;
622 
623 // 		    default :
624 // 			alert("We don't support this type yet");
625 		}
626 	},
627 
628 	_createInputField : function() {
629 		var	pe     = this._propertyEditor;
630 		var td     = document.getElementById(this._fieldCellId);
631 		var canvas = pe.getRelDiv();
632 		var input  = document.createElement("input");
633 
634 		input.className = "DwtPropertyEditor-input " + this.type;
635 		input.setAttribute("autocomplete", "off");
636 
637 		input.type = this.type == "password"
638 			? "password"
639 			: "text";
640 
641 		var left = td.offsetLeft, top = td.offsetTop;
642 		if (AjxEnv.isGeckoBased) {
643 			--left;
644 			--top;
645 		}
646 		input.style.left = left + "px";
647 		input.style.top = top + "px";
648 		input.style.width = td.offsetWidth + 1 + "px";
649 		input.style.height = td.offsetHeight + 1 + "px";
650 
651 		input.value = this._getValue();
652 
653 		canvas.appendChild(input);
654 		input.focus();
655 
656 		input.onblur = DwtPropertyEditor.simpleClosure(this._saveInput, this);
657 		input.onkeydown = DwtPropertyEditor.simpleClosure(this._inputKeyPress, this);
658 
659 		this._propertyEditor._currentInputField = this._inputField = input;
660 		if (!AjxEnv.isGeckoBased)
661 			input.select();
662 		else
663 			input.setSelectionRange(0, input.value.length);
664 	},
665 
666 	_getValue : function() {
667 		return this.value || "";
668 	},
669 
670 	_checkValue : function(val) {
671 		var empty = val == "";
672 
673 		if (empty) {
674 			if (!this.required)
675 				return val;
676 			this._displayMsg(AjxMsg.valueIsRequired);
677 			return null;
678 		}
679 
680 		if (this.maxLength != null && val.length > this.maxLength) {
681 			this._displayMsg(AjxMessageFormat.format(AjxMsg.stringTooLong, this.maxLength));
682 			return null;
683 		}
684 
685 		if (this.minLength != null && val.length < this.minLength) {
686 			this._displayMsg(AjxMessageFormat.format(AjxMsg.stringTooShort, this.minLength));
687 			return null;
688 		}
689 
690 		if (this.mustMatch && !this.mustMatch.test(val)) {
691 			this._displayMsg(this.msg_mustMatch ||
692 					 DwtPropertyEditor.MSG.mustMatch.replace(
693 						 /REGEXP/, this.mustMatch.toString()));
694 			return null;
695 		}
696 
697 		if (this.mustNotMatch && this.mustNotMatch.test(val)) {
698 			this._displayMsg(this.msg_mustNotMatch ||
699 					 DwtPropertyEditor.MSG.mustNotMatch.replace(
700 						 /REGEXP/, this.mustNotMatch.toString()));
701 			return null;
702 		}
703 
704 		switch (this.type) {
705 		    case "integer" :
706 		    case "number" :
707 			var n = new Number(val);
708 			if (isNaN(n)) {
709 				this._displayMsg(AjxMsg.notANumber);
710 				return null;
711 			}
712 			if (this.type == "integer" && Math.round(n) != n) {
713 				this._displayMsg(AjxMsg.notAnInteger);
714 				return null;
715 			}
716 			if (this.minValue != null && n < this.minValue) {
717 				this._displayMsg(AjxMessageFormat.format(AjxMsg.numberLessThanMin, this.minValue));
718 				return null;
719 			}
720 			if (this.maxValue != null && n > this.maxValue) {
721 				this._displayMsg(AjxMessageFormat.format(AjxMsg.numberMoreThanMax, this.maxValue));
722 				return null;
723 			}
724 			val = n;
725 			if (this.type == "number" && this.decimals != null) {
726 				var str = val.toString();
727 				var pos = str.indexOf(".");
728 				if (pos == -1)
729 					pos = str.length;
730 				val = val.toPrecision(pos + this.decimals);
731 			}
732 			break;
733 		}
734 		return val;
735 	},
736 
737 	_displayMsg : function(msg) {
738 		var x, y, w, h;
739 		var pe  = this._propertyEditor;
740 		var div = pe._currentMsgDiv;
741 
742 		if (!div) {
743 			div = document.createElement("div");
744 			div.className = "DwtPropertyEditor-ErrorMsg";
745 			pe.getRelDiv().appendChild(div);
746 		} else
747 			pe._stopMsgDivTimer();
748 		div.style.visibility = "hidden";
749 		div.innerHTML = AjxStringUtil.htmlEncode(msg);
750 		// position & size
751 		var table = pe.getTable();
752 		w = table.offsetWidth; // padding & border!
753 		if (!AjxEnv.isIE)
754 			w -= 12;
755 		x = table.offsetLeft;
756 		div.style.left = x + "px";
757 		div.style.width = w + "px";
758 		h = div.offsetHeight;
759 		var td = document.getElementById(this._fieldCellId);
760 		y = td.offsetTop + td.offsetHeight;
761 		if (y + h > table.offsetTop + table.offsetHeight)
762 			y = td.offsetTop - h;
763 		div.style.top = y + "px";
764 		div.style.visibility = "";
765 		pe._setCurrentMsgDiv(div);
766 	},
767 
768 	_saveInput : function() {
769 		var input = this._inputField;
770 		var val = this._checkValue(input.value);
771 		if (val != null) {
772 			this._setValue(val);
773 			input.onblur = input.onkeyup = input.onkeydown = input.onkeypress = null;
774 			var td = document.getElementById(this._fieldCellId);
775 			td.innerHTML = this._makeDisplayValue();
776 			this._inputField = null;
777 			this._propertyEditor._currentInputField = null;
778 			this._propertyEditor._clearMsgDiv();
779 			input.parentNode.removeChild(input);
780 			return true;
781 		} else {
782 			if (input.className.indexOf(" DwtPropertyEditor-input-error") == -1)
783 				input.className += " DwtPropertyEditor-input-error";
784 			input.focus();
785 			return false;
786 		}
787 	},
788 
789 	_inputKeyPress : function(ev) {
790 		ev || (ev = window.event);
791 		var input = this._inputField;
792 		if (ev.keyCode == 13) {
793 			this._saveInput();
794 		} else if (ev.keyCode == 27) {
795 			input.value = this._getValue();
796 			this._saveInput();
797 		} else {
798 			this._propertyEditor._clearMsgDiv();
799 			input.className = input.className.replace(/ DwtPropertyEditor-input-error/, "");
800 		}
801 	},
802 
803 	_onCheckboxChange : function(ev) {
804 		ev || (ev = window.event);
805 		var el = AjxEnv.isIE ? ev.srcElement : ev.target;
806 		el._prop._setValue(el.checked ? "true" : "false");
807 	},
808 
809 	_onSelectChange : function() {
810 		this._setValue(this._select.getValue());
811 	},
812 
813 	_onCheckboxGroupChange	:	function(ev) {
814 		ev || (ev = window.event);
815 		var el = AjxEnv.isIE ? ev.srcElement : ev.target;
816 		var chkBxs=el._prop._checkBox;
817 		var val = [];
818 		for(var i=0;i<chkBxs.length;i++){
819 			if(chkBxs[i].checked){
820 				val.push(chkBxs[i]._label);
821 			}
822 		}
823 		el._prop._setValue(val);
824 	},	
825 
826 	_onCalendarSelect : function() {
827 		this._setValue(this._dateCalendar.getDate().getTime());
828 		this._dateButton.setText(this._makeDisplayValue());
829 	},
830 
831 	_onDwtInputFieldValidated : function(dwtField, validated, value) {
832 		if (validated)
833 			this._setValue(value);
834 	},
835 
836 	_setValue : function(val) {
837 		this.value = val;
838 		var tr = this._getRowEl();
839 		tr.className = tr.className.replace(/ dirty/, "");
840 		if (this._modified())
841 			tr.className += " dirty";
842 	},
843 
844 	_validate : function() {
845 		if (this._inputField) {
846 			if (this._inputField instanceof DwtInputField)
847 				return this._inputField.validate();
848 			else
849 				return this._inputField.onblur();
850 		} else
851 			return true;
852 	}
853 };
854 
855 // Since we don't like nested functions...
856 DwtPropertyEditor.simpleClosure = function(func, obj) {
857 	return function() { return func.call(obj, arguments[0]); };
858 };
859