2 * (c) 2005-2011 Richard Cowin (http://openrico.org)
3 * (c) 2005-2011 Matt Brown (http://dowdybrown.com)
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6 * file except in compliance with the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software distributed under the
11 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12 * either express or implied. See the License for the specific language governing permissions
13 * and limitations under the License.
16 if(typeof Rico=='undefined') throw("LiveGridForms requires the Rico JavaScript framework");
19 Rico.TableEdit = function(liveGrid) {
20 this.initialize(liveGrid);
23 Rico.TableEdit.prototype = {
25 * @class Supports editing LiveGrid data.
28 initialize: function(liveGrid) {
29 Rico.log('Rico.TableEdit initialize: '+liveGrid.tableId);
32 maxDisplayLen : 20, // max displayed text field length
33 panelHeight : 200, // size of tabbed panels
35 compact : false, // compact corners
36 RecordName : Rico.getPhraseById("record"),
37 updateURL : window.location.href, // default is that updates post back to the generating page
38 showSaveMsg : 'errors' // disposition of database update responses (full - show full response, errors - show full response for errors and short response otherwise)
40 Rico.extend(this.options, liveGrid.options);
42 this.menu=liveGrid.menu;
43 this.menu.options.dataMenuHandler=function(grid,r,c,onBlankRow) { return self.editMenu(grid,r,c,onBlankRow); };
44 this.menu.ignoreClicks();
45 this.editText=Rico.getPhraseById("editRecord",this.options.RecordName);
46 this.cloneText=Rico.getPhraseById("cloneRecord",this.options.RecordName);
47 this.delText=Rico.getPhraseById("deleteRecord",this.options.RecordName);
48 this.addText=Rico.getPhraseById("addRecord",this.options.RecordName);
49 this.buttonHover=new Rico.HoverSet();
50 this.dateRegExp=/^\s*(\w+)(\W)(\w+)(\W)(\w+)/i;
51 this.createKeyArray();
52 if (typeof(this.options.ConfirmDeleteCol) != 'number')
53 this.options.ConfirmDeleteCol=this.keys.length > 0 ? -2 : -1;
55 this.saveMsg=Rico.$(liveGrid.tableId+'_savemsg');
56 Rico.eventBind(document,"click", Rico.eventHandle(this,'clearSaveMsg'));
57 this.extraMenuItems=[];
58 this.responseHandler=function(xhr) { self.processResponse(xhr); };
59 Rico.log("Rico.TableEdit.initialize complete");
62 createKeyArray: function() {
64 for (var i=0; i<this.grid.columns.length; i++) {
65 if (this.grid.columns[i].format && this.grid.columns[i].format.isKey)
66 this.keys.push({colidx:i});
70 createEditDiv: function() {
75 this.formPopup=this.createWindow();
76 Rico.addClass(this.formPopup.content.parentNode,'ricoLG_editDiv');
77 if (this.options.canEdit || this.options.canAdd) {
79 this.createForm(this.form);
81 var buttonClose=this.createButton(Rico.getPhraseById("close"));
82 Rico.eventBind(buttonClose,"click", Rico.eventHandle(this,'cancelEdit'), false);
83 this.createForm(this.formPopup.contentDiv);
85 this.editDivCreated=true;
87 // create responseDialog
89 this.responseDialog = this.grid.createDiv('editResponse',document.body);
90 this.responseDialog.style.display='none';
92 var buttonOK = document.createElement('button');
93 buttonOK.appendChild(document.createTextNode(Rico.getPhraseById("ok")));
94 Rico.eventBind(buttonOK,"click", Rico.eventHandle(this,'ackResponse'));
95 this.responseDialog.appendChild(buttonOK);
97 this.responseDiv = this.grid.createDiv('editResponseText',this.responseDialog);
99 if (this.panelGroup) {
100 Rico.log("createEditDiv complete, requestCount="+this.requestCount);
101 Rico.runLater(50,this,'initPanelGroup');
105 createWindow: function() {
107 return new Rico.Window('', {closeFunc: function() { self.makeFormInvisible(); }, overflow: this.options.ColGroups ? 'hidden' : 'auto'});
110 initPanelGroup: function() {
112 Rico.log("initPanelGroup: "+this.requestCount);
113 if (this.requestCount>0) return;
114 var wi=parseInt(this.options.panelWidth,10);
116 //this.form.style.width=(wi+10)+'px';
117 if (Rico.isWebKit) this.formPopup.container.style.display='block'; // this causes display to flash briefly
118 this.options.bgColor = Rico.Color.createColorFromBackground(this.form).toString();
120 this.formPopup.container.style.display='none';
121 this.formPanels=new Rico.TabbedPanel(this.panelGroup, this.options);
124 notEmpty: function(v) {
125 return typeof(v)!='undefined';
128 startForm: function() {
129 this.form = document.createElement('form');
131 this.form.onsubmit=function() {return false;};
132 this.form.autocomplete="off"; // seems to fix "Permission denied..." errors in FF
133 this.formPopup.contentDiv.appendChild(this.form);
135 var tab = document.createElement('div');
136 tab.className='ButtonBar';
137 var button=tab.appendChild(this.createButton(Rico.getPhraseById("saveRecord",this.options.RecordName)));
138 Rico.eventBind(button,"click", Rico.eventHandle(this,'TESubmit'), false);
139 button=tab.appendChild(this.createButton(Rico.getPhraseById("cancel")));
140 Rico.eventBind(button,"click", Rico.eventHandle(this,'cancelEdit'), false);
141 this.form.appendChild(tab);
144 this.hiddenFields = document.createElement('div');
145 this.hiddenFields.style.display='none';
146 this.action = this.appendHiddenField(this.grid.actionId,'');
148 for (i=0; i<this.grid.columns.length; i++) {
149 fldSpec=this.grid.columns[i].format;
150 if (fldSpec && fldSpec.FormView && fldSpec.FormView=="hidden")
151 this.appendHiddenField(fldSpec.FieldName,fldSpec.ColData);
153 for (var k=0; k<this.keys.length; k++) {
154 this.keys[k].keyField = this.appendHiddenField('_k'+this.keys[k].colidx,'');
156 this.form.appendChild(this.hiddenFields);
159 createButton: function(buttonLabel) {
160 var button = document.createElement('a');
161 button.href='javascript:void(0)';
162 button.innerHTML=buttonLabel;
163 button.className='RicoButton';
164 if (Rico.theme.button) Rico.addClass(button,Rico.theme.button);
165 this.buttonHover.add(button);
169 createPanel: function(i) {
171 for (var j=0; j<this.grid.columns.length; j++) {
172 var fldSpec=this.grid.columns[j].format;
173 if (!fldSpec) continue;
174 if (!fldSpec.EntryType) continue;
175 if (fldSpec.EntryType=='H') continue;
176 if (fldSpec.FormView && fldSpec.FormView=="hidden") continue;
177 var panelIdx=fldSpec.ColGroupIdx || 0;
183 if (!hasFields) return null;
184 this.panelHdr[i] = document.createElement('li');
185 this.panelHdr[i].innerHTML=this.options.ColGroups[i];
186 this.panelHdrs.appendChild(this.panelHdr[i]);
187 this.panelContent[i] = document.createElement('div');
188 this.panelContents.appendChild(this.panelContent[i]);
189 this.panelActualIdx[i]=this.panelCnt++;
190 return this.createFormTable(this.panelContent[i],'tabContent');
193 createForm: function(parentDiv) {
194 var i,div,fldSpec,panelIdx,tables=[];
197 this.panelContent=[];
198 if (this.options.ColGroups) {
199 this.panelGroup = document.createElement('div');
200 this.panelGroup.className='tabPanelGroup';
201 this.panelHdrs = document.createElement('ul');
202 this.panelGroup.appendChild(this.panelHdrs);
203 this.panelContents = document.createElement('div');
204 this.panelContents.className='tabContentContainer';
205 this.panelGroup.appendChild(this.panelContents);
206 this.panelActualIdx=[];
207 parentDiv.appendChild(this.panelGroup);
208 if (this.grid.direction=='rtl') {
209 for (i=this.options.ColGroups.length-1; i>=0; i--) {
210 tables[i]=this.createPanel(i);
213 for (i=0; i<this.options.ColGroups.length; i++) {
214 tables[i]=this.createPanel(i);
217 parentDiv.appendChild(this.panelGroup);
219 div=document.createElement('div');
220 div.className='noTabContent';
221 tables[0]=this.createFormTable(div);
222 parentDiv.appendChild(div);
224 for (i=0; i<this.grid.columns.length; i++) {
225 fldSpec=this.grid.columns[i].format;
226 if (!fldSpec) continue;
227 panelIdx=fldSpec.ColGroupIdx || 0;
228 if (tables[panelIdx]) this.appendFormField(this.grid.columns[i],tables[panelIdx]);
229 if (typeof fldSpec.pattern=='string') {
230 switch (fldSpec.pattern) {
232 fldSpec.regexp=/^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(([0-9]{1,3})|([a-zA-Z]{2,3})|(aero|coop|info|museum|name))$/;
234 case 'float-unsigned':
235 fldSpec.regexp=/^\d+(\.\d+)?$/;
238 fldSpec.regexp=/^[-+]?\d+(\.\d+)?$/;
241 fldSpec.regexp=/^\d+$/;
244 fldSpec.regexp=/^[-+]?\d+$/;
247 fldSpec.regexp=new RegExp(fldSpec.pattern);
254 createFormTable: function(div) {
255 var tab=document.createElement('table');
257 div.appendChild(tab);
261 appendHiddenField: function(name,value) {
262 var field=Rico.createFormField(this.hiddenFields,'input','hidden',name,name);
267 appendFormField: function(column, table) {
268 var fmt=column.format;
269 if (!fmt.EntryType) return;
270 if (fmt.EntryType=="H") return;
271 if (fmt.FormView) return;
272 Rico.log('appendFormField: '+column.displayName+' - '+fmt.EntryType);
273 var row = fmt.noFormBreak && table.rows.length > 0 ? table.rows[table.rows.length-1] : table.insertRow(-1);
274 var hdr = row.insertCell(-1);
275 column.formLabel=hdr;
276 if (hdr.noWrap) hdr.noWrap=true;
277 var entry = row.insertCell(-1);
278 if (entry.noWrap) entry.noWrap=true;
279 hdr.id='lbl_'+fmt.FieldName;
280 var field, name=fmt.FieldName;
281 switch (fmt.EntryType) {
284 field=Rico.createFormField(entry,'textarea',null,name);
285 field.cols=fmt.TxtAreaCols;
286 field.rows=fmt.TxtAreaRows;
287 field.innerHTML=fmt.ColData;
288 hdr.style.verticalAlign='top';
292 field=Rico.createFormField(entry,'div',null,name);
293 if (fmt.DescriptionField) field.RicoUpdate=fmt.DescriptionField;
294 if (fmt.MultiSelect) Rico.addClass(field, 'MultiSelect');
295 if (fmt.isNullable && !fmt.MultiSelect) this.addSelectNone(field);
296 this.selectValuesRequest(field,column);
299 field=Rico.createFormField(entry,'select',null,name);
300 if (fmt.isNullable) this.addSelectNone(field);
301 Rico.eventBind(field,"change", Rico.eventHandle(this,'checkSelectNew'));
302 this.selectValuesRequest(field,column);
303 field=document.createElement('span');
304 field.className='ricoEditLabel';
305 field.id='labelnew__'+fmt.FieldName;
306 field.innerHTML=' '+Rico.getPhraseById('formNewValue').replace(' ',' ');
307 entry.appendChild(field);
308 name='textnew__'+fmt.FieldName;
309 field=Rico.createFormField(entry,'input','text',name,name);
314 field=Rico.createFormField(entry,'input','text',name,name);
315 this.initField(field,fmt);
317 field=Rico.createFormField(entry,'select',null,name);
318 if (fmt.MultiSelect) field.multiple=true;
319 if (fmt.SelectRows) field.size=parseInt(fmt.SelectRows,10);
320 if (fmt.isNullable && !fmt.MultiSelect) this.addSelectNone(field);
321 if (fmt.DescriptionField) {
322 field.RicoUpdate=fmt.DescriptionField;
323 Rico.eventBind(field,"change", Rico.eventHandle(this,'selectClick'), false);
325 this.selectValuesRequest(field,column);
329 if (!fmt.isNullable) fmt.required=true;
330 if (!fmt.dateFmt) fmt.dateFmt=Rico.dateFmt;
331 if (!fmt.Help) fmt.Help=fmt.dateFmt;
332 if (typeof fmt.min=='string') fmt.min=Rico.setISO8601(fmt.min) || new Date(fmt.min);
333 if (typeof fmt.max=='string') fmt.max=Rico.setISO8601(fmt.max) || new Date(fmt.max);
334 fmt.Length=Math.max(fmt.dateFmt.length,10);
335 if (Rico.inputtypes.date) {
336 // use the WebForms calendar
337 field=Rico.createFormField(entry,'input','date',name,name);
338 field.required=fmt.required;
339 if (fmt.min) field.min=Rico.toISO8601String(fmt.min,3);
340 if (fmt.max) field.max=Rico.toISO8601String(fmt.max,3);
341 field.required=fmt.required;
342 fmt.SelectCtl=null; // no need for Rico calendar control
344 field=Rico.createFormField(entry,'input','text',name,name);
346 this.initField(field,fmt);
349 if (!fmt.isNullable) fmt.required=true;
350 if (!fmt.pattern) fmt.pattern='int-signed';
351 if (Rico.inputtypes.number) {
352 field=Rico.createFormField(entry,'input','number',name,name);
353 field.required=fmt.required;
358 field=Rico.createFormField(entry,'input','text',name,name);
360 if (typeof fmt.min=='string') fmt.min=parseInt(fmt.min,10);
361 if (typeof fmt.max=='string') fmt.max=parseInt(fmt.max,10);
362 this.initField(field,fmt);
365 if (!fmt.isNullable) fmt.required=true;
366 if (!fmt.pattern) fmt.pattern='float-signed';
367 field=Rico.createFormField(entry,'input','text',name,name);
368 this.initField(field,fmt);
369 if (typeof fmt.min=='string') fmt.min=parseFloat(fmt.min);
370 if (typeof fmt.max=='string') fmt.max=parseFloat(fmt.max);
373 field=Rico.createFormField(entry,'input','text',name,name);
374 if (!fmt.isNullable && fmt.EntryType!='T') fmt.required=true;
375 this.initField(field,fmt);
378 if (field && fmt.SelectCtl) {
379 Rico.EditControls.applyTo(column,field,fmt.EntryType=='D');
382 hdr.className='ricoEditLabel';
385 hdrSuffix=" <span class='rico-icon rico-info'></span>";
387 var hdrText=fmt.EntryType.length>1 && fmt.EntryType.charAt(1)=='L' ? column.next.displayName : column.displayName;
388 hdr.innerHTML=hdrText+hdrSuffix;
391 addSelectNone: function(field) {
392 this.addSelectOption(field,this.options.TableSelectNone,Rico.getPhraseById("selectNone"));
395 initField: function(field,fmt) {
397 field.maxLength=fmt.Length;
398 field.size=Math.min(fmt.Length, this.options.maxDisplayLen);
400 field.value=fmt.ColData;
403 selectClick: function(e) {
404 var SelObj=Rico.eventElement(e);
405 if (SelObj.readOnly) {
409 if (SelObj.RicoUpdate) {
410 var opt=SelObj.options[SelObj.selectedIndex];
411 Rico.$(SelObj.RicoUpdate).value=opt.innerHTML;
415 radioClick: function(e) {
416 var ChkBoxObj=Rico.eventElement(e);
417 if (ChkBoxObj.readOnly) {
421 var container=Rico.getParentByTagName(ChkBoxObj,'div');
422 if (container.RicoUpdate) {
423 Rico.$(container.RicoUpdate).value=ChkBoxObj.nextSibling.innerHTML;
427 checkSelectNew: function(e) {
428 this.updateSelectNew(Rico.eventElement(e));
431 updateSelectNew: function(SelObj) {
432 var vis=(SelObj.value==this.options.TableSelectNew) ? "" : "hidden";
433 Rico.$("labelnew__" + SelObj.id).style.visibility=vis;
434 Rico.$("textnew__" + SelObj.id).style.visibility=vis;
437 selectValuesRequest: function(elem,column) {
438 var fldSpec=column.format;
439 if (fldSpec.SelectValues) {
440 var valueList=fldSpec.SelectValues.split(',');
441 for (var i=0; i<valueList.length; i++)
442 this.addSelectOption(elem,valueList[i],valueList[i],i);
445 var options={}, self=this;
446 Rico.extend(options, this.grid.buffer.ajaxOptions);
447 options.parameters = this.grid.buffer.formQueryHashXML(0,-1);
448 options.parameters.edit = column.index;
449 options.onComplete = function(request) { self.selectValuesUpdate(elem,request); };
450 new Rico.ajaxRequest(this.grid.buffer.dataSource, options);
451 Rico.log("selectValuesRequest: "+fldSpec.FieldName);
455 selectValuesUpdate: function(elem,request) {
456 var response = request.responseXML.getElementsByTagName("ajax-response");
457 Rico.log("selectValuesUpdate: "+request.status);
458 if (response == null || response.length != 1) return;
459 response=response[0];
460 var error = response.getElementsByTagName('error');
461 if (error.length > 0) {
462 var errmsg=Rico.getContentAsString(error[0],this.grid.buffer.isEncoded);
463 Rico.log("Data provider returned an error:\n"+errmsg);
464 alert(Rico.getPhraseById("requestError",errmsg));
467 response=response.getElementsByTagName('response')[0];
468 var rowsElement = response.getElementsByTagName('rows')[0];
469 var rows = this.grid.buffer.dom2jstable(rowsElement);
470 Rico.log("selectValuesUpdate: id="+elem.id+' rows='+rows.length);
471 for (var i=0; i<rows.length; i++) {
472 if (rows[i].length>0) {
474 var c1=(rows[i].length>1) ? rows[i][1] : c0;
475 this.addSelectOption(elem,c0,c1,i);
478 if (Rico.$('textnew__'+elem.id))
479 this.addSelectOption(elem,this.options.TableSelectNew,Rico.getPhraseById("selectNewVal"));
481 Rico.runLater(50,this,'initPanelGroup');
484 addSelectOption: function(elem,value,text,idx) {
485 switch (elem.tagName.toLowerCase()) {
487 var opt=Rico.createFormField(elem,'input', Rico.hasClass(elem, 'MultiSelect') ? 'checkbox' : 'radio', elem.id+'_'+idx, elem.id);
489 var lbl=document.createElement('label');
492 elem.appendChild(lbl);
493 Rico.eventBind(opt,"click", Rico.eventHandle(this,'radioClick'), false);
496 Rico.addSelectOption(elem,value,text);
501 clearSaveMsg: function() {
502 if (this.saveMsg) this.saveMsg.innerHTML="";
505 addMenuItem: function(menuText,menuAction,enabled) {
506 this.extraMenuItems.push({menuText:menuText,menuAction:menuAction,enabled:enabled});
509 editMenu: function(grid,r,c,onBlankRow) {
511 if (this.grid.buffer.sessionExpired==true || this.grid.buffer.startPos<0) return false;
513 var elemTitle=Rico.$('pageTitle');
514 var pageTitle=elemTitle ? elemTitle.innerHTML : document.title;
515 this.menu.addMenuHeading(pageTitle);
517 if (onBlankRow==false) {
518 for (var i=0; i<this.extraMenuItems.length; i++) {
519 this.menu.addMenuItem(this.extraMenuItems[i].menuText,this.extraMenuItems[i].menuAction,this.extraMenuItems[i].enabled);
521 this.menu.addMenuItem(this.editText, function() { self.editRecord(); },this.canEdit(r));
522 this.menu.addMenuItem(this.delText, function() { self.deleteRecord(); },this.canDelete(r));
523 if (this.options.canClone) {
524 this.menu.addMenuItem(this.cloneText, function() { self.cloneRecord(); },this.canAdd(r) && this.canEdit(r));
527 this.menu.addMenuItem(this.addText, function() { self.addRecord(); },this.canAdd(r));
531 canAdd: function(r) {
532 return (typeof this.options.canAdd=='function') ? this.options.canAdd(r) : this.options.canAdd;
535 canEdit: function(r) {
536 return (typeof this.options.canEdit=='function') ? this.options.canEdit(r) : this.options.canEdit;
539 canDelete: function(r) {
540 return (typeof this.options.canDelete=='function') ? this.options.canDelete(r) : this.options.canDelete;
543 cancelEdit: function(e) {
545 this.makeFormInvisible();
546 this.grid.highlightEnabled=true;
547 this.menu.cancelmenu();
551 setField: function(fldnum,fldvalue) {
552 var fldSpec=this.grid.columns[fldnum].format;
553 var e=Rico.$(fldSpec.FieldName);
554 var a,i,o,elems,opts,txt;
556 Rico.log('setField: '+fldSpec.FieldName+'='+fldvalue);
557 switch (e.tagName.toUpperCase()) {
559 elems=e.getElementsByTagName('INPUT');
561 if (fldSpec.MultiSelect && fldvalue) {
562 a=fldvalue.split(',');
563 for (var i=0; i<a.length; i++) o[a[i]]=1;
567 for (i=0; i<elems.length; i++)
568 elems[i].checked=o[elems[i].value]==1;
571 if (fldSpec.EntryType=='D') {
572 // remove time data if it exists
573 a=fldvalue.split(/\s|T/);
575 if (this.isTextInput(e)) {
576 var d=fldvalue.toLowerCase() == 'today' ? new Date() : Rico.setISO8601(fldvalue);
577 if (d) fldvalue=Rico.formatDate(d,fldSpec.dateFmt);
584 //alert('setField SELECT: id='+e.id+'\nvalue='+fldvalue+'\nopt cnt='+opts.length)
586 if (fldSpec.MultiSelect && fldvalue) {
587 a=fldvalue.split(',');
588 for (var i=0; i<a.length; i++) o[a[i]]=1;
589 for (i=0; i<opts.length; i++)
590 opts[i].selected=o[opts[i].value]==1;
592 for (i=0; i<opts.length; i++) {
593 if (opts[i].value==fldvalue) {
599 if (fldSpec.EntryType=='N') {
600 txt=Rico.$('textnew__'+e.id);
601 if (!txt) alert('Warning: unable to find id "textnew__'+e.id+'"');
603 if (e.selectedIndex!=i) e.selectedIndex=opts.length-1;
604 this.updateSelectNew(e);
609 if (fldSpec.EntryType=='tinyMCE' && typeof(tinyMCE)!='undefined' && this.initialized) {
610 if (tinyMCE.updateContent) {
611 tinyMCE.updateContent(e.id); // version 2.x
613 tinyMCE.execInstanceCommand(e.id, 'mceSetContent', false, fldvalue); // version 3.x
620 setReadOnly: function(action) {
621 for (var ro,i=0; i<this.grid.columns.length; i++) {
622 var fldSpec=this.grid.columns[i].format;
623 if (!fldSpec) continue;
624 var e=Rico.$(fldSpec.FieldName);
627 case 'ins': ro=!fldSpec.Writeable || fldSpec.ReadOnly || fldSpec.UpdateOnly; break;
628 case 'upd': ro=!fldSpec.Writeable || fldSpec.ReadOnly || fldSpec.InsertOnly; break;
629 default: ro=false; break;
631 switch (e.tagName.toUpperCase()) {
633 var elems=e.getElementsByTagName('INPUT');
634 for (var j=0; j<elems.length; j++) {
635 elems[j].disabled=ro;
639 if (fldSpec.EntryType=='N') {
640 var txt=Rico.$('textnew__'+e.id);
648 if (fldSpec.selectIcon) fldSpec.selectIcon.style.display=ro ? 'none' : '';
654 hideResponse: function(msg) {
655 this.responseDiv.innerHTML=msg;
656 this.responseDialog.style.display='none';
659 showResponse: function() {
660 var offset=Rico.cumulativeOffset(this.grid.outerDiv);
661 offset.top+=Rico.docScrollTop();
662 this.responseDialog.style.top=offset.top+"px";
663 this.responseDialog.style.left=offset.left+"px";
664 this.responseDialog.style.display='';
667 processResponse: function(xhr) {
668 var responseText,success=true;
669 Rico.log('Processing response from form submittal: '+typeof(xhr));
670 this.responseDiv.innerHTML=xhr.responseText;
671 var respNodes=Rico.select('.ricoFormResponse',this.responseDiv);
673 // generate a translated response
674 Rico.log('Found ricoFormResponse');
675 var phraseId=Rico.trim(respNodes[0].className).split(/\s+/)[1];
676 responseText=Rico.getPhraseById(phraseId,this.options.RecordName);
678 // present the response as sent from the server (untranslated)
679 Rico.log('Processing response text');
680 var ch=this.responseDiv.childNodes;
681 for (var i=ch.length-1; i>=0; i--) {
682 if (ch[i].nodeType==1 && ch[i].nodeName!='P' && ch[i].nodeName!='DIV' && ch[i].nodeName!='BR')
683 this.responseDiv.removeChild(ch[i]);
685 responseText=Rico.stripTags(this.responseDiv.innerHTML);
686 success=(responseText.toLowerCase().indexOf('error')==-1);
688 if (success && this.options.showSaveMsg!='full') {
689 this.hideResponse('');
690 this.grid.resetContents();
691 this.grid.buffer.foundRowCount = false;
692 this.grid.buffer.fetch(this.grid.lastRowPos || 0);
693 if (this.saveMsg) this.saveMsg.innerHTML=' '+responseText+' ';
695 this.processCallback(this.options.onSubmitResponse);
696 Rico.log('Processing response completed');
699 processCallback: function(callback) {
700 switch (typeof callback) {
701 case 'string': return eval(callback);
702 case 'function': return callback();
706 // called when ok pressed on error response message
707 ackResponse: function(e) {
708 this.hideResponse('');
709 this.grid.highlightEnabled=true;
712 cloneRecord: function() {
713 this.formPopup.setTitle(this.cloneText);
714 this.displayEditForm("ins");
717 editRecord: function() {
718 this.formPopup.setTitle(this.editText);
719 this.displayEditForm("upd");
722 displayEditForm: function(action) {
723 this.grid.highlightEnabled=false;
724 this.menu.cancelmenu();
725 this.hideResponse(Rico.getPhraseById('saving'));
726 this.grid.outerDiv.style.cursor = 'auto';
727 this.action.value=action;
728 for (var i=0; i<this.grid.columns.length; i++) {
729 var c=this.grid.columns[i];
731 var v=c.getValue(this.rowIdx);
733 if (c.format.selectDesc) {
734 if (c.format.EntryType.length>1 && c.format.EntryType.charAt(1)=='L')
735 v=this.grid.columns[i+1].getValue(this.rowIdx);
737 if (v==='') v=' ';
738 c.format.selectDesc.innerHTML=v;
740 if (c.format.SelectCtl)
741 Rico.EditControls.displayClrImg(c, !c.format.InsertOnly);
744 this.setReadOnly(action);
745 for (var k=0; k<this.keys.length; k++) {
746 this.keys[k].keyField.value = this.grid.buffer.getWindowValue(this.rowIdx,this.keys[k].colidx);
748 this.makeFormVisible(this.rowIdx);
751 addPrepare: function() {
752 this.hideResponse(Rico.getPhraseById('saving'));
754 this.setReadOnly("ins");
755 this.action.value="ins";
756 for (var i=0; i<this.grid.columns.length; i++) {
757 var c=this.grid.columns[i];
759 this.setField(i,c.format.ColData);
760 if (c.format.SelectCtl) {
761 if (c.format.EntryType != 'D') Rico.EditControls.resetValue(c);
762 Rico.EditControls.displayClrImg(c, !c.format.UpdateOnly);
768 addRecord: function() {
769 this.menu.cancelmenu();
770 this.formPopup.setTitle(this.addText);
772 this.makeFormVisible(-1);
773 if (this.formPanels) this.formPanels.select(0);
776 drillDown: function(e,masterColNum,detailColNum) {
777 return this.grid.drillDown.apply(this.grid, arguments);
780 // set filter on a detail grid that is in a master-detail relationship
781 setDetailFilter: function(colNumber,filterValue) {
782 this.grid.setDetailFilter(colNumber,filterValue);
785 makeFormVisible: function(row) {
786 this.formPopup.container.style.display='block';
789 var editWi=this.formPopup.container.offsetWidth;
790 var odOffset=Rico.cumulativeOffset(this.grid.outerDiv);
791 var winWi=Rico.windowWidth();
792 this.formPopup.container.style.left=editWi+odOffset.left > winWi ? (winWi-editWi)+'px' : (odOffset.left+1)+'px';
795 var scrTop=Rico.docScrollTop();
796 var editHt=this.formPopup.container.offsetHeight;
797 var newTop=odOffset.top+this.grid.hdrHt+scrTop;
798 var bottom=Rico.windowHeight()+scrTop;
800 newTop+=(row+1)*this.grid.rowHeight;
801 if (newTop+editHt>bottom) newTop-=(editHt+this.grid.rowHeight);
803 if (newTop+editHt>bottom) newTop=bottom-editHt-2;
806 if (this.processCallback(this.options.formOpen) === false) return;
807 this.formPopup.openPopup(null,Math.max(newTop,scrTop));
808 this.formPopup.container.style.visibility='visible';
809 Rico.EditControls.setZ(Rico.getStyle(this.formPopup.container,'zIndex'));
810 if (this.initialized) return;
813 for (i = 0; i < this.grid.columns.length; i++) {
814 spec=this.grid.columns[i].format;
815 if (!spec || !spec.EntryType || !spec.FieldName) continue;
816 switch (spec.EntryType) {
818 if (typeof tinyMCE!='undefined') tinyMCE.execCommand('mceAddControl', true, spec.FieldName);
822 this.initialized=true;
825 makeFormInvisible: function() {
826 for (var i=0; i<this.grid.columns.length; i++) {
827 if (this.grid.columns[i].format && this.grid.columns[i].format.SelectCtl)
828 Rico.EditControls.close(this.grid.columns[i].format.SelectCtl);
830 this.formPopup.container.style.visibility='hidden';
831 this.formPopup.closePopup();
832 this.processCallback(this.options.formClose);
835 getConfirmDesc: function(rowIdx) {
836 return Rico.stripTags(this.grid.cell(rowIdx,this.options.ConfirmDeleteCol).innerHTML).replace(' ',' '); //.unescapeHTML();
839 deleteRecord: function() {
840 this.menu.cancelmenu();
842 switch(this.options.ConfirmDeleteCol){
844 desc=Rico.getPhraseById("thisRecord",this.options.RecordName);
846 case -2 : // Use key/column header to identify the row
848 for (var k=0; k<this.keys.length; k++) {
849 var i=this.keys[k].colidx;
850 var fmt=this.grid.columns[i].format;
851 if (fmt.EntryType.length>1 && fmt.EntryType.charAt(1)=='L') i++;
852 var value=Rico.trim(Rico.stripTags(this.grid.cell(this.rowIdx,i).innerHTML).replace(/ /g,' '));
853 if (desc) desc+=', ';
854 desc+=this.grid.columns[i].displayName + ' \"' + value + '\"';
858 desc='\"' + Rico.truncate(this.getConfirmDesc(this.rowIdx),50) + '\"';
861 if (!this.options.ConfirmDelete.valueOf || confirm(Rico.getPhraseById("confirmDelete",desc))) {
862 this.hideResponse(Rico.getPhraseById('deleting'));
865 parms[this.grid.actionId]="del";
866 for (var k=0; k<this.keys.length; k++) {
867 var i=this.keys[k].colidx;
868 var value=this.grid.columns[i].getValue(this.rowIdx);
869 parms['_k'+i]=value; // prototype does the encoding automatically
870 //parms['_k'+i]=encodeURIComponent(value);
872 new Rico.ajaxRequest(this.options.updateURL, {parameters:parms,method:'post',onComplete:this.responseHandler});
874 this.menu.cancelmenu();
877 validationMsg: function(elem,colnum,phraseId) {
878 var col=this.grid.columns[colnum];
879 if (this.formPanels) this.formPanels.select(this.panelActualIdx[col.format.ColGroupIdx]);
880 var label=Rico.stripTags(col.formLabel.innerHTML).replace(/ /g,' ');
881 var msg=Rico.getPhraseById(phraseId," \"" + label + "\"");
882 Rico.log(' Validation error: '+msg);
883 if (col.format.Help) msg+="\n\n"+col.format.Help;
885 setTimeout(function() { try { elem.focus(); elem.select(); } catch(e) {}; }, 10);
889 isTextInput: function(elem) {
890 if (!elem) return false;
891 if (elem.tagName.toLowerCase()!='input') return false;
892 if (elem.type.toLowerCase()!='text') return false;
893 if (elem.readOnly) return false;
894 if (!Rico.visible(elem)) return false;
898 parseDate: function(v, dateFmt) {
900 if (!this.dateRegExp.exec(dateFmt)) return NaN;
901 dateParts[RegExp.$1]=0;
902 dateParts[RegExp.$3]=1;
903 dateParts[RegExp.$5]=2;
904 var aDate = v.split(/\D/);
906 var curyr=d.getFullYear();
907 if (aDate.length==2 && dateParts.yyyy==2) aDate.push(curyr);
908 if (aDate.length!=3) return NaN;
909 var dd=parseInt(aDate[dateParts.dd], 10);
910 if (dd==0 || dd>31) return NaN;
911 var mm=parseInt(aDate[dateParts.mm], 10) - 1;
912 if (mm > 11) return NaN;
913 var yy=parseInt(aDate[dateParts.yyyy], 10);
915 // apply a century to 2-digit years
916 yy+=curyr - (curyr % 100);
918 return new Date(yy,mm,dd,0,0,0); // ensure time is midnight
921 TESubmit: function(e) {
922 var i,ro,lbl,spec,elem,n,dateValues=[];
925 Rico.log('Event: TESubmit called to validate input');
927 // check fields that are supposed to be non-blank
929 for (i = 0; i < this.grid.columns.length; i++) {
930 spec=this.grid.columns[i].format;
931 if (!spec || !spec.EntryType || !spec.FieldName) continue;
932 elem=Rico.$(spec.FieldName);
933 if (!this.isTextInput(elem)) continue;
934 switch (this.action.value) {
935 case 'ins': ro=!spec.Writeable || spec.ReadOnly || spec.UpdateOnly; break;
936 case 'upd': ro=!spec.Writeable || spec.ReadOnly || spec.InsertOnly; break;
937 default: ro=false; break;
939 if (ro) continue; // readonly, so don't validate
940 Rico.log(' Validating field #'+i+' EntryType='+spec.EntryType+' ('+spec.FieldName+')');
943 if (elem.value.length == 0) {
945 return this.validationMsg(elem,i,"formPleaseEnter");
951 if (elem.value.length > 0 && spec.regexp && !spec.regexp.test(elem.value))
952 return this.validationMsg(elem,i,"formInvalidFmt");
954 // check min/max and date values
955 switch (spec.EntryType.charAt(0)) {
956 case 'I': n=parseInt(elem.value,10); break;
957 case 'F': n=parseFloat(elem.value); break;
959 n=this.parseDate(elem.value,spec.dateFmt);
960 if (isNaN(n)) return this.validationMsg(elem,i,"formInvalidFmt");
961 dateValues.push({e:elem,v:n});
963 default: n=NaN; break;
965 if (typeof spec.min!='undefined' && !isNaN(n) && n < spec.min)
966 return this.validationMsg(elem,i,"formOutOfRange");
967 if (typeof spec.max!='undefined' && !isNaN(n) && n > spec.max)
968 return this.validationMsg(elem,i,"formOutOfRange");
970 if (this.processCallback(this.options.formSubmit) === false) return false;
972 // update drop-down for any columns with entry type of N
974 for (i = 0; i < this.grid.columns.length; i++) {
975 spec=this.grid.columns[i].format;
976 if (!spec || !spec.EntryType || !spec.FieldName) continue;
977 if (spec.EntryType.charAt(0) != 'N') continue;
978 var SelObj=Rico.$(spec.FieldName);
979 if (!SelObj || SelObj.value!=this.options.TableSelectNew) continue;
980 var newtext=Rico.$("textnew__" + SelObj.id).value;
981 this.addSelectOption(SelObj,newtext,newtext);
984 // set date values to ISO format
985 for (i = 0; i < dateValues.length; i++) {
986 dateValues[i].e.value = Rico.formatDate(dateValues[i].v,'yyyy-mm-dd');
989 if (typeof tinyMCE!='undefined') tinyMCE.triggerSave();
990 this.makeFormInvisible();
992 this.menu.cancelmenu();
996 sendForm: function() {
997 this.setReadOnly("reset"); // reset disabled flag so that all fields are sent to server
999 Rico.log("sendForm: "+this.grid.tableId);
1000 Rico.ajaxSubmit(this.form, this.options.updateURL, {method:'post',onComplete:this.responseHandler});
1006 * @namespace Registers custom popup widgets to fill in a text box (e.g. ricoCalendar and ricoTree)
1008 * Custom widget must implement:
1009 * open() method (make control visible)
1010 * close() method (hide control)
1011 * container property (div element that contains the control)
1012 * id property (uniquely identifies the widget class)
1014 * widget calls returnValue method to return a value to the caller
1016 * this object handles clicks on the control's icon and positions the control appropriately.
1019 Rico.EditControls = {
1024 register: function(widget, imgsrc) {
1025 this.widgetList[widget.id] = {imgsrc:imgsrc, widget:widget, currentEl:''};
1027 widget.returnValue=function(newVal,newDesc) { self.setValue(widget,newVal,newDesc); };
1028 Rico.log("Rico.EditControls.register:"+widget.id);
1032 this.zIndex=Math.max(this.zIndex,z+10);
1035 applyTo: function(column,inputCtl,showInput) {
1036 var wInfo=this.widgetList[column.format.SelectCtl];
1038 Rico.log('Rico.EditControls.applyTo: '+column.displayName+' : '+column.format.SelectCtl);
1039 var newimg, descSpan = document.createElement('span');
1040 if (wInfo.imgsrc.indexOf('.')==-1 && wInfo.imgsrc.indexOf('/')==-1) {
1041 // treat imgsrc as a class name
1042 newimg = document.createElement('span');
1043 newimg.className=wInfo.imgsrc;
1045 // treat imgsrc as an image uri
1046 newimg = document.createElement('img');
1047 newimg.src=wInfo.imgsrc;
1049 newimg.style.verticalAlign='top';
1050 newimg.style.marginLeft='4px';
1051 newimg.style.cursor='pointer';
1052 newimg.id=this.imgId(column.format.FieldName);
1053 Rico.eventBind(newimg,"click", Rico.eventHandle(this,'processClick'));
1054 inputCtl.parentNode.appendChild(descSpan);
1055 inputCtl.parentNode.appendChild(newimg);
1057 descSpan.style.display='none';
1059 inputCtl.style.display='none'; // comment out this line for debugging
1062 if (column.format.isNullable) {
1063 clr=Rico.clearButton(Rico.eventHandle(this,'processClear'));
1064 clr.id=newimg.id+'_clear';
1065 inputCtl.parentNode.appendChild(clr);
1067 this.elemList[newimg.id] = {descSpan:descSpan, inputCtl:inputCtl, widget:wInfo.widget, listObj:wInfo, column:column, clrimg:clr};
1068 column.format.selectIcon=newimg;
1069 column.format.selectDesc=descSpan;
1072 displayClrImg: function(column,bShow) {
1073 var el=this.elemList[this.imgId(column.format.FieldName)];
1074 //alert(column.format.FieldName+': '+bShow+' '+el.clrimg.id);
1075 if (el && el.clrimg) el.clrimg.style.display=bShow ? 'inline-block' : 'none';
1078 processClear: function(e) {
1079 var elem=Rico.eventElement(e);
1080 var el=this.elemList[elem.id.slice(0,-6)];
1082 el.inputCtl.value='';
1083 el.descSpan.innerHTML=el.column._format('');
1086 processClick: function(e) {
1087 var elem=Rico.eventElement(e);
1088 var el=this.elemList[elem.id];
1090 if (el.listObj.currentEl==elem.id && el.widget.container.style.display!='none') {
1092 el.listObj.currentEl='';
1094 el.listObj.currentEl=elem.id;
1095 Rico.log('Rico.EditControls.processClick: '+el.widget.id+' : '+el.inputCtl.value);
1096 el.widget.container.style.zIndex=this.zIndex;
1097 el.widget.open(el.inputCtl.value,el.column); // this may change the size of the widget
1098 Rico.positionCtlOverIcon(el.widget.container,elem);
1102 imgId: function(fieldname) {
1103 return 'icon_'+fieldname;
1106 resetValue: function(column) {
1107 var el=this.elemList[this.imgId(column.format.FieldName)];
1109 el.inputCtl.value=column.format.ColData;
1110 var v=column._format(column.format.ColData);
1111 if (v==='') v=' ';
1112 el.descSpan.innerHTML=v;
1115 setValue: function(widget,newVal,newDesc) {
1116 var wInfo=this.widgetList[widget.id];
1117 if (!wInfo) return null;
1118 var id=wInfo.currentEl;
1119 if (!id) return null;
1120 var el=this.elemList[id];
1121 if (!el) return null;
1122 el.inputCtl.value=newVal;
1123 if (!newDesc) newDesc=el.column._format(newVal);
1124 el.descSpan.innerHTML=newDesc;
1125 if (el.column.format.DescriptionField)
1126 Rico.$(el.column.format.DescriptionField).value = newDesc;
1127 //alert(widget.id+':'+id+':'+el.inputCtl.id+':'+el.inputCtl.value+':'+newDesc);
1130 close: function(id) {
1131 var wInfo=this.widgetList[id];
1133 if (wInfo.widget.container.style.display!='none')
1134 wInfo.widget.close();