1 /*
  2  *  Copyright (C) 2008-2009 WaveMaker Software, Inc.
  3  *
  4  *  This file is part of the WaveMaker Client Runtime.
  5  *
  6  *  Licensed under the Apache License, Version 2.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
  9  *
 10  *      http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  *  Unless required by applicable law or agreed to in writing, software
 13  *  distributed under the License is distributed on an "AS IS" BASIS,
 14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  *  See the License for the specific language governing permissions and
 16  *  limitations under the License.
 17  */
 18 dojo.provide("wm.base.widget.LiveForm");
 19 dojo.require('wm.base.lib.data');
 20 
 21 wm.getLiveForms = function(inPage) {
 22 	var forms = [];
 23 	wm.forEachWidget(inPage.root, function(w) {
 24 		if (w instanceof wm.LiveForm)
 25 			forms.push(w);
 26 	})
 27 	return forms;
 28 }
 29 
 30 wm.getMatchingFormWidgets = function(inForm, inMatch) {
 31 	var match = [];
 32 	wm.forEach(inForm.widgets, function(w) {
 33 			if (inMatch(w))
 34 				match.push(w);
 35 			if ((w instanceof wm.Container) && !(w instanceof wm.LiveFormBase))
 36 				match = match.concat(wm.getMatchingFormWidgets(w, inMatch));
 37 		});
 38 		return match;
 39 };
 40 
 41 wm.getParentForm = function(inWidget) {
 42 	var w = inWidget.parent, r = inWidget.getRoot(), r = r && r.root;
 43 	while (w && w != r) {
 44 		if (w instanceof wm.LiveFormBase)
 45 			return w;
 46 		w = w.parent;
 47 	}
 48 }
 49 
 50 wm.getFormLiveView = function(inForm) {
 51 	var lv = inForm && inForm.getLiveVariable();
 52 	return lv && lv.liveView;
 53 }
 54 
 55 wm.getFormField = function(inWidget) {
 56 	var a = [], w = inWidget;
 57 	while (w && !(w instanceof wm.LiveForm)) {
 58 		if (w.formField)
 59 			a.unshift(w.formField);
 60 		w = wm.getParentForm(w);
 61 	}
 62 	return a.join('.');
 63 }
 64 
 65 wm.focusContainer = function(inContainer) {
 66 	wm.onidle(function() {
 67 		wm.forEachWidget(inContainer, function(w) {
 68 			if (w.focus && (!w.canFocus || w.canFocus())) {
 69 				w.focus();
 70 				return false;
 71 			}
 72 		});
 73 	});
 74 }
 75 
 76 /**
 77 	Base class for {@link wm.LiveForm}.
 78 	@name wm.LiveFormBase
 79 	@class
 80 	@extends wm.Panel
 81 	@noindex
 82 */
 83 dojo.declare("wm.LiveFormBase", wm.Panel, {
 84 	/** @lends wm.LiveFormBase.prototype */
 85 	editorHeight: "26px",
 86 	editorWidth: "100%",
 87 	captionSize: "50%",
 88 	captionAlign: "right",
 89 	captionPosition: "left",
 90 	height: "228px",
 91 	width: "100%",
 92 	//fitToContent: true,
 93 	layoutKind: "top-to-bottom",
 94 	readonly: false,
 95 	/**
 96 		The dataSet the LiveForm uses for source data.
 97 		
 98 		Typically dataSet is bound to a grid's <i>selectedItem</i> (where the grid is showing the output of a liveVariable) or directly to a liveVariable.
 99 		
100 		When dataSet uses a LiveView, LiveForm generates editors for each of the fields in its related liveView.
101 		When dataSet uses a LiveTable, a default view is created which contains all top level properties in the data type (not including composite key fields).
102 		
103 		Although it is possible to setup a LiveForm to do automatic CRUD operations when using a LiveTable, it's easiest
104 		to set up a LiveView instead.
105 		
106 		@type Variable
107 	*/
108 	dataSet: null,
109 	/**
110 		Data as modified by the form editors is available as <i>dataOutput</i>.
111 		@type Variable
112 	*/
113 	dataOutput: null,
114 	init: function() {
115 		this.dataOutput = new wm.Variable({name: "dataOutput", owner: this});
116 		this.inherited(arguments);
117 	},
118 	postInit: function() {
119 		this.inherited(arguments);
120 		this.dataOutput = this.$.dataOutput;
121 		if (wm.pasting)
122 			wm.fire(this, "designPasted");
123 		this.populateEditors();
124 	},
125 	//===========================================================================
126 	// Form data
127 	//===========================================================================
128 	setDataSet: function(inDataSet) {
129 		this.beginEditUpdate();
130 		this.dataSet = inDataSet;
131 		var d = this.getItemData();
132 		this.populateEditors();
133 		this.setDataOutput(d);
134 		this.endEditUpdate();
135 	},
136 	setDataOutput: function(inDataSet) {
137 		this.dataOutput.setDataSet(inDataSet);
138 	},
139 	clearDataOutput: function() {
140 		// FIXME: handle related editors specially
141 		dojo.forEach(this.getRelatedEditorsArray(), function(e) {
142 				e.clearDataOutput();
143 		});
144 		this.dataOutput.setData({});
145 	},
146 	getItemData: function() {
147 		return wm.fire(this.dataSet, "getCursorItem");
148 	},
149 	// FIXME: store this explicitly?
150 	_getDataType: function() {
151 		var t = (this.dataSet || 0).type;
152 		if (!wm.typeManager.isStructuredType(t)) {
153 			var v = this.getLiveVariable();
154 			t = v && v.type;
155 		}
156 		if (wm.typeManager.isStructuredType(t))
157 			return t;
158 	},
159 	// get the liveVariable related to this dataSet
160 	// currently just checks if dataSet is a liveVariable
161 	// currently does not check for subNards that may be part of a dataSet
162 	getLiveVariable: function() {
163 		var
164 			s = this.dataSet,
165 			o = s && s.owner;
166 		o = o && !(o instanceof wm.Variable) ? o : null;
167 		// if source not owned by a variable but it has a dataSet, use it if it's a LiveVariable
168 		if (o && o.dataSet && o.dataSet instanceof wm.LiveVariable)
169 			return o.dataSet;
170 		// otherwise walk owners to look for a LiveVariable
171 		while (s) {
172 			if (s instanceof wm.LiveVariable)
173 				return s;
174 			s = s.owner;
175 			if (!(s.owner instanceof wm.Variable))
176 				break;
177 		}
178 	},
179 	beginEditUpdate: function() {
180 		this.dataOutput.beginUpdate();
181 		dojo.forEach(this.getFormEditorsArray(), function(e) {
182 			wm.fire(e, "beginEditUpdate");
183 		});
184 	},
185 	endEditUpdate: function() {
186 		this.dataOutput.endUpdate();
187 		dojo.forEach(this.getFormEditorsArray(), function(e) {
188 			wm.fire(e, "endEditUpdate");
189 		});
190 	},
191 	//===========================================================================
192 	// Editor management
193 	//===========================================================================
194 	populateEditors: function() {
195 		var i = this.getItemData(), data = i ? i.getData() : null;
196 		dojo.forEach(this.getFormEditorsArray(), function(e) {
197 			if (e instanceof wm.LiveFormBase)
198 				wm.fire(e, "populateEditors");
199 			else {
200 				wm.fire(e, "setDataValue", [e.formField && data ? data[e.formField] : data]);
201 			}
202 		});
203 	},
204 	populateDataOutput: function() {
205 		var d = this.dataOutput;
206 		dojo.forEach(this.getFormEditorsArray(), function(e) {
207 			if (e instanceof wm.LiveFormBase)
208 				wm.fire(e, "populateDataOutput");
209 			else if (e.formField)
210 				d.setValue(e.formField, e.getDataValue());
211 		});
212 	},
213 	editStarting: function() {
214 		dojo.forEach(this.getFormEditorsArray(), function(e) {
215 			wm.fire(e, "editStarting");
216 		});
217 	},
218 	editCancelling: function() {
219 		dojo.forEach(this.getFormEditorsArray(), function(e) {
220 			wm.fire(e, "editCancelling");
221 		});
222 	},
223 	/**
224 		Clear all editors.
225 		As usual, the data clear is a change propagated via 
226 		bindings. So, typically, <i>dataOutput</i> is cleared too.
227 	*/
228 	clearData: function() {
229 		dojo.forEach(this.getFormEditorsArray(), function(e) {
230 			wm.fire(e, "clear");
231 		});
232 		// FIXME: handle related editors specially
233 		dojo.forEach(this.getRelatedEditorsArray(), function(e) {
234 			wm.fire(e, "clearData");
235 		});
236 	},
237 	getEditorsArray: function() {
238 		return wm.getMatchingFormWidgets(this, function(w) {
239 			return (w instanceof wm.Editor || w.constructor.superclass.declaredClass == "wm.Editor");
240 		});
241 	},
242 	// FIXME: handle related editors specially
243 	getRelatedEditorsArray: function(inContainer) {
244 		return wm.getMatchingFormWidgets(this, function(w) {
245 			return (w instanceof wm.RelatedEditor);
246 		});
247 	},
248 	getFormEditorsArray: function() {
249 		return wm.getMatchingFormWidgets(this, function(w) {
250 			//return ((w instanceof wm.Editor && w.formField !== undefined) || w instanceof wm.RelatedEditor);
251 			return (w.formField !== undefined);
252 		});
253 	},
254 	_getEditorBindSource: function(inSourceId) {
255 		var parts = (inSourceId || "").split(".");
256 		parts.pop();
257 		var
258 			s = parts.join('.'),
259 			v = this.getValueById(s);
260 		if (v instanceof wm.Editor || v instanceof wm.RelatedEditor)
261 			return v;
262 	},
263 	// get editors bound to dataOutput
264 	getBoundEditorsArray: function() {
265 		var editors = [];
266 		// get editors bound to dataOutput
267 		var wires = this.$.binding.wires;
268 		for (var i in wires) {
269 			w = wires[i];
270 			if (!w.targetId && w.targetProperty.indexOf("dataOutput") == 0) {
271 				e = this._getEditorBindSource(w.source);
272 				if (e)
273 					editors.push(e);
274 			}
275 		}
276 		return editors;
277 	},
278 	//===========================================================================
279 	// Editor setters
280 	//===========================================================================
281 	canChangeEditorReadonly: function(inEditor, inReadonly, inCanChangeFunc) {
282 		var c = dojo.isFunction(inCanChangeFunc);
283 		return !c || inCanChangeFunc(inEditor, this, inReadonly);
284 	},
285 	_setReadonly: function(inReadonly, inCanChangeFunc) {
286 		dojo.forEach(this.getFormEditorsArray(), function(e) {
287 			if (this.canChangeEditorReadonly(e, inReadonly, inCanChangeFunc))
288 				e.setReadonly(inReadonly);
289 		}, this);
290 		// FIXME: handle related editors specially
291 		dojo.forEach(this.getRelatedEditorsArray(), function(e) {
292 			if (this.canChangeEditorReadonly(e, inReadonly, inCanChangeFunc))
293 				e._setReadonly(inReadonly, inCanChangeFunc);
294 		}, this);
295 	},
296 	setReadonly: function(inReadonly) {
297 		this.readonly = inReadonly;
298 		this._setReadonly(inReadonly);
299 	},
300 	setCaptionSize: function(inSize) {
301 		this.captionSize = inSize;
302 		dojo.forEach(this.getEditorsArray(), function(e) {
303 			e.setCaptionSize(inSize);
304 		});
305 	},
306 	setCaptionUnits: function(inUnits) {
307 		this.captionUnits = inUnits;
308 		dojo.forEach(this.getEditorsArray(), function(e) {
309 			e.setCaptionUnits(inUnits);
310 		});
311 	},
312 	setCaptionAlign: function(inAlign) {
313 		this.captionAlign = inAlign;
314 		dojo.forEach(this.getEditorsArray(), function(e) {
315 			e.setCaptionAlign(inAlign);
316 		});
317 	},
318 	setCaptionPosition: function(inPosition) {
319 		this.captionPosition = inPosition;
320 		dojo.forEach(this.getEditorsArray(), function(e) {
321 			e.setCaptionPosition(inPosition);
322 		});
323 	},
324 	setEditorWidth: function(inEditorWidth) {
325 		this.editorWidth = inEditorWidth;
326 		dojo.forEach(this.getEditorsArray(), function(e) {
327 			e.setWidth(inEditorWidth);
328 		});
329 	},
330 	setEditorHeight: function(inEditorHeight) {
331 		this.editorHeight = inEditorHeight;
332 		dojo.forEach(this.getEditorsArray(), function(e) {
333 			e.setHeight(inEditorHeight);
334 		});
335 	},
336 	valueChanged: function(inProp, inValue) {
337 		// FIXME: disallow change messages from being set on our variable properties
338 		// they send these messages themselves when they change...
339 		if (this[inProp] instanceof wm.Variable)
340 			return;
341 		else
342 			this.inherited(arguments);
343 	},
344 	getViewDataIndex: function(inFormField) {
345 		return inFormField;
346 	},
347 	//===========================================================================
348 	// Data Navigation API
349 	//===========================================================================
350 	getRecordCount: function() {
351 		return wm.fire(this.getDataSource(), "getCount");
352 	},
353 	getDataSource: function() {
354 		if (!this._dataSource) {
355 			var
356 				b = this.$ && this.$.binding,
357 				v = (b && b.wires["dataSet"] || 0).source;
358 			this._dataSource = v && this.getValueById(v);
359 		}
360 		return this._dataSource;
361 	},
362 	setRecord: function(inIndex) {
363 		wm.fire(this.getDataSource(), "setCursor", [inIndex]);
364 	},
365 	setNext: function() {
366 		wm.fire(this.getDataSource(), "setNext");
367 	},
368 	setPrevious: function() {
369 		wm.fire(this.getDataSource(), "setPrevious");
370 	},
371 	setFirst: function() {
372 		wm.fire(this.getDataSource(), "setFirst");
373 	},
374 	setLast: function() {
375 		wm.fire(this.getDataSource(), "setLast");
376 	},
377 	getIndex: function() {
378 		return (this.getDataSource() || 0).cursor || 0;
379 	}
380 });
381 
382 /**
383 	LiveForm displays a set of editors for a data type.
384 	LiveForm's editors display the data in its <i>dataSet</i> property.
385 	Output data is provided via the <i>dataOutput</i> property.
386 	LiveForm can directly perform operations on the dataOuput without the need for additional services,
387 	it generates a set of editors automatically.
388 	An additional <i>editPanel</i> widget is added by default so that operations are available without additional user setup.
389 	@name wm.LiveForm
390 	@class
391 	@extends wm.LiveFormBase
392 */
393 dojo.declare("wm.LiveForm", wm.LiveFormBase, {
394 	/**
395 		@lends wm.LiveForm.prototype
396 	*/
397 	defaultButton: "",
398 	displayErrors: true,
399 	// process editing via liveData API
400 	// if this setting is off, users can manually handle editing events
401 	// and editor readonly/required states are not managed automatically
402 	// other than being toggled on/off when editing starts/stops
403 	liveEditing: true,
404 	liveSaving: true,
405 	liveVariable: null,
406 	_confirmDelete: true,
407 	_formMessages: {
408 		confirmDelete: "Are you sure you want to delete this data?"
409 	},
410 	_controlSubForms: false,
411 	destroy: function() {
412 		this._cancelDefaultButton();
413 		this.inherited(arguments);
414 	},
415 	init: function() {
416 		this.connect(this.domNode, "keyup", this, "keyup");
417 		// bc
418 		this.canBeginEdit = this.hasEditableData;
419 		this.inherited(arguments);
420 	},
421 	postInit: function() {
422 		this.inherited(arguments);
423 		this.initLiveVariable();
424 		// BC: if captionSize contains only digits, append units
425 		if (String(this.captionSize).search(/\D/) == -1)
426 			this.captionSize += this.captionUnits;
427 		// BC: if editorSize contains only digits, append units
428 		if (String(this.editorSize).search(/\D/) == -1)
429 			this.editorSize += this.editorSizeUnits;
430 		//
431 		if (this.liveEditing && !this.isDesignLoaded())
432 			this.setReadonly(this.readonly);
433 	},
434 	initLiveVariable: function() {
435 		var lv = this.liveVariable = new wm.LiveVariable({
436 			name: "liveVariable",
437 			owner: this,
438 			liveSource: (this.dataSet || 0).type,
439 			autoUpdate: false
440 		});
441 		this.connect(lv, "onBeforeUpdate", this, "beforeOperation");
442 		this.connect(lv, "onSuccess", this, "operationSucceeded");
443 		this.connect(lv, "onResult", this, "onResult");
444 		this.connect(lv, "onError", this, "onError");
445 	},
446 	//===========================================================================
447 	// Form data
448 	//===========================================================================
449 	setDataSet: function(inDataSet) {
450 		if (this.operation)
451 			return;
452 		this._cancelDefaultButton();
453 		this.inherited(arguments, [inDataSet]);
454 	},
455 	//===========================================================================
456 	// Edit API
457 	//===========================================================================
458 	/**
459 		Clear the form's editors and make them editable.
460 		Fires <i>onBeginInsert</i> event.
461 	*/
462 	beginDataInsert: function() {
463 		// Note: must clear dataOutput so that it's in a fresh state for insert
464 		// this is because we may have stale data from a previous setting that's not
465 		// cleared via clearing editors.
466 		// Because of this, any statically set / bound value to dataOutput will be blown away.
467 		this.clearDataOutput();
468 		this.beginEditUpdate();
469 		this.clearData();
470 		this.endEditUpdate();
471 		this.beginEdit("insert");
472 		this.onBeginInsert();
473 		this.validate();
474 		return true;
475 	},
476 	/**
477 		Make the form's editors and editable.
478 		Fires <i>onBeginUpdate</i> event.
479 	*/
480 	beginDataUpdate: function() {
481 		this.beginEdit("update");
482 		this.onBeginUpdate();
483 		return true;
484 	},
485 	beginEdit: function(inOperation) {
486 		this.editStarting();
487 		this.operation = inOperation;
488 		if (this.liveEditing) {
489 			if (this.hasLiveService())
490 				this._setReadonly(false, dojo.hitch(this, "_canChangeEditorReadonly", [inOperation]));
491 			else
492 				this.setReadonly(false);
493 		}
494 	},
495 	endEdit: function() {
496 		if (this.liveEditing)
497 			this.setReadonly(true);
498 		this.operation = null;
499 	},
500 	/**
501 		Cancels an edit by restoring the editors to the data from the <i>dataSet</i> property.
502 	*/
503 	cancelEdit: function() {
504 		this.editCancelling();
505 		var d = this.getItemData();
506 		this.beginEditUpdate();
507 		//this.clearData();
508 		this.dataOutput.setData(d);
509 		this.endEditUpdate();
510 		//wm.fire(this.dataSet, "notify");
511 		this.populateEditors();
512 		this.onCancelEdit();
513 		this.endEdit();
514 	},
515 	// editors that should not be changed during an edit should remain readonly
516 	_canChangeEditorReadonly: function(inOperations, inEditor, inForm, inReadonly) {
517 		if (inEditor instanceof wm.Editor && inEditor.formField) {
518 			var
519 				f = inEditor.formField,
520 				dt = inForm.dataSet.type,
521 				s = wm.typeManager.getTypeSchema(dt),
522 				pi = wm.typeManager.getPropertyInfoFromSchema(s, f),
523 				ops = inOperations;
524 			if (!f)
525 				return true;
526 			// NOTE: if an editor should be excluded or not changed 
527 			// for given operation then it should remain read only.
528 			//
529 			// NOTE: exclude is use for inserts only so 
530 			// we can simply leave it read only since the editor will be blank
531 			var
532 				// this field should not be changed for the given operations
533 				noChange = pi && dojo.some(pi.noChange, function(i) { return (dojo.indexOf(ops, i) > -1)}),
534 				// this field should not be excluded for the given operations
535 				exclude = pi && dojo.some(pi.exclude, function(i) { return (dojo.indexOf(ops, i) > -1)});
536 			if (!inReadonly && (noChange || exclude))
537 				return false;
538 		}
539 		return true;
540 	},
541 	//===========================================================================
542 	// Data Verification
543 	//===========================================================================
544 	hasLiveService: function() {
545 		return Boolean(wm.typeManager.getLiveService((this.dataSet || 0).type));
546 	},
547 	hasEditableData: function() {
548 		var v = this.dataOutput;
549 		return !this.liveEditing || (v && wm.typeManager.getLiveService(v.type) && wm.data.hasIncludeData(v.type, v.getData()));
550 	},
551 	//===========================================================================
552 	// Editing server interaction
553 	//===========================================================================
554 	_getDeferredSuccess: function() {
555 		var d = new dojo.Deferred();
556 		d.callback(true);
557 		return d;
558 	},
559 	saveData: function() {
560 		if (this.operation == "insert")
561 			return this.insertData();
562 		if (this.operation == "update")
563 			return this.updateData();
564 	},
565 	/**
566 		Performs an insert operation based on the data in the
567 		<i>dataOutput</i> property.
568 	*/
569 	insertData: function() {
570 		return this.doOperation("insert");
571 	},
572 	/**
573 		Performs an update operation based on the data in the
574 		<i>dataOutput</i> property.
575 	*/
576 	updateData: function() {
577 		return this.doOperation("update");
578 	},
579 	/**
580 		Performs a delete operation based on the data in the
581 		<i>dataOutput</i> property.
582 	*/
583 	deleteData: function() {
584 		if (!this._confirmDelete || confirm(this._formMessages.confirmDelete)) {
585 			this.onBeginDelete()
586 			return this.doOperation("delete");
587 		} else {
588 			this.cancelEdit();
589 		}
590 	},
591 	doOperation: function(inOperation) {
592 		this.populateDataOutput();
593 		var data = this.dataOutput.getData();
594 		if (this.liveSaving) {
595 			var lv = this.liveVariable;
596 			lv.setOperation(inOperation);
597 			lv.sourceData.setData(this.dataOutput.getData());
598 			return lv.update();
599 		} else {
600 			switch (this.operation) {
601 				case "insert":
602 					this.onInsertData();
603 					break;
604 				case "update":
605 					this.onUpdateData();
606 					break;
607 				case "delete":
608 					this.onDeleteData();
609 					break;
610 			}
611 			this.endEdit();
612 			return this._getDeferredSuccess();
613 		}
614 	},
615 	operationSucceeded: function(inResult) {
616 		// if we get result as an array, take the frist one
617 		if (dojo.isArray(inResult))
618 			inResult = inResult[0];
619 		var op = this.liveVariable.operation;
620 		//
621 		if (op == "insert" || op == "delete")
622 			this.dataSet.cursor = 0;
623 		if (op == "insert" || op == "update") {
624 			wm.fire(this.getItemData(), "setData", [inResult]);
625 			wm.fire(this.dataSet, "notify");
626 		}
627 		//
628 		switch (op) {
629 			case "insert":
630 				this.onInsertData(inResult);
631 				break;
632 			case "update":
633 				this.onUpdateData(inResult);
634 				break;
635 			case "delete":
636 				this.beginEditUpdate();
637 				this.clearData();
638 				this.endEditUpdate();
639 				this.onDeleteData(inResult);
640 				break;
641 		}
642 		this.onSuccess(inResult);
643 		this.endEdit();
644 	},
645 	beforeOperation: function() {
646 		this.onBeforeOperation(this.liveVariable.operation);
647 	},
648 	//===========================================================================
649 	// Form management
650 	//===========================================================================
651 	getSubFormsArray: function() {
652 		var forms = [], w;
653 		for (var i in this.widgets) {
654 			w = this.widgets[i];
655 			if (w instanceof wm.LiveForm) {
656 				forms.push(w);
657 				forms = forms.concat(w.getSubFormsArray());
658 			}
659 		}
660 		return forms;
661 	},
662 	clearData: function() {
663 		this.inherited(arguments);
664 		if (this._controlSubForms)
665 			dojo.forEach(this.getSubFormsArray(), function(f) {
666 				f.clearData();
667 			});
668 	},
669 	_setReadonly: function(inReadonly, inCanChangeFunc) {
670 		this.inherited(arguments);
671 		if (this._controlSubForms)
672 			dojo.forEach(this.getSubFormsArray(), function(f) {
673 				f.setReadonly(inReadonly);
674 			});
675 	},
676 	//===========================================================================
677 	// Default Button Processing
678 	//===========================================================================
679 	forceValidation: function() {
680 		dojo.forEach(this.getEditorsArray(), function(e) {
681 			wm.fire(e.editor, "changed");
682 		});
683 		this.validate();
684 	},
685 	keyup: function(e) {
686 		// don't process enter for textareas
687 		if (e.keyCode == dojo.keys.ENTER && e.target.tagName != "TEXTAREA") {
688 			this._defaultButtonHandle = setTimeout(dojo.hitch(this, "_doDefaultButton"), 50);
689 		}
690 	},
691 	_doDefaultButton: function() {
692 		this._defaultButtonHandle = null;
693 		var d = this.defaultButton;
694 		if (d) {
695 			this.forceValidation();
696 			if (!d.disabled)
697 				wm.fire(d, "onclick");
698 		}
699 	},
700 	_cancelDefaultButton: function() {
701 		if (this._defaultButtonHandle) {
702 			clearTimeout(this._defaultButtonHandle);
703 			this._defaultButtonHandle = null;
704 		}
705 	},
706 	//===========================================================================
707 	// Events
708 	//===========================================================================
709 	onBeginInsert: function() {
710 	},
711 	onInsertData: function() {
712 	},
713 	onBeginUpdate: function() {
714 	},
715 	onUpdateData: function() {
716 	},
717 	onBeginDelete: function() {
718 	},
719 	onDeleteData: function() {
720 	},
721 	onCancelEdit: function() {
722 	},
723 	onBeforeOperation: function(inOperation) {
724 	},
725 	onSuccess: function(inData) {
726 	},
727 	onResult: function(inData) {
728 	},
729 	onError: function(inError) {
730 		wm.logging && console.error(inError);
731 		if (this.displayErrors) {
732 			var m = dojo.isString(inError) ? inError : (inError.message ? "Error: " + inError.message : "Unspecified Error");
733 			alert(m);
734 		}
735 	}
736 });