otsdaq_utilities  v2_05_02_indev
ConfigurationAPI.js
1 //=====================================================================================
2 //
3 // Created April, 2017
4 // by Ryan Rivera ((rrivera at fnal.gov))
5 //
6 // ConfigurationAPI.js
7 //
8 // Requirements:
9 // 1. paste the following:
10 //
11 // <script type="text/JavaScript" src="/WebPath/js/Globals.js"></script>
12 // <script type="text/JavaScript" src="/WebPath/js/Debug.js"></script>
13 // <script type="text/JavaScript" src="/WebPath/js/DesktopContent.js"></script>
14 // <script type="text/JavaScript" src="/WebPath/js/js_lib/ConfiguraitonAPI.js"></script>
15 // <link rel="stylesheet" type="text/css" href="/WebPath/css/ConfigurationAPI.css">
16 //
17 // ...anywhere inside the <head></head> tag of a window content html page
18 // 2. for proper functionality certain handlers are used:
19 // cannot overwrite handlers for window: onfocus, onscroll, onblur, onmousemove
20 // (if you must overwrite, try to call the DesktopContent handlers from your handlers)
21 //
22 // Recommendations:
23 // 1. use Debug to output status and errors, e.g.:
24 // Debug.log("this is my status",Debug.LOW_PRIORITY); //LOW_PRIORITY, MED_PRIORITY, INFO_PRIORITY, WARN_PRIORITY, HIGH_PRIORITY
25 // 2. call window.focus() to bring your window to the front of the Desktop
26 //
27 // The code of Requirement #1 should be inserted in the header of each page that will be
28 // the content of a window in the ots desktop.
29 //
30 // This code handles bringing the window to the front when the content
31 // is clicked or scrolled.
32 //
33 // Example usage: /WebPath/html/ConfigurationGUI.html
34 // /WebPath/html/ConfigurationGUI_subset.html
35 //
36 //=====================================================================================
37 
38 var ConfigurationAPI = ConfigurationAPI || {}; //define ConfigurationAPI namespace
39 
40 if (typeof Debug == 'undefined')
41  alert('ERROR: Debug is undefined! Must include Debug.js before ConfigurationAPI.js');
42 if (typeof Globals == 'undefined')
43  alert('ERROR: Globals is undefined! Must include Globals.js before ConfigurationAPI.js');
44 if (typeof DesktopContent == 'undefined' &&
45  typeof Desktop == 'undefined')
46  alert('ERROR: DesktopContent is undefined! Must include DesktopContent.js before ConfigurationAPI.js');
47 
48 
49 //"public" function list:
50 // ConfigurationAPI.getDateString(date)
51 // ConfigurationAPI.getActiveGroups(responseHandler)
52 // ConfigurationAPI.getAliasesAndGroups(responseHandler,optionForNoAliases,optionForNoGroups)
53 // ConfigurationAPI.getSubsetRecords(subsetBasePath,filterList,responseHandler,modifiedTables)
54 // ConfigurationAPI.getFieldsOfRecords(subsetBasePath,recordArr,fieldList,maxDepth,responseHandler,modifiedTables)
55 // ConfigurationAPI.getFieldValuesForRecords(subsetBasePath,recordArr,fieldObjArr,responseHandler,modifiedTables,silenceErrors)
56 // ConfigurationAPI.getUniqueFieldValuesForRecords(subsetBasePath,recordArr,fieldList,responseHandler,modifiedTables)
57 // ConfigurationAPI.setFieldValuesForRecords(subsetBasePath,recordArr,fieldObjArr,valueArr,responseHandler,modifiedTablesIn,silenceErrors) )
58 // ConfigurationAPI.popUpSaveModifiedTablesForm(modifiedTables,responseHandler)
59 // ConfigurationAPI.saveModifiedTables(modifiedTables,responseHandler,doNotIgnoreWarnings,doNotSaveAffectedGroups,doNotActivateAffectedGroups,doNotSaveAliases,doNotIgnoreGroupActivationWarnings,doNotKillPopUpEl,tablesToAdd)
60 // ConfigurationAPI.bitMapDialog(bitMapParams,initBitMapValue,okHandler,cancelHandler)
61 // ConfigurationAPI.createEditableFieldElement(fieldObj,fieldIndex,depthIndex /*optional*/)
62 // ConfigurationAPI.getEditableFieldValue(fieldObj,fieldIndex,depthIndex /*optional*/)
63 // ConfigurationAPI.setEditableFieldValue(fieldObj,value,fieldIndex,depthIndex /*optional*/)
64 // ConfigurationAPI.getSelectedEditableFieldIndex()
65 // ConfigurationAPI.addSubsetRecords(subsetBasePath,recordArr,responseHandler,modifiedTablesIn,silenceErrors)
66 // ConfigurationAPI.deleteSubsetRecords(subsetBasePath,recordArr,responseHandler,modifiedTablesIn,silenceErrors)
67 // ConfigurationAPI.renameSubsetRecords(subsetBasePath,recordArr,newRecordArr,responseHandler,modifiedTablesIn,silenceErrors)
68 // ConfigurationAPI.copySubsetRecords(subsetBasePath,recordArr,numberOfCopies,responseHandler,modifiedTablesIn,silenceErrors)
69 // ConfigurationAPI.addTableToConfigurationGroup(tableName)
70 
71 //"public" helpers:
72 // ConfigurationAPI.setCaretPosition(elem, caretPos, endPos)
73 // ConfigurationAPI.removeAllPopUps()
74 // ConfigurationAPI.setPopUpPosition(el,w,h,padding,border,margin,doNotResize,offsetUp)
75 // ConfigurationAPI.addClass(elem,class)
76 // ConfigurationAPI.removeClass(elem,class)
77 // ConfigurationAPI.hasClass(elem,class)
78 // ConfigurationAPI.extractActiveGroups(req)
79 // ConfigurationAPI.incrementName(name)
80 // ConfigurationAPI.createNewRecordName(startingName,existingArr)
81 
82 
83 //"public" members:
84 ConfigurationAPI._activeGroups = {}; //to fill, call ConfigurationAPI.getActiveGroups() or ConfigurationAPI.extractActiveGroups()
85 ConfigurationAPI._activeTables = {}; //to fill, call ConfigurationAPI.getFieldsOfRecords() among others
86 
87 //"public" constants:
88 ConfigurationAPI._DEFAULT_COMMENT = "No comment.";
89 ConfigurationAPI._POP_UP_DIALOG_ID = "ConfigurationAPI-popUpDialog";
90 
91 //"private" function list:
92 // ConfigurationAPI.handleGroupCommentToggle(groupName,setHideVal)
93 // ConfigurationAPI.handlePopUpHeightToggle(h,gh)
94 // ConfigurationAPI.handlePopUpAliasEditToggle(i)
95 // ConfigurationAPI.activateGroup(groupName, groupKey, ignoreWarnings, doneHandler)
96 // ConfigurationAPI.setGroupAliasInActiveBackbone(groupAlias,groupName,groupKey,newBackboneNameAdd,doneHandler,doReturnParams)
97 // ConfigurationAPI.newWizBackboneMemberHandler(req,params)
98 // ConfigurationAPI.saveGroupAndActivate(groupName,tableMap,doneHandler,doReturnParams)
99 // ConfigurationAPI.getOnePixelPngData(rgba)
100 // ConfigurationAPI.getGroupTypeMemberNames(groupType,responseHandler)
101 // ConfigurationAPI.getTree(treeBasePath,depth,modifiedTables,responseHandler,responseHandlerParam)
102 // ConfigurationAPI.getTreeChildren(tree,pathToChildren)
103 // ConfigurationAPI.getTreeRecordLinks(node)
104 // ConfigurationAPI.getTreeRecordName(node)
105 // ConfigurationAPI.getTreeLinkChildren(link)
106 // ConfigurationAPI.getTreeLinkTable(link)
107 
108 //
109 // for Editable Fields
110 // ConfigurationAPI.handleEditableFieldClick(depth,uid,editClick,type)
111 // ConfigurationAPI.handleEditableFieldHover(depth,uid,event)
112 // ConfigurationAPI.handleEditableFieldBodyMouseMove(event)
113 // ConfigurationAPI.handleEditableFieldEditOK()
114 // ConfigurationAPI.handleEditableFieldEditCancel()
115 // ConfigurationAPI.handleEditableFieldKeyDown(event,keyEl)
116 // ConfigurationAPI.fillEditableFieldElement(fieldEl,uid,depth,nodeName,value,valueType,choices,path,isGroupLink,childLinkIndex,linkId)
117 
118 //"private" constants:
119 ConfigurationAPI._VERSION_ALIAS_PREPEND = "ALIAS:";
120 ConfigurationAPI._SCRATCH_VERSION = 2147483647;
121 ConfigurationAPI._SCRATCH_ALIAS = "Scratch";
122 
123 ConfigurationAPI._OK_CANCEL_DIALOG_STR = "";
124 ConfigurationAPI._OK_CANCEL_DIALOG_STR += "<div title='' style='padding:5px;background-color:#eeeeee;border:1px solid #555555;position:relative;z-index:2000;" + //node the expander nodes in tree-view are z-index:1000
125  "width:95px;height:20px;margin:0 -122px -64px 10px; font-size: 16px; white-space:nowrap; text-align:center;'>";
126 ConfigurationAPI._OK_CANCEL_DIALOG_STR += "<a class='popUpOkCancel' onclick='javascript:ConfigurationAPI.handleEditableFieldEditOK(); event.stopPropagation();' onmouseup='event.stopPropagation();' title='Accept Changes' style='color:green'>" +
127  "<b style='color:green;font-size: 16px;'>OK</b></a> | " +
128  "<a class='popUpOkCancel' onclick='javascript:ConfigurationAPI.handleEditableFieldEditCancel(); event.stopPropagation();' onmouseup='event.stopPropagation();' title='Discard Changes' style='color:red'>" +
129  "<b style='color:red;font-size: 16px;'>Cancel</b></a>";
130 ConfigurationAPI._OK_CANCEL_DIALOG_STR += "</div>";
131 
132 //=====================================================================================
133 //getActiveGroups ~~
134 // get currently active groups
135 //
136 // when complete, the responseHandler is called with an object parameter.
137 // on failure, the object will be empty.
138 // on success, the object of Active Groups
139 // Group := {}
140 // obj.<groupType> = {}
141 // obj.<groupType>.groupName
142 // obj.<groupType>.groupKey
143 //
144 // <groupType> = Context, Backbone, Iterate, or Configuration
145 //
146 ConfigurationAPI.getActiveGroups = function(responseHandler)
147 {
148  //get active configuration group
149  DesktopContent.XMLHttpRequest("Request?RequestType=getActiveTableGroups",
150  "", function(req)
151  {
152  responseHandler(ConfigurationAPI.extractActiveGroups(req));
153  },
154  0,0,true //reqParam, progressHandler, callHandlerOnErr
155  ); //end of getActiveTableGroups handler
156 }
157 ConfigurationAPI.extractActiveGroups = function(req)
158 {
159  //can call this at almost all API handlers
160  try
161  {
162  var activeConfigGroups = [
163  DesktopContent.getXMLValue(req,"Context-ActiveGroupName"),
164  DesktopContent.getXMLValue(req,"Context-ActiveGroupKey"),
165  DesktopContent.getXMLValue(req,"Backbone-ActiveGroupName"),
166  DesktopContent.getXMLValue(req,"Backbone-ActiveGroupKey"),
167  DesktopContent.getXMLValue(req,"Iterate-ActiveGroupName"),
168  DesktopContent.getXMLValue(req,"Iterate-ActiveGroupKey"),
169  DesktopContent.getXMLValue(req,"Configuration-ActiveGroupName"),
170  DesktopContent.getXMLValue(req,"Configuration-ActiveGroupKey")];
171  var i=0;
172  var retObj = {};
173  retObj.Context = {};
174  retObj.Context.groupName = activeConfigGroups[i++];
175  retObj.Context.groupKey = activeConfigGroups[i++];
176  retObj.Backbone = {};
177  retObj.Backbone.groupName = activeConfigGroups[i++];
178  retObj.Backbone.groupKey = activeConfigGroups[i++];
179  retObj.Iterate = {};
180  retObj.Iterate.groupName = activeConfigGroups[i++];
181  retObj.Iterate.groupKey = activeConfigGroups[i++];
182  retObj.Configuration = {};
183  retObj.Configuration.groupName = activeConfigGroups[i++];
184  retObj.Configuration.groupKey = activeConfigGroups[i++];
185  }
186  catch(e)
187  {
188  Debug.log("Error extracting active groups: " + e);
189  return undefined;
190  }
191 
192  ConfigurationAPI._activeGroups = {};
193  ConfigurationAPI._activeGroups = retObj;
194 
195  return retObj;
196 }
197 
198 
199 //=====================================================================================
200 //getAliasesAndGroups ~~
201 // get system aliases, existing groups (w/ currently active groups)
202 //
203 // when complete, the responseHandler is called with an object parameter.
204 // on failure, the object will be empty.
205 // on success, the object of Active Groups
206 // Group := {}
207 // obj.activeGroups = {}
208 // obj.activeGroups.<groupType> = {}
209 // obj.activeGroups.<groupType>.groupName
210 // obj.activeGroups.<groupType>.groupKey
211 // obj.groups = {}
212 // obj.groups.<groupType> = {}
213 // obj.groups.<groupType>.<groupName> = {}
214 // obj.groups.<groupType>.<groupName>.keys = [latestKey, key, ...]
215 // obj.groups.<groupType>.<groupName>.groupComment
216 // obj.aliases = {}
217 // obj.aliases.<groupType> = [ aliasObj,... ]
218 // aliasObj = {}
219 // aliasObj.alias
220 // aliasObj.name
221 // aliasObj.key
222 // aliasObj.groupComment
223 // aliasObj.groupType
224 // aliasObj.aliasComment
225 //
226 // <groupType> = Context, Backbone, Iterate, or Configuration
227 //
228 ConfigurationAPI.getAliasesAndGroups = function(responseHandler,optionForNoAliases,
229  optionForNoGroups)
230 {
231  var retObj = {};
232  var reqCount = 0;
233 
234  //get aliases
235  if(!optionForNoAliases)
236  DesktopContent.XMLHttpRequest("Request?RequestType=getGroupAliases" +
237  "", //end get data
238  "", //end post data
239  function(req)
240  {
241 
242  Debug.log("getGroupAliases handler");
243 
244  var groupAliases = req.responseXML.getElementsByTagName("GroupAlias");
245  var groupNames = req.responseXML.getElementsByTagName("GroupName");
246  var groupKeys = req.responseXML.getElementsByTagName("GroupKey");
247  var groupComments = req.responseXML.getElementsByTagName("GroupComment");
248  var groupTypes = req.responseXML.getElementsByTagName("GroupType");
249  var aliasComments = req.responseXML.getElementsByTagName("AliasComment");
250 
251  retObj.aliases = {};
252  var type;
253 
254  for(var i=0;i<groupAliases.length;++i)
255  {
256  type = groupTypes[i].getAttribute('value');
257 
258  if(type == "") continue;
259 
260  if(!retObj.aliases[type])
261  retObj.aliases[type] = []; //create array
262 
263  retObj.aliases[type].push({
264  "alias" : groupAliases[i].getAttribute('value'),
265  "name" : groupNames[i].getAttribute('value'),
266  "key" : groupKeys[i].getAttribute('value'),
267  "groupComment" : groupComments[i].getAttribute('value'),
268  "groupComment" : groupTypes[i].getAttribute('value'),
269  "aliasComment" : aliasComments[i].getAttribute('value')
270  });
271  }
272 
273  ++reqCount;
274 
275  if(reqCount == 2 ||
276  (reqCount == 1 && optionForNoGroups))
277  {
278  //done!
279  console.log("getAliasesAndGroups retObj ",retObj);
280  responseHandler(retObj);
281  }
282 
283  }, //end getGroupAliases handler
284  0, //handler param
285  0,true, //progressHandler, callHandlerOnErr
286  ); //end get aliases
287 
288 
289  //get aliases
290  if(!optionForNoGroups)
291  DesktopContent.XMLHttpRequest("Request?RequestType=getTableGroups"
292  +"&doNotReturnMembers=1", //end get data
293  "", //end post data
294  function(req)
295  {
296  Debug.log("getTableGroups handler");
297 
298  retObj.activeGroups = {}; //clear
299  retObj.activeGroups = ConfigurationAPI.extractActiveGroups(req);
300 
301  var groupNames = req.responseXML.getElementsByTagName("TableGroupName");
302  var groupKeys = req.responseXML.getElementsByTagName("TableGroupKey");
303  var groupTypes = req.responseXML.getElementsByTagName("TableGroupType");
304  var groupComments = req.responseXML.getElementsByTagName("TableGroupComment");
305 
306  retObj.groups = {}; //clear
307 
308  var type, name;
309  for(var i=0;i<groupNames.length;++i)
310  {
311  type = groupTypes[i].getAttribute('value');
312 
313  if(type == "") continue;
314 
315  // obj.groups = {}
316  // obj.groups.<groupType> = {}
317  // obj.groups.<groupType>.<groupName> = {}
318  // obj.groups.<groupType>.<groupName>.keys = [latestKey, key, ...]
319  // obj.groups.<groupType>.<groupName>.groupComment
320 
321  if(!retObj.groups[type])
322  retObj.groups[type] = {}; //create first of type
323 
324  name = groupNames[i].getAttribute('value');
325  if(!retObj.groups[type][name])
326  {
327  retObj.groups[type][name] = {}; //create first of group name
328  //set group comment the first time
329  retObj.groups[type][name].groupComment = groupComments[i].getAttribute('value');
330  retObj.groups[type][name].keys = []; //create empty keys array
331  }
332  //add key
333  retObj.groups[type][name].keys.push(groupKeys[i].getAttribute('value'));
334  }
335 
336  ++reqCount;
337 
338  if(reqCount == 2 ||
339  (reqCount == 1 && optionForNoAliases))
340  {
341  //done!
342  console.log("getAliasesAndGroups retObj ",retObj);
343  responseHandler(retObj);
344  }
345  }, //handler
346  0, //handler param
347  0,true); //progressHandler, callHandlerOnErr
348 
349 } // end getAliasesAndGroups
350 
351 
352 //=====================================================================================
353 //getSubsetRecords ~~
354 // takes as input a base path where the desired records are,
355 // and a filter list.
356 //
357 // <filterList>
358 // filterList := relative-to-record-path=value(,value,...);path=value... filtering
359 // records with relative path not meeting all filter criteria
360 // - can accept multiple values per field (values separated by commas) (i.e. OR)
361 // - fields/value pairs separated by ; for AND
362 // - Note: limitation here is there is no OR among fields/value pairs (in future, could separate field/value pairs by : for OR)
363 // e.g. "LinkToFETypeConfiguration=NIMPlus,TemplateUDP;FEInterfacePluginName=NIMPlusPlugin"
364 //
365 // <modifiedTables> is an array of Table objects (as returned from
366 // ConfigurationAPI.setFieldValuesForRecords)
367 //
368 // when complete, the responseHandler is called with an array parameter.
369 // on failure, the array will be empty.
370 // on success, the array will be an array of records (their UIDs)
371 // from the subset that match the filter list
372 //
373 ConfigurationAPI.getSubsetRecords = function(subsetBasePath,
374  filterList,responseHandler,modifiedTables)
375 {
376  var modifiedTablesListStr = "";
377  for(var i=0;modifiedTables && i<modifiedTables.length;++i)
378  {
379  if(i) modifiedTablesListStr += ",";
380  modifiedTablesListStr += modifiedTables[i].tableName + "," +
381  modifiedTables[i].tableVersion;
382  }
383  if(filterList === undefined) filterList = "";
384 
385  DesktopContent.XMLHttpRequest("Request?RequestType=getTreeView" +
386  "&tableGroup=" +
387  "&tableGroupKey=-1" +
388  "&hideStatusFalse=0" +
389  "&depth=1", //end get data
390  "startPath=/" + subsetBasePath +
391  "&filterList=" + filterList +
392  "&modifiedTables=" + modifiedTablesListStr, //end post data
393  function(req)
394  {
395  ConfigurationAPI.extractActiveGroups(req);
396 
397  var records = [];
398  var err = DesktopContent.getXMLValue(req,"Error");
399  if(err)
400  {
401  Debug.log(err,Debug.HIGH_PRIORITY);
402  if(responseHandler) responseHandler(records);
403  return;
404  }
405 
406  //console.log(req);
407 
408  var tree = DesktopContent.getXMLNode(req,"tree");
409  var nodes = tree.children;
410  for(var i=0;i<nodes.length;++i)
411  records.push(nodes[i].getAttribute("value"));
412  Debug.log("Records: " + records);
413  if(responseHandler) responseHandler(records);
414 
415  }, //handler
416  0, //handler param
417  0,true); //progressHandler, callHandlerOnErr
418 }
419 
420 //=====================================================================================
421 //getTree ~~
422 // returns the currently active xml tree object specified by treeBasePath and depth
423 // considering the modifiedTables.
424 //
425 // on failure, calls response handler with undefined parameter
426 //
427 // responseHandler is called with extra responseHandlerParam (to indicate source, e.g.)
428 ConfigurationAPI.getTree = function(treeBasePath,depth,modifiedTables,
429  responseHandler,responseHandlerParam)
430 {
431  var modifiedTablesListStr = "";
432  for(var i=0;modifiedTables && i<modifiedTables.length;++i)
433  {
434  if(i) modifiedTablesListStr += ",";
435  modifiedTablesListStr += modifiedTables[i].tableName + "," +
436  modifiedTables[i].tableVersion;
437  }
438 
439  treeBasePath = treeBasePath.trim();
440  if(treeBasePath == "/") treeBasePath = ""; //server does not like // for root
441 
442  DesktopContent.XMLHttpRequest("Request?RequestType=getTreeView" +
443  "&tableGroup=" +
444  "&tableGroupKey=-1" +
445  "&hideStatusFalse=0" +
446  "&depth=" + depth, //end get data
447  "startPath=/" + treeBasePath +
448  "&filterList=" + "" +
449  "&modifiedTables=" + modifiedTablesListStr, //end post data
450  function(req)
451  {
452  var err = DesktopContent.getXMLValue(req,"Error");
453  if(err)
454  {
455  Debug.log(err,Debug.HIGH_PRIORITY);
456  if(responseHandler) responseHandler(undefined,responseHandlerParam);
457  return;
458  }
459 
460  //console.log(req);
461 
462  // var nodes = tree.children;
463  // for(var i=0;i<nodes.length;++i)
464  // if(nodeChildren[0].nodeName != "value")
465  // records.push(nodes[i].getAttribute("value"));
466  // else if(nodeChildren[j].nodeName == "node")
467  // child.push(nodes[i].getAttribute("value"));
468  //
469  // Debug.log("Records: " + records);
470  if(responseHandler) responseHandler(
471  DesktopContent.getXMLNode(req,"tree"),
472  responseHandlerParam);
473 
474  }, //handler
475  0, //handler param
476  0,true); //progressHandler, callHandlerOnErr
477 } // end getTree()
478 
479 
480 //=====================================================================================
481 //getTreeChildren ~~
482 // returns an array of children nodes at pathToChildren starting at root of tree (xml object).
483 //
484 ConfigurationAPI.getTreeChildren = function(tree,pathToChildren)
485 {
486  var pathArr = pathToChildren?pathToChildren.split('/'):"";
487  var children;
488  var found;
489 
490  children = tree.children;
491 
492  //look through path elements and for all nodes
493  for(var i=0;i<pathArr.length;++i)
494  {
495  if(pathArr[i].trim().length == 0) continue; //skip empty path segments
496 
497  Debug.log(i + ": " + pathArr[i]);
498 
499  found = false;
500  for(var j=0;j<children.length;++j)
501  if(children[j].getAttribute("value") == pathArr[i])
502  {
503  found = true;
504  //new tree
505  children = children[j].children;
506  Debug.log("found " + pathArr[i]);
507  break;
508  }
509 
510  if(!found)
511  {
512  Debug.log("Invalid path '" + pathToChildren + "' through tree! How did you get here? Notify admins.", Debug.HIGH_PRIORITY);
513  return undefined;
514  }
515  }
516 
517  //result is all children, but we just want nodes
518 
519  var retArr = [];
520  for(var i=0;i<children.length;++i)
521  if(children[i].nodeName == "node")
522  retArr.push(children[i]);
523 
524  return retArr;
525 
526 } //end getTreeChildren
527 
528 //=====================================================================================
529 //getTreeRecordLinks ~~
530 // returns an array of links within a record node
531 //
532 ConfigurationAPI.getTreeRecordLinks = function(node)
533 {
534  var children = node.children;
535  var retArr = [];
536  var subchildren;
537 
538  //for each child, check if link
539  for(var i=0;i<children.length;++i)
540  {
541  if(children[i].nodeName != "node") continue;
542 
543  subchildren = children[i].children;
544 
545  for(var j=0;j<subchildren.length;++j)
546  {
547  if(subchildren[j].nodeName == "LinkTableName")
548  {
549  retArr.push(children[i]);
550  break;
551  }
552  }
553  }
554 
555  return retArr;
556 } //end getTreeRecordLinks
557 
558 
559 //=====================================================================================
560 //getTreeRecordName ~~
561 // returns name of a record node
562 //
563 ConfigurationAPI.getTreeRecordName = function(node)
564 {
565  //if is UID link then give UID as name
566  // assume its in first two children
567  var children = node.children;
568  if(children.length > 2)
569  {
570  if(children[0].nodeName == "valueType" &&
571  children[0].getAttribute("value") == "Disconnected")
572  throw("Disconnected link!");
573 
574  if(children[0].nodeName == "UID")
575  return children[0].getAttribute("value");
576 
577  if(children[1].nodeName == "UID")
578  return children[0].getAttribute("value");
579  }
580 
581  return node.getAttribute("value");
582 } //end getTreeRecordName
583 
584 //=====================================================================================
585 //getTreeLinkChildren ~~
586 // returns an array of child nodes connected through the link
587 //
588 ConfigurationAPI.getTreeLinkChildren = function(link)
589 {
590  var children = link.children;
591  var retArr = [];
592 
593  for(var i=0;i<children.length;++i)
594  {
595  if(children[i].nodeName == "UID")
596  {
597  retArr.push(link);
598  break; //done since UID link
599  }
600  else if(children[i].nodeName == "node")
601  retArr.push(children[i]);
602  }
603 
604  return retArr;
605 } //end getTreeLinkChildren
606 
607 //=====================================================================================
608 //getTreeLinkTable ~~
609 // returns the name of the table connected through the link
610 //
611 ConfigurationAPI.getTreeLinkTable = function(link)
612 {
613  var children = link.children;
614  for(var i=0;i<children.length;++i)
615  if(children[i].nodeName == "LinkTableName")
616  return children[i].getAttribute("value");
617  throw("Table name not found!");
618 } //end getTreeLinkTable
619 
620 //=====================================================================================
621 //getFieldsOfRecords ~~
622 // takes as input a base path where the records are,
623 // and an array of records.
624 // <recordArr> is an array or record UIDs (as returned from
625 // ConfigurationAPI.getSubsetRecords)
626 // <fieldList> is a CSV list of tree paths relative to <subsetBasePath>
627 // to the allowed fields. If empty, then all available fields are allowed.
628 // e.g. "LinkToFETypeConfiguration,FEInterfacePluginName"
629 // := CSV of relative-to-record-path to filter common fields
630 // (accept or reject [use ! as first character to reject])
631 // [use leading* to ignore relative path - note that only leading and trailing wildcards work]
632 //
633 // <modifiedTables> is an array of Table objects (as returned from
634 // ConfigurationAPI.setFieldValuesForRecords)
635 //
636 // maxDepth is used to force an end to search for common fields
637 //
638 // when complete, the responseHandler is called with an array parameter.
639 // on failure, the array will be empty.
640 // on success, the array will be an array of Field objects
641 // Field := {}
642 // obj.fieldTableName
643 // obj.fieldUID
644 // obj.fieldColumnName
645 // obj.fieldRelativePath
646 // obj.fieldColumnType
647 // obj.fieldColumnDataType
648 // obj.fieldColumnDataChoicesArr[]
649 // obj.fieldColumnDefaultValue
650 //
651 //
652 ConfigurationAPI.getFieldsOfRecords = function(subsetBasePath,recordArr,fieldList,
653  maxDepth,responseHandler,modifiedTables)
654 {
655  var modifiedTablesListStr = "";
656  for(var i=0;modifiedTables && i<modifiedTables.length;++i)
657  {
658  if(i) modifiedTablesListStr += ",";
659  modifiedTablesListStr += modifiedTables[i].tableName + "," +
660  modifiedTables[i].tableVersion;
661  }
662 
663  var recordListStr = "";
664  if(Array.isArray(recordArr))
665  for(var i=0;i<recordArr.length;++i)
666  {
667  if(i) recordListStr += ",";
668  recordListStr += encodeURIComponent(recordArr[i]);
669  }
670  else //handle single record case
671  recordListStr = encodeURIComponent(recordArr);
672 
673  subsetBasePath = subsetBasePath.trim();
674  if(subsetBasePath == "/") subsetBasePath = "";
675 
676  DesktopContent.XMLHttpRequest("Request?RequestType=getTreeNodeCommonFields" +
677  "&tableGroup=" +
678  "&tableGroupKey=-1" +
679  "&depth=" + (maxDepth|0), //end get data
680  "startPath=/" + subsetBasePath +
681  "&recordList=" + recordListStr +
682  "&fieldList=" + fieldList +
683  "&modifiedTables=" + modifiedTablesListStr, //end post data
684  function(req)
685  {
686  var recFields = [];
687  var err = DesktopContent.getXMLValue(req,"Error");
688  if(err)
689  {
690  Debug.log(err,Debug.HIGH_PRIORITY);
691  if(responseHandler) responseHandler(recFields);
692  return;
693  }
694 
695  //fill active tables
696  {
697  var tableNames = req.responseXML.getElementsByTagName("ActiveTableName");
698  var tableVersions = req.responseXML.getElementsByTagName("ActiveTableVersion");
699  ConfigurationAPI._activeTables = {};
700  for(var i=0;i<tableNames.length;++i)
701  {
702  ConfigurationAPI._activeTables[DesktopContent.getXMLValue(tableNames[i])] =
703  DesktopContent.getXMLValue(tableVersions[i]);
704  }
705  Debug.logv("ConfigurationAPI._activeTables =",ConfigurationAPI._activeTables);
706  } //end fill active tables
707 
708 
709  var fields = DesktopContent.getXMLNode(req,"fields");
710 
711  var FieldTableNames = fields.getElementsByTagName("FieldTableName");
712  var FieldColumnNames = fields.getElementsByTagName("FieldColumnName");
713  var FieldRelativePaths = fields.getElementsByTagName("FieldRelativePath");
714  var FieldColumnTypes = fields.getElementsByTagName("FieldColumnType");
715  var FieldColumnDataTypes = fields.getElementsByTagName("FieldColumnDataType");
716  var FieldColumnDataChoices = fields.getElementsByTagName("FieldColumnDataChoices");
717  var FieldColumnDefaultValues = fields.getElementsByTagName("FieldColumnDefaultValue");
718 
719 
720  for(var i=0;i<FieldTableNames.length;++i)
721  {
722  var obj = {};
723  obj.fieldTableName = DesktopContent.getXMLValue(FieldTableNames[i]);
724  obj.fieldColumnName = DesktopContent.getXMLValue(FieldColumnNames[i]);
725  obj.fieldRelativePath = DesktopContent.getXMLValue(FieldRelativePaths[i]);
726  obj.fieldColumnType = DesktopContent.getXMLValue(FieldColumnTypes[i]);
727  obj.fieldColumnDataType = DesktopContent.getXMLValue(FieldColumnDataTypes[i]);
728  obj.fieldColumnDefaultValue = DesktopContent.getXMLValue(FieldColumnDefaultValues[i]);
729 
730  var FieldColumnDataChoicesArr = FieldColumnDataChoices[i].getElementsByTagName("FieldColumnDataChoice");
731  obj.fieldColumnDataChoicesArr = [];
732  for(var j=0; j<FieldColumnDataChoicesArr.length;++j)
733  obj.fieldColumnDataChoicesArr.push(DesktopContent.getXMLValue(FieldColumnDataChoicesArr[j]));
734 
735  recFields.push(obj);
736  }
737  Debug.log("Records length: " + recFields.length);
738  if(responseHandler) responseHandler(recFields);
739 
740  }, //handler
741  0, //handler param
742  0,true); //progressHandler, callHandlerOnErr
743 } //end getFieldsOfRecords()
744 
745 //=====================================================================================
746 //getFieldValuesForRecords ~~
747 // takes as input a base path where the record is,
748 // and the record uid.
749 // <recordArr> is an array or record UIDs (as returned from
750 // ConfigurationAPI.getSubsetRecords)
751 // <fieldObjArr> is an array of field objects (as returned from
752 // ConfigurationAPI.getFieldsOfRecords). This
753 // is converted internally to a CSV list of tree paths relative to <subsetBasePath>
754 // to the fields to be read.
755 //
756 // <modifiedTables> is an array of Table objects (as returned from
757 // ConfigurationAPI.setFieldValuesForRecords)
758 //
759 // when complete, the responseHandler is called with an array parameter.
760 // on failure, the array will be empty.
761 // on success, the array will be an array of FieldValue objects
762 // FieldValue := {}
763 // obj.fieldUID
764 // obj.fieldPath
765 // obj.fieldValue
766 //
767 ConfigurationAPI.getFieldValuesForRecords = function(subsetBasePath,
768  recordArr,fieldObjArr,
769  responseHandler,modifiedTables,silenceErrors)
770 {
771  var modifiedTablesListStr = "";
772  for(var i=0;modifiedTables && i<modifiedTables.length;++i)
773  {
774  if(i) modifiedTablesListStr += ",";
775  modifiedTablesListStr += modifiedTables[i].tableName + "," +
776  modifiedTables[i].tableVersion;
777  }
778 
779  var recordListStr = "";
780  if(Array.isArray(recordArr))
781  for(var i=0;i<recordArr.length;++i)
782  {
783  if(i) recordListStr += ",";
784  recordListStr += encodeURIComponent(recordArr[i]);
785  }
786  else //handle single record case
787  recordListStr = encodeURIComponent(recordArr);
788 
789 
790  var fieldListStr = "";
791  if(fieldObjArr.length && (typeof fieldObjArr[0] === "string"))
792  {
793  //assume fieldObjArr is a user generated array and not URI encoded
794 
795  if(Array.isArray(fieldObjArr))
796  for(var i=0;i<fieldObjArr.length;++i)
797  {
798  if(i) fieldListStr += ",";
799  fieldListStr += encodeURIComponent(fieldObjArr[i]);
800  }
801  else
802  fieldListStr = encodeURIComponent(fieldObjArr);
803  }
804  else
805  {
806  //assume fieldObjArr already URI encoded (as returned by ConfigurationAPI.getFieldsOfRecords())
807 
808  for(var i=0;i<fieldObjArr.length;++i)
809  {
810  if(i) fieldListStr += ",";
811  fieldListStr += fieldObjArr[i].fieldRelativePath +
812  fieldObjArr[i].fieldColumnName;
813  }
814  }
815 
816  DesktopContent.XMLHttpRequest("Request?RequestType=getTreeNodeFieldValues" +
817  "&tableGroup=" +
818  "&tableGroupKey=-1", //end get data
819  "startPath=/" + subsetBasePath +
820  "&recordList=" + recordListStr +
821  "&fieldList=" + fieldListStr +
822  "&modifiedTables=" + modifiedTablesListStr, //end post data
823  function(req)
824  {
825  var recFieldValues = [];
826  var err = DesktopContent.getXMLValue(req,"Error");
827  if(err)
828  {
829  if(!silenceErrors) Debug.log(err,Debug.HIGH_PRIORITY);
830  if(responseHandler) responseHandler(recFieldValues,err);
831  return;
832  }
833 
834  var fieldValues = req.responseXML.getElementsByTagName("fieldValues");
835 
836  for(var f=0;f<fieldValues.length;++f)
837  {
838  var FieldPaths = fieldValues[f].getElementsByTagName("FieldPath");
839  var FieldValues = fieldValues[f].getElementsByTagName("FieldValue");
840  for(var i=0;i<FieldPaths.length;++i)
841  {
842  var obj = {};
843  obj.fieldUID = DesktopContent.getXMLValue(fieldValues[f]);
844  obj.fieldPath = DesktopContent.getXMLValue(FieldPaths[i]);
845  obj.fieldValue = DesktopContent.getXMLValue(FieldValues[i]);
846  recFieldValues.push(obj);
847 
848  //track last stable value, inject in object (e.g. used by child link fill field)
849  fieldObjArr[i].fieldColumnValue = obj.fieldValue;
850  }
851  }
852 
853  if(responseHandler) responseHandler(recFieldValues);
854 
855  }, //handler
856  0, //handler param
857  0,true); //progressHandler, callHandlerOnErr
858 } //end getFieldValuesForRecords()
859 
860 
861 //=====================================================================================
862 //getUniqueFieldValuesForRecords ~~
863 // takes as input a base path where the records are,
864 // and an array of records.
865 // <recordArr> is an array or record UIDs (as returned from
866 // ConfigurationAPI.getSubsetRecords)
867 // <fieldList> is a CSV list of tree paths relative to <subsetBasePath>/<recordUID>/
868 // to the fields for which to get the set of unique values.
869 // If empty, then expect an empty array.
870 // e.g. "LinkToFETypeConfiguration,FEInterfacePluginName"
871 //
872 // <modifiedTables> is an array of Table objects (as returned from
873 // ConfigurationAPI.setFieldValuesForRecords)
874 //
875 // when complete, the responseHandler is called with an array parameter.
876 // on failure, the array will be empty.
877 // on success, the array will be an array of UniqueValues objects
878 // UniqueValues := {}
879 // obj.fieldName
880 // obj.fieldUniqueValueArray
881 //
882 //
883 ConfigurationAPI.getUniqueFieldValuesForRecords = function(subsetBasePath,recordArr,fieldList,
884  responseHandler,modifiedTables)
885 {
886  var modifiedTablesListStr = "";
887  for(var i=0;modifiedTables && i<modifiedTables.length;++i)
888  {
889  if(i) modifiedTablesListStr += ",";
890  modifiedTablesListStr += modifiedTables[i].tableName + "," +
891  modifiedTables[i].tableVersion;
892  }
893 
894  var recordListStr = "";
895  if(Array.isArray(recordArr))
896  for(var i=0;i<recordArr.length;++i)
897  {
898  if(i) recordListStr += ",";
899  recordListStr += encodeURIComponent(recordArr[i]);
900  }
901  else //handle single record case
902  recordListStr = encodeURIComponent(recordArr);
903 
904  DesktopContent.XMLHttpRequest("Request?RequestType=getUniqueFieldValuesForRecords" +
905  "&tableGroup=" +
906  "&tableGroupKey=-1", //end get data
907  "startPath=/" + subsetBasePath +
908  "&recordList=" + recordListStr +
909  "&fieldList=" + fieldList +
910  "&modifiedTables=" + modifiedTablesListStr, //end post data
911  function(req)
912  {
913  var fieldUniqueValues = [];
914  var err = DesktopContent.getXMLValue(req,"Error");
915  if(err)
916  {
917  Debug.log(err,Debug.HIGH_PRIORITY);
918  if(responseHandler) responseHandler(fieldUniqueValues);
919  return;
920  }
921 
922  var fields = req.responseXML.getElementsByTagName("field");
923 
924  for(var i=0;i<fields.length;++i)
925  {
926 
927  var uniqueValues = fields[i].getElementsByTagName("uniqueValue");
928  var groupIdChildLinkIndex = DesktopContent.getXMLNode(
929  fields[i],"childLinkIndex");
930 
931  var obj = {};
932  obj.fieldName = DesktopContent.getXMLValue(fields[i]);
933 
934  if(groupIdChildLinkIndex)
935  obj.childLinkIndex = DesktopContent.getXMLValue(groupIdChildLinkIndex);
936 
937  obj.fieldUniqueValueArray = [];
938  for(var j=0;j<uniqueValues.length;++j)
939  obj.fieldUniqueValueArray.push(DesktopContent.getXMLValue(uniqueValues[j]));
940  fieldUniqueValues.push(obj);
941  }
942  Debug.log("fieldUniqueValues length: " + fieldUniqueValues.length);
943  if(responseHandler) responseHandler(fieldUniqueValues);
944 
945  }, //handler
946  0, //handler param
947  0,true); //progressHandler, callHandlerOnErr
948 } //end getUniqueFieldValuesForRecords()
949 
950 //=====================================================================================
951 //setFieldValuesForRecords ~~
952 // takes as input a base path where the records are,
953 // and an array of records.
954 // <recordArr> is an array or record UIDs (as returned from
955 // ConfigurationAPI.getSubsetRecords)
956 // <fieldObjArr> is an array of field objects (as returned from
957 // ConfigurationAPI.getFieldsOfRecords). This
958 // is converted internally to a CSV list of tree paths relative to <subsetBasePath>
959 // to the fields to be written.
960 // ALTERNATIVELY: the user can pass an array of full path strings.
961 // <valueArr> is an array of values, with index corresponding to the associated
962 // field in the <fieldObjArr>.
963 //
964 // <modifiedTables> is an array of Table objects (as returned from
965 // ConfigurationAPI.setFieldValuesForRecords)
966 //
967 // when complete, the responseHandler is called with an array parameter.
968 // on failure, the array will be empty.
969 // on success, the array will be an array of Table objects
970 // Table := {}
971 // obj.tableName
972 // obj.tableVersion
973 // obj.tableComment
974 //
975 //
976 ConfigurationAPI.setFieldValuesForRecords = function(subsetBasePath,recordArr,fieldObjArr,
977  valueArr,responseHandler,modifiedTablesIn,silenceErrors)
978 {
979  var modifiedTablesListStr = "";
980  for(var i=0;modifiedTablesIn && i<modifiedTablesIn.length;++i)
981  {
982  if(i) modifiedTablesListStr += ",";
983  modifiedTablesListStr += modifiedTablesIn[i].tableName + "," +
984  modifiedTablesIn[i].tableVersion;
985  }
986 
987  var fieldListStr = "";
988  if(fieldObjArr.length && (typeof fieldObjArr[0] === "string"))
989  {
990  //assume fieldObjArr is a user generated array and not URI encoded
991 
992  if(Array.isArray(fieldObjArr))
993  for(var i=0;i<fieldObjArr.length;++i)
994  {
995  if(i) fieldListStr += ",";
996  fieldListStr += encodeURIComponent(fieldObjArr[i]);
997  }
998  else
999  fieldListStr = encodeURIComponent(fieldObjArr);
1000  }
1001  else
1002  {
1003  //assume fieldObjArr already URI encoded (as returned by ConfigurationAPI.getFieldsOfRecords())
1004 
1005  for(var i=0;i<fieldObjArr.length;++i)
1006  {
1007  if(i) fieldListStr += ",";
1008  fieldListStr += fieldObjArr[i].fieldRelativePath +
1009  fieldObjArr[i].fieldColumnName;
1010  }
1011  }
1012 
1013 
1014  var valueListStr = "";
1015  if(Array.isArray(valueArr))
1016  for(var i=0;i<valueArr.length;++i)
1017  {
1018  if(i) valueListStr += ",";
1019  valueListStr += encodeURIComponent(valueArr[i]);
1020  }
1021  else //handle single record case
1022  valueListStr = encodeURIComponent(valueArr);
1023 
1024 
1025  var recordListStr = "";
1026  if(Array.isArray(recordArr))
1027  for(var i=0;i<recordArr.length;++i)
1028  {
1029  if(i) recordListStr += ",";
1030  recordListStr += encodeURIComponent(recordArr[i]);
1031  }
1032  else //handle single record case
1033  recordListStr = encodeURIComponent(recordArr);
1034 
1035  DesktopContent.XMLHttpRequest("Request?RequestType=setTreeNodeFieldValues" +
1036  "&tableGroup=" +
1037  "&tableGroupKey=-1", //end get data
1038  "startPath=/" + subsetBasePath +
1039  "&recordList=" + recordListStr +
1040  "&valueList=" + valueListStr +
1041  "&fieldList=" + fieldListStr +
1042  "&modifiedTables=" + modifiedTablesListStr, //end post data
1043  function(req)
1044  {
1045  var modifiedTables = [];
1046 
1047  var err = DesktopContent.getXMLValue(req,"Error");
1048  if(err)
1049  {
1050  if(!silenceErrors)
1051  Debug.log(err,Debug.HIGH_PRIORITY);
1052  if(responseHandler) responseHandler(modifiedTables,err);
1053  return;
1054  }
1055  //modifiedTables
1056  var tableNames = req.responseXML.getElementsByTagName("NewActiveTableName");
1057  var tableVersions = req.responseXML.getElementsByTagName("NewActiveTableVersion");
1058  var tableComments = req.responseXML.getElementsByTagName("NewActiveTableComment");
1059  var tableVersion;
1060 
1061  //add only temporary version
1062  for(var i=0;i<tableNames.length;++i)
1063  {
1064  tableVersion = DesktopContent.getXMLValue(tableVersions[i])|0; //force integer
1065  if(tableVersion >= -1) continue; //skip unless temporary
1066  var obj = {};
1067  obj.tableName = DesktopContent.getXMLValue(tableNames[i]);
1068  obj.tableVersion = DesktopContent.getXMLValue(tableVersions[i]);
1069  obj.tableComment = DesktopContent.getXMLValue(tableComments[i]);
1070  modifiedTables.push(obj);
1071  }
1072 
1073  if(responseHandler) responseHandler(modifiedTables);
1074 
1075  }, //handler
1076  0, //handler param
1077  0,true); //progressHandler, callHandlerOnErr
1078 } //end setFieldValuesForRecords()
1079 
1080 //=====================================================================================
1081 //popUpSaveModifiedTablesForm ~~
1082 // presents the user with the form to choose the options for ConfigurationAPI.saveModifiedTables
1083 //
1084 // When ConfigurationAPI.saveModifiedTables is called,
1085 // it will generate popup messages indicating progress.
1086 //
1087 // <modifiedTables> is an array of Table objects (as returned from
1088 // ConfigurationAPI.setFieldValuesForRecords)
1089 //
1090 // when complete, the responseHandler is called with 3 array parameters.
1091 // on failure, the arrays will be empty.
1092 // on success, the arrays will be an array of Saved Table objects
1093 // SavedTable := {}
1094 // obj.tableName
1095 // obj.tableVersion
1096 // obj.tableComment
1097 //
1098 // ...and array of Saved Group objects
1099 // SavedGroup := {}
1100 // obj.groupName
1101 // obj.groupKey
1102 // obj.groupComment
1103 //
1104 // ...and array of Saved Alias objects
1105 // SavedAlias := {}
1106 // obj.groupName
1107 // obj.groupKey
1108 // obj.groupAlias
1109 //
1110 ConfigurationAPI.popUpSaveModifiedTablesForm = function(modifiedTables,responseHandler)
1111 {
1112  //mimic ConfigurationGUI::popUpSaveTreeForm()
1113 
1114  Debug.log("ConfigurationAPI popUpSaveModifiedTablesForm");
1115 
1116  var str = "";
1117 
1118  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
1119  if(!el)
1120  {
1121  el = document.createElement("div");
1122  el.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID);
1123  }
1124  el.style.display = "none";
1125 
1126  var gh = 50;
1127  var w = 380;
1128  var h = 330;
1129  ConfigurationAPI.setPopUpPosition(el,w /*w*/,h-gh*2 /*h*/);
1130 
1131  //set position and size
1132 // var w = 380;
1133 // var h = 330;
1134 // var gh = 50;
1135 // var ww = DesktopContent.getWindowWidth();
1136 // var wh = DesktopContent.getWindowHeight();
1137 // el.style.top = (DesktopContent.getWindowScrollTop() + ((wh-h-2)/2)- gh*2) + "px"; //allow for 2xgh growth for each affected group
1138 // el.style.left = (DesktopContent.getWindowScrollLeft() + ((ww-w-2)/2)) + "px";
1139 // el.style.width = w + "px";
1140 // el.style.height = h + "px";
1141 
1142  //always
1143  // - save modified tables (show list of modified tables)
1144  // (and which active group they are in)
1145  //
1146  //optionally
1147  // - checkbox to bump version of modified active groups
1148  // - checkbox to assign system alias to bumped active group
1149 
1150 
1151  var modTblCount = 0;
1152  var modTblStr = "";
1153  var modifiedTablesListStr = ""; //csv table, temporay version,...
1154 
1155  for(var j=0;j<modifiedTables.length;++j)
1156  if((modifiedTables[j].tableVersion|0) < -1)
1157  {
1158  if(modTblCount++)
1159  modTblStr += ",";
1160  modTblStr += modifiedTables[j].tableName;
1161 
1162  if(modifiedTablesListStr.length)
1163  modifiedTablesListStr += ",";
1164  modifiedTablesListStr += modifiedTables[j].tableName;
1165  modifiedTablesListStr += ",";
1166  modifiedTablesListStr += modifiedTables[j].tableVersion;
1167  }
1168 
1169  var str = "<a id='" +
1170  ConfigurationAPI._POP_UP_DIALOG_ID +
1171  "-cancel' href='#'>Cancel</a><br><br>";
1172 
1173  str += "<div id='" + ConfigurationAPI._POP_UP_DIALOG_ID + "-div'>";
1174  str += "Saving will create new persistent versions of each modified table." +
1175  "<br><br>" +
1176  "Here is the list of modified tables (count=" + modTblCount +
1177  "):" +
1178  "<br>";
1179 
1180 
1181  //display modified tables
1182  str += "<div style='white-space:nowrap; width:" + w + "px; height:40px; " +
1183  "overflow:auto; font-weight: bold;'>";
1184  str += modTblStr;
1185  str += "</div>";
1186 
1187  //get affected groups
1188  // and save member map to hidden div for Save action
1190  DesktopContent.XMLHttpRequest("Request?RequestType=getAffectedActiveGroups" +
1191  "&groupName=" +
1192  "&groupKey=-1", //end get params
1193  "&modifiedTables=" + modifiedTablesListStr, //end post params
1194  function(req)
1195  {
1196  var err = DesktopContent.getXMLValue(req,"Error");
1197  if(err)
1198  {
1199  Debug.log(err,Debug.HIGH_PRIORITY);
1200  el.innerHTML = str;
1201  return;
1202  }
1203 
1204  //for each affected group
1205  // put csv: name,key,memberName,memberVersion...
1206  var groups = req.responseXML.getElementsByTagName("AffectedActiveGroup");
1207  var memberNames, memberVersions;
1208  var xmlGroupName;
1209  modTblStr = ""; //re-use
1210  for(var i=0;i<groups.length;++i)
1211  {
1212  xmlGroupName = DesktopContent.getXMLValue(groups[i],"GroupName");
1213  str += "<div style='display:none' class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
1214  "-affectedGroups' >";
1215  str += xmlGroupName;
1216  str += "," + DesktopContent.getXMLValue(groups[i],"GroupKey");
1217 
1218  memberNames = groups[i].getElementsByTagName("MemberName");
1219  memberVersions = groups[i].getElementsByTagName("MemberVersion");
1220  Debug.log("memberNames.length " + memberNames.length);
1221  for(var j=0;j<memberNames.length;++j)
1222  str += "," + DesktopContent.getXMLValue(memberNames[j]) +
1223  "," + DesktopContent.getXMLValue(memberVersions[j]);
1224  str += "</div>"; //close div " + ConfigurationAPI._POP_UP_DIALOG_ID + "-affectedGroups
1225 
1226 
1227  if(modTblStr.length)
1228  modTblStr += ",";
1229 
1230 
1231  modTblStr += "<a style='color:black' href='#' onclick='javascript:" +
1232  "var forFirefox = ConfigurationAPI.handleGroupCommentToggle(\"" +
1233  xmlGroupName + "\");" +
1234  " ConfigurationAPI.handlePopUpHeightToggle(" + h + "," + gh + ");'>";
1235  modTblStr += xmlGroupName;
1236  modTblStr += "</a>";
1237 
1238  //store cached group comment in hidden html
1239  modTblStr += "<div id='" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupComment-" +
1240  xmlGroupName + "' " +
1241  "class='" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupComment-cache' " +
1242  "style='display:none'>" +
1243  decodeURIComponent(DesktopContent.getXMLValue(groups[i],"GroupComment")) +
1244  "</div>";
1245  }
1246 
1247  str += "Please choose the options you want and click 'Save':" +
1248  "<br>";
1249 
1250  //add checkbox to save affected groups
1251  str += "<input type='checkbox' id='" + ConfigurationAPI._POP_UP_DIALOG_ID +
1252  "-bumpGroupVersions' checked " +
1253  "onclick='ConfigurationAPI.handlePopUpHeightToggle(" + h + "," + gh + ");'>";
1254  //add link so text toggles checkbox
1255  str += "<a href='#' onclick='javascript:" +
1256  "var el = document.getElementById(\"" + ConfigurationAPI._POP_UP_DIALOG_ID +
1257  "-bumpGroupVersions\");" +
1258  "var forFirefox = (el.checked = !el.checked);" +
1259  " ConfigurationAPI.handlePopUpHeightToggle(" + h + "," + gh + "); return false;'>";
1260  str += "Save Affected Groups as New Keys";
1261  str += "</a>";
1262  str += "</input><br>";
1263 
1264  //add checkbox to activate saved affected groups
1265  str += "<input type='checkbox' id='" + ConfigurationAPI._POP_UP_DIALOG_ID +
1266  "-activateBumpedGroupVersions' checked " +
1267  ">";
1268  //add link so text toggles checkbox
1269  str += "<a href='#' onclick='javascript:" +
1270  "var el = document.getElementById(\"" + ConfigurationAPI._POP_UP_DIALOG_ID +
1271  "-activateBumpedGroupVersions\");" +
1272  "if(el.disabled) return false; " +
1273  "var forFirefox = (el.checked = !el.checked);" +
1274  "return false;'>";
1275  str += "Also Activate New Groups";
1276  str += "</a>";
1277  str += "</input><br>";
1278 
1279  str += "Here is the list of affected groups (count=" + groups.length +
1280  "):" +
1281  "<br>";
1282 
1283  //display affected groups
1284  str += "<div style='white-space:nowrap; width:" + w + "px; margin-bottom:20px; " +
1285  "overflow:auto; font-weight: bold;'>";
1286  str += modTblStr;
1287  str += "<div id='clearDiv'></div>";
1288  str += "<center>";
1289 
1290  str += "<div id='" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupComment-header'></div>";
1291 
1292  str += "<div id='clearDiv'></div>";
1293 
1294  str += "<textarea id='" + ConfigurationAPI._POP_UP_DIALOG_ID +
1295  "-groupComment' rows='4' cols='50' " +
1296  "style='width:417px;height:68px;display:none;margin:0;'>";
1297  str += ConfigurationAPI._DEFAULT_COMMENT;
1298  str += "</textarea>";
1299  str += "</center>";
1300 
1301  str += "</div>"; //end affected groups div
1302 
1303  str += "<div id='" + ConfigurationAPI._POP_UP_DIALOG_ID +
1304  "-groupAliasArea' ><center>";
1305 
1306  //get existing group aliases
1308  DesktopContent.XMLHttpRequest("Request?RequestType=getGroupAliases" +
1309  "",
1310  "",
1311  function(req)
1312  {
1313  var err = DesktopContent.getXMLValue(req,"Error");
1314  if(err)
1315  {
1316  Debug.log(err,Debug.HIGH_PRIORITY);
1317  el.innerHTML = str;
1318  return;
1319  }
1320 
1321  var aliases = req.responseXML.getElementsByTagName("GroupAlias");
1322  var aliasGroupNames = req.responseXML.getElementsByTagName("GroupName");
1323  var aliasGroupKeys = req.responseXML.getElementsByTagName("GroupKey");
1324 
1325  //for each affected group
1326  // -Show checkbox for setting alias and dropdown for alias
1327  // and a pencil to change dropdown to text box to free-form alias.
1328  // -Also, identify if already aliased and choose that as default option
1329  // in dropwdown.
1330  var alias, aliasGroupName, aliasGroupKey;
1331  var groupName, groupKey;
1332  var groupOptionIndex = []; //keep distance and index of option for each group, or -1 if none
1333  for(var i=0;i<groups.length;++i)
1334  {
1335  groupOptionIndex.push([-1,0]); //index and distance
1336 
1337  groupName = DesktopContent.getXMLValue(groups[i],"GroupName");
1338  groupKey = DesktopContent.getXMLValue(groups[i],"GroupKey");
1339 
1340  //find alias
1341  modTblStr = ""; //re-use
1342  for(var j=0;j<aliasGroupNames.length;++j)
1343  {
1344  alias = DesktopContent.getXMLValue(aliases[j]);
1345  aliasGroupName = DesktopContent.getXMLValue(aliasGroupNames[j]);
1346  aliasGroupKey = DesktopContent.getXMLValue(aliasGroupKeys[j]);
1347 
1348  //Debug.log("compare " + aliasGroupName + ":" +
1349  // aliasGroupKey);
1350 
1351  //also build drop down
1352  modTblStr += "<option value='" + alias + "' ";
1353 
1354  //consider any alias with same groupName
1355  if(aliasGroupName == groupName)
1356  {
1357  if(groupOptionIndex[i][0] == -1 || //take best match
1358  Math.abs(groupKey - aliasGroupKey) < groupOptionIndex[i][1])
1359  {
1360  Debug.log("found alias");
1361  groupOptionIndex[i][0] = j; //index
1362  groupOptionIndex[i][1] = Math.abs(groupKey - aliasGroupKey); //distance
1363  }
1364  }
1365  modTblStr += ">";
1366  modTblStr += alias; //can display however
1367  modTblStr += "</option>";
1368  }
1369 
1370  str += "<input type='checkbox' class='" + ConfigurationAPI._POP_UP_DIALOG_ID + "-setGroupAlias' " +
1371  (groupOptionIndex[i][0] >= 0?"checked":"") + //check if has an alias already
1372  ">";
1373  //add link so text toggles checkbox
1374  str += "<a href='#' onclick='javascript:" +
1375  "var el = document.getElementsByClassName(\"" + ConfigurationAPI._POP_UP_DIALOG_ID + "-setGroupAlias\");" +
1376  "var forFirefox = (el[" + i + "].checked = !el[" + i + "].checked);" +
1377  " return false;'>";
1378  str += "Set '<b style='font-size:16px'>" + groupName + "</b>' to System Alias:";
1379  str += "</a><br>";
1380 
1381  str += "<table cellpadding='0' cellspacing='0' border='0'><tr><td>";
1382  str += "<select " +
1383  "id='" + ConfigurationAPI._POP_UP_DIALOG_ID + "-editAliasSelect-" + (i) + "' " +
1384  "style='margin:2px; height:" + (25) + "px'>";
1385  str += modTblStr;
1386  str += "</select>";
1387 
1388  str += "<input type='text' " +
1389  "id='" + ConfigurationAPI._POP_UP_DIALOG_ID + "-editAliasTextBox-" + (i) + "' " +
1390  "style='display:none; margin:2px; width:150px; height:" +
1391  (19) + "px'>";
1392  str += "";
1393  str += "</input>";
1394  str += "</td><td>";
1395 
1396  str += "<div style='display:block' " +
1397  "class='" + ConfigurationAPI._POP_UP_DIALOG_ID + "-editIcon' id='" + ConfigurationAPI._POP_UP_DIALOG_ID +
1398  "-editIcon-" +
1399  (i) + "' " +
1400  "onclick='ConfigurationAPI.handlePopUpAliasEditToggle(" +
1401  i +
1402  ");' " +
1403  "title='Toggle free-form system alias editing' " +
1404  "></div>";
1405 
1406  str += "<div class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
1407  "-preloadImage' id='" + ConfigurationAPI._POP_UP_DIALOG_ID +
1408  "-preloadImage-editIconHover'></div>";
1409 
1410  str += "</td></tr></table>";
1411 
1412  str += "</input>";
1413 
1414  //increase height each time a group check is added
1415  h += gh;
1416  el.style.height = h + "px";
1417  }
1418 
1419  str += "</center></div>"; //close id='" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupAliasArea'
1420 
1421 
1422  // done with system alias handling
1423  // continue with pop-up prompt
1424 
1425  str += "</div><br>"; //close main popup div
1426 //
1427 // var onmouseupJS = "";
1428 // onmouseupJS += "document.getElementById(\"" + ConfigurationAPI._POP_UP_DIALOG_ID + "-submitButton\").disabled = true;";
1429 // onmouseupJS += "ConfigurationAPI.handleGroupCommentToggle(0,1);"; //force cache of group comment
1430 // onmouseupJS += "ConfigurationAPI.handlePopUpHeightToggle(" + h + "," + gh + ");";
1431 // onmouseupJS += "ConfigurationAPI.saveModifiedTables();";
1432 
1433  str += "<input id='" + ConfigurationAPI._POP_UP_DIALOG_ID +
1434  "-submitButton' type='button' " + //onmouseup='" +
1435  //onmouseupJS + "' " +
1436  "value='Save' title='" +
1437  "Save new versions of every modified table\n" +
1438  "(Optionally, save new active groups and assign system aliases)" +
1439  "'/>";
1440  el.innerHTML = str;
1441 
1442  //create submit onmouseup handler
1443  document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID +
1444  "-submitButton").onmouseup = function() {
1445  Debug.log("Submit mouseup");
1446  this.disabled = true;
1447  ConfigurationAPI.handleGroupCommentToggle(0,1); //force cache of group comment
1448  ConfigurationAPI.handlePopUpHeightToggle(h,gh);
1449 
1450  var savingGroups =
1451  document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID +
1452  "-bumpGroupVersions").checked;
1453  var activatingSavedGroups =
1454  document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID +
1455  "-activateBumpedGroupVersions").checked;
1456 
1457  ConfigurationAPI.saveModifiedTables(modifiedTables,responseHandler,
1458  true); //doNotIgnoreWarnings
1459 
1460  }; //end submit onmouseup handler
1461 
1462  //create cancel onclick handler
1463  document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID +
1464  "-cancel").onclick = function(event) {
1465  Debug.log("Cancel click");
1466  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
1467  if(el) el.parentNode.removeChild(el); //close popup
1468  if(responseHandler) responseHandler([],[],[]); //empty array indicates nothing done
1469  return false;
1470  }; //end submit onmouseup handler
1471 
1472 
1473  //handle default dropdown selections for group alias
1474  for(var i=0;i<groups.length;++i)
1475  if(groupOptionIndex[i][0] != -1) //have a default
1476  document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-editAliasSelect-" +
1477  i).selectedIndex = groupOptionIndex[i][0];
1478 
1479  },0,0,true //reqParam, progressHandler, callHandlerOnErr
1480  ); //end of getGroupAliases handler
1481 
1482  },0,0,true //reqParam, progressHandler, callHandlerOnErr
1483  ); //end of getActiveTableGroups handler
1484 
1485 
1486  document.body.appendChild(el); //add element to display div
1487  el.style.display = "block";
1488 
1489 }
1490 
1491 
1492 //=====================================================================================
1493 //handleGroupCommentToggle ~~
1494 // toggles affected group comment box and handles details
1495 ConfigurationAPI.handleGroupCommentToggle = function(groupName,setHideVal)
1496 {
1497  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupComment");
1498  var hel = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupComment-header");
1499  var cel;
1500 
1501  var doHide = el.style.display != "none";
1502  if(setHideVal !== undefined)
1503  doHide = setHideVal;
1504 
1505  if(doHide) //cache (possibly modified) group comment
1506  {
1507  if(hel.textContent == "") return; //assume was a force hide when already hidden
1508 
1509  //get current groupName so we know where to cache comment
1510  var gn = hel.textContent.split("'")[1];
1511  Debug.log("gn " + gn);
1512  cel = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupComment-" +
1513  gn);
1514  cel.innerHTML = "";
1515  cel.appendChild(document.createTextNode(el.value));
1516 
1517  //setup group comment header properly
1518  hel.innerHTML = "";
1519  el.style.display = "none";
1520 
1521  //if for sure hiding, then done
1522  if(gn == groupName || setHideVal !== undefined)
1523  return;
1524  //else show immediately the new selection
1525  }
1526 
1527  //show groupName comment
1528  {
1529  cel = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupComment-" +
1530  groupName);
1531  el.value = cel.textContent;
1532  el.style.display = "block"; //show display before set caret (for Firefox)
1533  ConfigurationAPI.setCaretPosition(el,0,cel.textContent.length);
1534 
1535  hel.innerHTML = ("&apos;" + groupName + "&apos; group comment:");
1536  }
1537 }
1538 
1539 //=====================================================================================
1540 //handlePopUpHeightToggle ~~
1541 // checkbox was already set before this function call
1542 // this responds to current value
1543 //
1544 // pass height and group height
1545 ConfigurationAPI.handlePopUpHeightToggle = function(h,gh)
1546 {
1547  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-bumpGroupVersions");
1548  Debug.log("ConfigurationAPI.handlePopUpHeightToggle " + el.checked);
1549 
1550  var ael = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-activateBumpedGroupVersions");
1551 
1552  var groupCommentEl = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupComment");
1553  var groupCommentHeight = 0;
1554 
1555  if(groupCommentEl && groupCommentEl.style.display != "none")
1556  groupCommentHeight += 100;
1557 
1558  var popEl = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "");
1559  if(!el.checked)
1560  {
1561  //hide alias area and subtract the height
1562 
1563  document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupAliasArea").style.display = "none";
1564  popEl.style.height = (h + groupCommentHeight) + "px";
1565  ael.disabled = true;
1566  }
1567  else
1568  {
1569  //show alias area and add the height
1570 
1571  //count if grps is 1 or 2
1572  var grps = document.getElementsByClassName("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-affectedGroups");
1573  popEl.style.height = (h + grps.length*gh + groupCommentHeight) + "px";
1574  document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-groupAliasArea").style.display = "block";
1575  ael.disabled = false;
1576  }
1577 }
1578 
1579 //=====================================================================================
1580 //handlePopUpAliasEditToggle ~~
1581 ConfigurationAPI.handlePopUpAliasEditToggle = function(i)
1582 {
1583  Debug.log("handlePopUpAliasEditToggle " + i);
1584 
1585  var sel = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-editAliasSelect-"+i);
1586  var tel = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-editAliasTextBox-"+i);
1587  Debug.log("sel.style.display " + sel.style.display);
1588  if(sel.style.display == "none")
1589  {
1590  sel.style.display = "block";
1591  tel.style.display = "none";
1592  }
1593  else
1594  {
1595  tel.style.width = (sel.offsetWidth-2) + "px";
1596  sel.style.display = "none";
1597  tel.style.display = "block";
1598  ConfigurationAPI.setCaretPosition(tel,0,tel.value.length);
1599  }
1600 } //end handlePopUpAliasEditToggle()
1601 
1602 //=====================================================================================
1603 //saveModifiedTables ~~
1604 ConfigurationAPI.addTableToConfigurationGroup = function(tableName)
1605 {
1606  Debug.log("addTableToConfigurationGroup",tableName);
1607 
1608  ConfigurationAPI.saveModifiedTables([{"tableName":tableName,"tableVersion":-1}]);
1609 } //end addTableToConfigurationGroup()
1610 
1611 //=====================================================================================
1612 //saveModifiedTables ~~
1613 // Takes as input an array of modified tables and saves
1614 // those table temporary versions to persistent versions.
1615 // Optionally, save/activate affected groups and setup associated aliases.
1616 //
1617 // By default, it will ignore warnings, save affected groups, and save
1618 // the system aliases for affected groups (most similar system alias)
1619 //
1620 // It will also generate popup messages indicating progress.
1621 //
1622 // <modifiedTables> is an array of Table objects (as returned from
1623 // ConfigurationAPI.setFieldValuesForRecords)
1624 //
1625 // <tablesToAdd> is an array of Table objects to add to modified group. -1 would be empty mockup table.
1626 //
1627 // Note: when called from popup, uses info from popup.
1628 //
1629 // when complete, the responseHandler is called with 3 array parameters.
1630 // on failure, the arrays will be empty.
1631 // on success, the arrays will be an array of Saved Table objects
1632 // SavedTable := {}
1633 // obj.tableName
1634 // obj.tableVersion
1635 // obj.tableComment
1636 //
1637 // ...and array of Saved Group objects
1638 // SavedGroup := {}
1639 // obj.groupName
1640 // obj.groupKey
1641 // obj.groupComment
1642 //
1643 // ...and array of Saved Alias objects
1644 // SavedAlias := {}
1645 // obj.groupName
1646 // obj.groupKey
1647 // obj.groupAlias
1648 //
1649 //
1650 ConfigurationAPI.saveModifiedTables = function(modifiedTables,responseHandler,
1651  doNotIgnoreWarnings,doNotSaveAffectedGroups,
1652  doNotActivateAffectedGroups,doNotSaveAliases,
1653  doNotIgnoreGroupActivationWarnings,
1654  doNotKillPopUpEl, tablesToAdd)
1655 {
1656  //copy from ConfigurationGUI::saveModifiedTree
1657 
1658  var savedTables = [];
1659  var savedGroups = [];
1660  var savedAliases = [];
1661 
1662  if(!modifiedTables.length)
1663  {
1664  Debug.log("No tables were modified. Nothing to do.", Debug.WARN_PRIORITY);
1665  if(responseHandler) responseHandler(savedTables,savedGroups,savedAliases);
1666  return;
1667  }
1668 
1669  //for each modified table
1670  // save new version
1671  // update tree member table version based on result
1672  //if saving groups
1673  // for each affected group
1674  // save member list but with tree table versions
1675  // modify root group name if changed
1676  //if saving aliases
1677  // for each alias
1678  // set alias for new group
1679 
1680 
1681  var numberOfRequests = 0;
1682  var numberOfReturns = 0;
1683  var allRequestsSent = false;
1684 
1685  //::::::::::::::::::::::::::::::::::::::::::
1686  //localHandleAffectedGroups ~~
1687  function localHandleAffectedGroups()
1688  {
1689  Debug.log("Done with table saving.");
1690 
1691  //check if saving groups
1692  var savingGroups;
1693  var activatingSavedGroups;
1694  var doRequestAffectedGroups = false;
1695  try
1696  {
1697  savingGroups =
1698  document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-bumpGroupVersions").checked;
1699 
1700  activatingSavedGroups =
1701  document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-activateBumpedGroupVersions").checked;
1702  }
1703  catch(err)
1704  {
1705  savingGroups = !doNotSaveAffectedGroups;
1706  activatingSavedGroups = !doNotActivateAffectedGroups;
1707  doRequestAffectedGroups = true; //popup doesn't exist, so need to do the work on own
1708  }
1709 
1710  if(!savingGroups) //then no need to proceed. exit!
1711  {
1712  //kill popup dialog
1713  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "");
1714  if(el && !doNotKillPopUpEl) el.parentNode.removeChild(el);
1715  if(responseHandler) responseHandler(savedTables,savedGroups,savedAliases);
1716  return;
1717  }
1718 
1719  //identify root group name/key
1720  //var rootGroupEl = document.getElementById("treeView-ConfigGroupLink");
1721  //var rootGroupName = rootGroupEl.childNodes[0].textContent;
1722  //var rootGroupKey = rootGroupEl.childNodes[1].textContent;
1723 
1724  //Debug.log("rootGroup = " + rootGroupName + "(" + rootGroupKey + ")");
1725  Debug.log("On to saving groups");
1726 
1727  numberOfRequests = 0; //re-use
1728  numberOfReturns = 0; //re-use
1729  allRequestsSent = false; //re-use
1730 
1731  var affectedGroupNames = []; //to be populated for use by alias setting
1732  var affectedGroupComments = []; //to be populated for use by alias setting
1733  var affectedGroupTableMap = []; //to be populated for use by alias setting
1734 
1735  var affectedGroupKeys = []; //to be populated after group save for use by alias setting
1736 
1737  if(doRequestAffectedGroups)
1738  {
1739  //replace temporary versions with new persistent versions
1740  var modifiedTablesListStr = ""; //csv table, temporay version,...
1741  var modTblCount = 0;
1742  var modTblStr = "";
1743  for(var j=0;j<modifiedTables.length;++j)
1744  if((modifiedTables[j].tableVersion|0) < 0)
1745  {
1746  if(modTblCount++)
1747  modTblStr += ",";
1748  modTblStr += modifiedTables[j].tableName;
1749 
1750  if(modifiedTablesListStr.length)
1751  modifiedTablesListStr += ",";
1752  modifiedTablesListStr += modifiedTables[j].tableName;
1753  modifiedTablesListStr += ",";
1754  modifiedTablesListStr += modifiedTables[j].tableVersion;
1755  }
1756 
1757  //get affected groups
1758  // and save member map to hidden div for Save action
1760  DesktopContent.XMLHttpRequest("Request?RequestType=getAffectedActiveGroups" +
1761  "&groupName=" +
1762  "&groupKey=-1", //end get params
1763  "&modifiedTables=" + modifiedTablesListStr, //end post params
1764  function(req)
1765  {
1766  var err = DesktopContent.getXMLValue(req,"Error");
1767  if(err)
1768  {
1769  Debug.log(err,Debug.HIGH_PRIORITY);
1770  el.innerHTML = str;
1771  if(responseHandler) responseHandler(savedTables,savedGroups,savedAliases);
1772  return;
1773  }
1774  //for each affected group
1775  // put csv: name,key,memberName,memberVersion...
1776  var groups = req.responseXML.getElementsByTagName("AffectedActiveGroup");
1777  var memberNames, memberVersions;
1778  var xmlGroupName;
1779  modTblStr = ""; //re-use
1780  for(var i=0;i<groups.length;++i)
1781  {
1782  affectedGroupNames.push( DesktopContent.getXMLValue(groups[i],"GroupName"));
1783  affectedGroupComments.push(decodeURIComponent(DesktopContent.getXMLValue(groups[i],"GroupComment")));
1784 
1785  memberNames = groups[i].getElementsByTagName("MemberName");
1786  memberVersions = groups[i].getElementsByTagName("MemberVersion");
1787 
1788  Debug.log("memberNames.length " + memberNames.length);
1789 
1790  //build member table map
1791  affectedGroupTableMap[i] = "tableList=";
1792  var memberVersion, memberName;
1793  for(var j=0;j<memberNames.length;++j)
1794  {
1795  memberVersion = DesktopContent.getXMLValue(memberVersions[j])|0; //force integer
1796  memberName = DesktopContent.getXMLValue(memberNames[j]);
1797  if(memberVersion < -1) //there should be a new modified version
1798  {
1799  Debug.log("affectedArr " + memberName + "-v" + memberVersion);
1800  //find version
1801  for(var k=0;k<savedTables.length;++k)
1802  if(memberName == savedTables[k].tableName)
1803  {
1804  Debug.log("found " + savedTables[k].tableName + "-v" +
1805  savedTables[k].tableVersion);
1806  affectedGroupTableMap[i] += memberName + "," +
1807  savedTables[k].tableVersion + ",";
1808  break;
1809  }
1810  }
1811  else
1812  affectedGroupTableMap[i] += memberName +
1813  "," + memberVersion + ",";
1814 
1815  //check tables to add and remove if already in the list
1816  for(var t=0;tablesToAdd && t<tablesToAdd.length;++t)
1817  if(memberName == tablesToAdd[t].tableName)
1818  {
1819  Debug.log("Removing table to add '" +
1820  memberName + "', already in group.");
1821  tablesToAdd.splice(t,1); //remove 1 element at position t
1822  console.log("Now tablesToAdd",tablesToAdd);
1823  break;
1824  }
1825  } //end primary member loop
1826 
1827  //for the last group, add tables (-1 means mockup)
1828  if(i==groups.length-1 && tablesToAdd)
1829  {
1830  for(var j=0;j<tablesToAdd.length;++j)
1831  affectedGroupTableMap[i] += tablesToAdd[j].tableName +
1832  "," + tablesToAdd[j].tableVersion + ",";
1833  } //end mockup table add
1834  }
1835 
1836  localHandleSavingAffectedGroups();
1837  },0,0,true //reqParam, progressHandler, callHandlerOnErr
1838  ); //end of getAffectedActiveGroups req
1839  }
1840  else
1841  {
1842  var affectedGroupEls =
1843  document.getElementsByClassName(ConfigurationAPI._POP_UP_DIALOG_ID +
1844  "-affectedGroups");
1845  var affectedGroupCommentEls =
1846  document.getElementsByClassName(ConfigurationAPI._POP_UP_DIALOG_ID +
1847  "-groupComment-cache");
1848 
1849  // for each affected group element
1850  for(var i=0;i<affectedGroupEls.length;++i)
1851  {
1852  Debug.log(affectedGroupEls[i].textContent);
1853  Debug.log("group comment: " + affectedGroupCommentEls[i].textContent);
1854 
1855  var affectedArr = affectedGroupEls[i].textContent.split(',');
1856 
1857  affectedGroupComments.push(affectedGroupCommentEls[i].textContent);
1858  affectedGroupNames.push(affectedArr[0]);
1859 
1860  //build member table map
1861  affectedGroupTableMap[i] = "tableList=";
1862  //member map starts after group name/key (i.e. [2])
1863  for(var a=2;a<affectedArr.length;a+=2)
1864  if((affectedArr[a+1]|0) < -1) //there should be a new modified version
1865  {
1866  Debug.log("affectedArr " + affectedArr[a] + "-v" + affectedArr[a+1]);
1867  //find version
1868  for(var k=0;k<savedTables.length;++k)
1869  if(affectedArr[a] == savedTables[k].tableName)
1870  {
1871  Debug.log("found " + savedTables[k].tableName + "-v" +
1872  savedTables[k].tableVersion);
1873  affectedGroupTableMap[i] += affectedArr[a] + "," +
1874  savedTables[k].tableVersion + ",";
1875  break;
1876  }
1877  }
1878  else //use existing version
1879  affectedGroupTableMap[i] += affectedArr[a] + "," + affectedArr[a+1] + ",";
1880  }
1881 
1882  localHandleSavingAffectedGroups();
1883  }
1884 
1885  //::::::::::::::::::::::::::::::::::::::::::
1886  //localHandleSavingAffectedGroups ~~
1887  function localHandleSavingAffectedGroups()
1888  {
1889  // for each affected group
1890  for(var i=0;i<affectedGroupNames.length;++i)
1891  {
1892  reqStr = ""; //reuse
1893  reqStr = "Request?RequestType=saveNewTableGroup" +
1894  "&groupName=" + affectedGroupNames[i] +
1895  "&allowDuplicates=0" +
1896  "&lookForEquivalent=1" +
1897  "&ignoreWarnings=" + (doNotIgnoreWarnings?0:1) +
1898  "&groupComment=" + encodeURIComponent(affectedGroupComments[i]);
1899  Debug.log(reqStr);
1900  Debug.log(affectedGroupTableMap[i]);
1901 
1902  ++numberOfRequests;
1904  DesktopContent.XMLHttpRequest(reqStr, affectedGroupTableMap[i],
1905  function(req,affectedGroupIndex)
1906  {
1907 
1908  var attemptedNewGroupName = DesktopContent.getXMLValue(req,"AttemptedNewGroupName");
1909  var treeErr = DesktopContent.getXMLValue(req,"TreeErrors");
1910  if(treeErr)
1911  {
1912  Debug.log(treeErr,Debug.HIGH_PRIORITY);
1913  Debug.log("There were problems identified in the tree view of the " +
1914  "attempted new group '" +
1915  attemptedNewGroupName +
1916  "'.\nThe new group was not created.\n" +
1917  "(Note: Other tables and groups may have been successfully created, " +
1918  "and would have success indications below this error info)\n\n" +
1919  "You can save the group anyway (if you think it is a good idea) by clicking " +
1920  "the button in the pop-up dialog " +
1921  "'<u>Save Groups with Warnings Ignored</u>.' " +
1922  "\n\nOtherwise, you can hit '<u>Cancel</u>.' and fix the tree. " +
1923  "Below you will find the description of the problem:",
1924  Debug.HIGH_PRIORITY);
1925 
1926  //change dialog save button
1927  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-submitButton");
1928  if(el)
1929  {
1930  el.onmouseup = function() {
1931  Debug.log("Submit mouseup");
1932  this.disabled = true;
1933  ConfigurationAPI.handleGroupCommentToggle(0,1); //force cache of group comment
1934  ConfigurationAPI.handlePopUpHeightToggle(h,gh);
1935 
1936  var savingGroups =
1937  document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID +
1938  "-bumpGroupVersions").checked;
1939  var activatingSavedGroups =
1940  document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID +
1941  "-activateBumpedGroupVersions").checked;
1942 
1943  ConfigurationAPI.saveModifiedTables(modifiedTables,responseHandler,
1944  false, //doNotIgnoreWarnings
1945  doNotSaveAffectedGroups,
1946  doNotActivateAffectedGroups,doNotSaveAliases
1947  );
1948  };
1949  el.value = "Save Groups with Warnings Ignored";
1950  el.disabled = false;
1951  }
1952  return;
1953  }
1954 
1955  var err = DesktopContent.getXMLValue(req,"Error");
1956  if(err)
1957  {
1958  Debug.log(err,Debug.HIGH_PRIORITY);
1959 
1960  //kill popup dialog
1961  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "");
1962  if(el && !doNotKillPopUpEl) el.parentNode.removeChild(el);
1963  if(responseHandler) responseHandler(savedTables,savedGroups,savedAliases);
1964  return;
1965  }
1966 
1967  ++numberOfReturns;
1968 
1969  var newGroupKey = DesktopContent.getXMLValue(req,"TableGroupKey");
1970  affectedGroupKeys.push(newGroupKey);
1971 
1972  {
1973  var obj = {};
1974  obj.groupName = attemptedNewGroupName;
1975  obj.groupKey = newGroupKey;
1976  obj.groupComment = affectedGroupComments[affectedGroupIndex];
1977  savedGroups.push(obj);
1978  }
1979 
1980 
1981  var foundEquivalentKey = DesktopContent.getXMLValue(req,"foundEquivalentKey");
1982  if(foundEquivalentKey)
1983  Debug.log("Using existing group '" + attemptedNewGroupName +
1984  " (" + newGroupKey + ")'", Debug.INFO_PRIORITY);
1985  else
1986  Debug.log("Successfully created new group '" + attemptedNewGroupName +
1987  " (" + newGroupKey + ")'", Debug.INFO_PRIORITY);
1988 
1989  //need to modify root group name if changed
1990 
1991  //activate if option was selected
1992  if(activatingSavedGroups) //allow to happen in parallel
1993  ConfigurationAPI.activateGroup(attemptedNewGroupName,newGroupKey,
1994  doNotIgnoreGroupActivationWarnings?false:true /* ignoreWarnings */);
1995 
1996 
1997  if(allRequestsSent &&
1998  numberOfReturns == numberOfRequests)
1999  {
2000  Debug.log("Done with group saving.");
2001 
2002  Debug.log("Moving on to Alias creation...");
2003 
2004  //check each alias checkbox
2005  // for each alias that is checked
2006  // set alias for new group
2007  //if any aliases modified, save and activate backbone
2008 
2009  //get checkboxes
2010  var setAliasCheckboxes;
2011 
2012  var groupAlias, groupName, groupKey;
2013  var setAliasCheckboxIndex = -1;
2014  var groupAliasName, groupAliasVersion;
2015 
2016  var affectedGroupAliases = [];
2017 
2018  //in order to set alias, we need:
2019  // groupAlias
2020  // groupName
2021  // groupKey
2022 
2023  //for each set alias checkbox that is checked
2024  // modify the active group alias table one after the other
2025  //if no checkboxes, then take queue from input parameters
2026  // (and request group aliases)
2027  try
2028  {
2029  setAliasCheckboxes =
2030  document.getElementsByClassName("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-setGroupAlias");
2031  //Not sure if the above may throw under certain cicumstances with popup,
2032  // so keeping above and below for now
2033  if(setAliasCheckboxes.length != affectedGroupNames.length)
2034  throw("no popup"); //if no popup, throw
2035 
2036 
2037  localNextAliasHandler();
2038  Debug.log("Aliases set in motion");
2039  }
2040  catch(err)
2041  {
2042  //no popup, so take from input and set for all affected groups
2043  setAliasCheckboxes = [];
2044  for(var i in affectedGroupNames)
2045  setAliasCheckboxes.push({"checked" : ((!doNotSaveAliases)?1:0) });
2046 
2047  //get group aliases and then set alias setting in motion
2048  // -- i.e. build affectedGroupAliases
2049  // -- (already have affectedGroupNames and affectedGroupKeys)
2050  // -- also, modify setAliasCheckboxes as unchecked if no default alias can be found
2051 
2052 
2053  //get existing group aliases
2055  DesktopContent.XMLHttpRequest("Request?RequestType=getGroupAliases" +
2056  "",
2057  "",
2058  function(req)
2059  {
2060  var err = DesktopContent.getXMLValue(req,"Error");
2061  if(err)
2062  {
2063  Debug.log(err,Debug.HIGH_PRIORITY);
2064  if(responseHandler) responseHandler(savedTables,savedGroups,savedAliases);
2065  return;
2066  }
2067 
2068  var aliases = req.responseXML.getElementsByTagName("GroupAlias");
2069  var aliasGroupNames = req.responseXML.getElementsByTagName("GroupName");
2070  var aliasGroupKeys = req.responseXML.getElementsByTagName("GroupKey");
2071 
2072  //for each affected group
2073  // identify if already aliased and choose that as default option
2074 
2075  var alias, aliasGroupName, aliasGroupKey;
2076  var groupName, groupKey;
2077  var groupOptionIndex = []; //keep distance and index of option for each group, or -1 if none
2078  for(var i=0;i<affectedGroupNames.length;++i)
2079  {
2080  groupOptionIndex.push([-1,0]); //index and distance
2081 
2082  groupName = affectedGroupNames[i];
2083  groupKey = affectedGroupKeys[i];
2084 
2085  //find alias
2086  for(var j=0;j<aliasGroupNames.length;++j)
2087  {
2088  alias = DesktopContent.getXMLValue(aliases[j]);
2089  aliasGroupName = DesktopContent.getXMLValue(aliasGroupNames[j]);
2090  aliasGroupKey = DesktopContent.getXMLValue(aliasGroupKeys[j]);
2091 
2092  //Debug.log("compare " + aliasGroupName + ":" +
2093  // aliasGroupKey);
2094 
2095  //consider any alias with same groupName
2096  if(aliasGroupName == groupName)
2097  {
2098  if(groupOptionIndex[i][0] == -1 || //take best match
2099  Math.abs(groupKey - aliasGroupKey) < groupOptionIndex[i][1])
2100  {
2101  Debug.log("found alias");
2102  groupOptionIndex[i][0] = j; //index
2103  groupOptionIndex[i][1] = Math.abs(groupKey - aliasGroupKey); //distance
2104  }
2105  }
2106  }
2107 
2108  //modify setAliasCheckboxes as unchecked if no default alias can be found
2109  setAliasCheckboxes[i].checked = (groupOptionIndex[i][0] >= 0?1:0);
2110 
2111  affectedGroupAliases.push(groupOptionIndex[i][0] >= 0?
2112  DesktopContent.getXMLValue(aliases[groupOptionIndex[i][0]]):"");
2113  } //end affected groups loop, choosing alias match
2114 
2115 
2116  localNextAliasHandler();
2117  Debug.log("Aliases set in motion");
2118 
2119  },0,0,true //reqParam, progressHandler, callHandlerOnErr
2120  ); //end of getGroupAliases handler
2121 
2122  } //end of catch scope
2123 
2124 
2125 
2126  //localNextAliasHandler
2127  // uses setAliasCheckboxIndex to iterate through setAliasCheckboxes
2128  // and send the next request to modify the activegroupAlias table
2129  // sequentially
2130  function localNextAliasHandler(retParams)
2131  {
2132  //first time there is no setAliasCheckboxIndex == -1
2133  if(setAliasCheckboxIndex >= 0)
2134  {
2135  if(retParams)
2136  {
2137  if(retParams.newGroupCreated)
2138  {
2139  Debug.log("Successfully modified the active Backbone group " +
2140  " to set the System Alias '" + groupAlias + "' to " +
2141  " refer to the current group '" + groupName +
2142  " (" + groupKey + ").'" +
2143  "\n\n" +
2144  "Backbone group '" + retParams.groupName + " (" +
2145  retParams.groupKey + ")' was created and activated.",
2146  Debug.INFO_PRIORITY);
2147 
2148  {
2149  var obj = {};
2150  obj.groupName = groupName;
2151  obj.groupKey = groupKey;
2152  obj.groupAlias = groupAlias;
2153  savedAliases.push(obj);
2154  }
2155  }
2156  else
2157  Debug.log("Success, but no need to create a new Backbone group. " +
2158  "An existing Backbone group " +
2159  " already has the System Alias '" + groupAlias + "' " +
2160  " referring to the current group '" + groupName +
2161  " (" + groupKey + ").'" +
2162  "\n\n" +
2163  "Backbone group '" + retParams.groupName + " (" +
2164  retParams.groupKey + ")' was activated.",
2165  Debug.INFO_PRIORITY);
2166  }
2167  else
2168  {
2169  Debug.log("Process interrupted. Failed to modify the currently active Backbone!",Debug.HIGH_PRIORITY);
2170 
2171  //kill popup dialog
2172  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "");
2173  if(el && !doNotKillPopUpEl) el.parentNode.removeChild(el);
2174  if(responseHandler) responseHandler(savedTables,savedGroups,savedAliases);
2175  return;
2176  }
2177 
2178  ++setAliasCheckboxIndex; //req back, so ready for next index
2179  }
2180  else
2181  setAliasCheckboxIndex = 0; //ready for first checkbox
2182 
2183  //get next affected group index
2184  while(setAliasCheckboxIndex < setAliasCheckboxes.length &&
2185  !setAliasCheckboxes[setAliasCheckboxIndex].checked)
2186  Debug.log("Skipping checkbox " + (++setAliasCheckboxIndex));
2187 
2188  if(setAliasCheckboxIndex >= setAliasCheckboxes.length)
2189  {
2190  Debug.log("Done with alias checkboxes ");
2191 
2192  if(!retParams)//req)
2193  {
2194  Debug.log("No System Aliases were changed, so Backbone was not modified. Done.");
2195 
2196  //kill popup dialog
2197  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "");
2198  if(el && !doNotKillPopUpEl) el.parentNode.removeChild(el);
2199  if(responseHandler) responseHandler(savedTables,savedGroups,savedAliases);
2200  return;
2201  }
2202 
2203  Debug.log("Saving and activating Backbone done.");
2204 
2205  //kill popup dialog
2206  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "");
2207  if(el && !doNotKillPopUpEl) el.parentNode.removeChild(el);
2208  if(responseHandler) responseHandler(savedTables,savedGroups,savedAliases);
2209  return;
2210  }
2211 
2212  //get next alias
2213  try
2214  {
2215  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-editAliasSelect-" +
2216  setAliasCheckboxIndex);
2217  if(el.style.display == "none")
2218  {
2219  //get value from text box
2220  el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "-editAliasTextBox-" +
2221  setAliasCheckboxIndex);
2222  }
2223  groupAlias = el.value;
2224  }
2225  catch(err)
2226  {
2227  //if exception, then no popup, and get alias from
2228  // affectedGroupAliases
2229  groupAlias = affectedGroupAliases[setAliasCheckboxIndex];
2230  }
2231 
2232  groupName = affectedGroupNames[setAliasCheckboxIndex];
2233  groupKey = affectedGroupKeys[setAliasCheckboxIndex];
2234 
2235  Debug.log("groupAlias = " + groupAlias);
2236  Debug.log("groupName = " + groupName);
2237  Debug.log("groupKey = " + groupKey);
2238 
2239  ConfigurationAPI.setGroupAliasInActiveBackbone(groupAlias,groupName,groupKey,
2240  "SaveWiz",
2241  localNextAliasHandler,
2242  true); //request return parameters
2243  }
2244 
2245  } // end of if statement to check if done with group saving
2246 
2247  },i,0,true //reqParam, progressHandler, callHandlerOnErr
2248  ); //end save new group request
2249  } //end affected group for loop
2250 
2251  allRequestsSent = true;
2252  if(numberOfRequests == 0) //no groups to save
2253  {
2254  //this could happen if editing tables with no current active groups
2255  Debug.log("There were no groups to save!", Debug.INFO_PRIORITY);
2256 
2257  //kill popup dialog
2258  var el = document.getElementById("" + ConfigurationAPI._POP_UP_DIALOG_ID + "");
2259  if(el && !doNotKillPopUpEl) el.parentNode.removeChild(el);
2260  }
2261  } //end localHandleSavingAffectedGroups
2262  } //end localHandleAffectedGroups
2263 
2264 
2265  //go through each modified table
2266  // if modified table
2267  // save new version
2268  // update return object based on result
2269  for(var j=0;j<modifiedTables.length;++j)
2270  if((modifiedTables[j].tableVersion|0) < 0) //for each modified and mockup table
2271  {
2272  var reqStr = "Request?RequestType=saveSpecificTable" +
2273  "&dataOffset=0&chunkSize=0" +
2274  "&tableName=" + modifiedTables[j].tableName +
2275  "&version="+modifiedTables[j].tableVersion +
2276  "&temporary=0" +
2277  "&tableComment=" +
2278  encodeURIComponent(modifiedTables[j].tableComment?modifiedTables[j].tableComment:"") +
2279  "&sourceTableAsIs=1" +
2280  "&lookForEquivalent=1"; //accept equivalent tables! (ignoring author and timestamp)
2281  Debug.log(reqStr);
2282 
2283  ++numberOfRequests;
2284 
2285  // save new version
2287  DesktopContent.XMLHttpRequest(reqStr, "",
2288  function(req,modifiedTableIndex)
2289  {
2290  var err = DesktopContent.getXMLValue(req,"Error");
2291  if(err)
2292  {
2293  Debug.log(err,Debug.HIGH_PRIORITY);
2294 
2295  //kill popup dialog
2296  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
2297  //do not kill on error --- if(el && !doNotKillPopUpEl) el.parentNode.removeChild(el);
2298  if(responseHandler) responseHandler(savedTables,savedGroups,savedAliases);
2299  return;
2300  }
2301 
2302  var tableName = DesktopContent.getXMLValue(req,"savedName");
2303  var version = DesktopContent.getXMLValue(req,"savedVersion");
2304  var foundEquivalentVersion = DesktopContent.getXMLValue(req,"foundEquivalentVersion") | 0;
2305 
2306  if(foundEquivalentVersion)
2307  Debug.log("Using existing table '" + tableName + "-v" +
2308  version + "'",Debug.INFO_PRIORITY);
2309  else
2310  Debug.log("Successfully created new table '" + tableName + "-v" +
2311  version + "'",Debug.INFO_PRIORITY);
2312 
2313  //update saved table version based on result
2314  {
2315  var obj = {};
2316  obj.tableName = tableName;
2317  obj.tableVersion = version;
2318  obj.tableComment = modifiedTables[modifiedTableIndex].tableComment;
2319  savedTables.push(obj);
2320  }
2321 
2322  ++numberOfReturns;
2323 
2324  if(allRequestsSent &&
2325  numberOfReturns == numberOfRequests)
2326  {
2327  if(!doNotSaveAffectedGroups)
2328  localHandleAffectedGroups();
2329  }
2330  },j,0,true //reqParam, progressHandler, callHandlerOnErr
2331  ); //end save new table request
2332  } //end modified table for loop
2333 
2334  allRequestsSent = true;
2335  if(numberOfRequests == 0) //no requests were sent, so go on to affected groups
2336  {
2337  //localHandleAffectedGroups();
2338  Debug.log("No tables were modified. Should be impossible to get here.", Debug.HIGH_PRIORITY);
2339  }
2340 } //end saveModifiedTables()
2341 
2342 
2343 //=====================================================================================
2344 //activateGroup ~~
2345 ConfigurationAPI.activateGroup = function(groupName, groupKey,
2346  ignoreWarnings, doneHandler)
2347 {
2348  DesktopContent.XMLHttpRequest("Request?RequestType=activateTableGroup" +
2349  "&groupName=" + groupName +
2350  "&groupKey=" + groupKey +
2351  "&ignoreWarnings=" + (ignoreWarnings?"1":"0") +
2352  "", //end get data
2353  "", //end post data
2354  function(req)
2355  {
2356 
2357  var err = DesktopContent.getXMLValue(req,"Error");
2358  if(err)
2359  {
2360  Debug.log(err,Debug.HIGH_PRIORITY);
2361 
2362  //Debug.log(_OTS_RELAUNCH_STR,Debug.INFO_PRIORITY);
2363 
2364  //show activate with warnings link
2365  var str = "";
2366 
2367  //add ignore warnings Activate link
2368  str += " <a href='#' onclick='javascript:ConfigurationAPI.activateGroup(\"" +
2369  groupName +
2370  "\",\"" + groupKey + "\",true); return false;'>"; //ignore warnings
2371  str += "Activate " +
2372  groupName + "(" + groupKey + ") w/warnings ignored</a>";
2373 
2374  Debug.log("If you are are sure it is a good idea you can try to " +
2375  "activate the group with warnings ignored: " +
2376  str,Debug.HIGH_PRIORITY);
2377  return;
2378  }
2379 
2380  if(doneHandler) doneHandler();
2381  },
2382  true, 0 , true); //reqIndex, progressHandler, callHandlerOnErr
2383 } //end activateGroup()
2384 
2385 //=====================================================================================
2386 //setGroupAliasInActiveBackbone ~~
2387 // Used to set a group alias.
2388 // This function will activate the resulting backbone group and
2389 // call a done handler
2390 //
2391 // if doReturnParms
2392 // then the handler is called with an object
2393 // describing the new backbone group object:
2394 // retParams.newGroupCreated //true if successfully created
2395 // retParams.groupName //backbone group name
2396 // retParams.groupKey //backbone group key
2397 //
2398 ConfigurationAPI.setGroupAliasInActiveBackbone = function(groupAlias,groupName,groupKey,
2399  newBackboneNameAdd,doneHandler,doReturnParams)
2400 {
2401  Debug.log("setGroupAliasInActiveBackbone groupAlias=" + groupAlias);
2402  Debug.log("setGroupAliasInActiveBackbone groupName=" + groupName);
2403  Debug.log("setGroupAliasInActiveBackbone groupKey=" + groupKey);
2404 
2405  if(!groupAlias || groupAlias.trim() == "")
2406  {
2407  Debug.log("Process interrupted. Invalid empty alias given!",Debug.HIGH_PRIORITY);
2408  if(doneHandler) doneHandler(); //error so call done handler
2409  return;
2410  }
2411 
2412  if(!groupName || groupName.trim() == "" || !groupKey || groupKey.trim() == "")
2413  {
2414  Debug.log("Process interrupted. Invalid group name and key given!",Debug.HIGH_PRIORITY);
2415  if(doneHandler) doneHandler(); //error so call done handler
2416  return;
2417  }
2418 
2419  if(!newBackboneNameAdd || newBackboneNameAdd == "")
2420  newBackboneNameAdd = "Wiz";
2421  newBackboneNameAdd += "Backbone";
2422  Debug.log("setGroupAliasInActiveBackbone newBackboneNameAdd=" + newBackboneNameAdd);
2423 
2424  DesktopContent.XMLHttpRequest("Request?RequestType=setGroupAliasInActiveBackbone" +
2425  "&groupAlias=" + groupAlias +
2426  "&groupName=" + groupName +
2427  "&groupKey=" + groupKey, "",
2428  ConfigurationAPI.newWizBackboneMemberHandler,
2429  [("GroupAlias" + newBackboneNameAdd),doneHandler,doReturnParams],
2430  0,true //progressHandler, callHandlerOnErr
2431  );
2432 }
2433 
2434 //=====================================================================================
2435 //newWizBackboneMemberHandler
2436 // Used to handle the response from modifying a member of the backbone.
2437 // This handler will activate the resulting backbone group and
2438 // call a done handler.
2439 //
2440 // params = [newBackboneGroupName, doneHandler, doReturnParams]
2441 ConfigurationAPI.newWizBackboneMemberHandler = function(req,params)
2442 {
2443  var err = DesktopContent.getXMLValue(req,"Error");
2444  if(err)
2445  {
2446  Debug.log(err,Debug.HIGH_PRIORITY);
2447  Debug.log("Process interrupted. Failed to modify the currently active Backbone!",Debug.HIGH_PRIORITY);
2448 
2449  if(params[1])
2450  params[1](); //error so call done handler
2451  return;
2452  }
2453 
2454  var groupAliasName = DesktopContent.getXMLValue(req,"savedName");
2455  var groupAliasVersion = DesktopContent.getXMLValue(req,"savedVersion");
2456 
2457  Debug.log("groupAliasName=" + groupAliasName);
2458  Debug.log("groupAliasVersion=" + groupAliasVersion);
2459 
2460  var configNames = req.responseXML.getElementsByTagName("oldBackboneName");
2461  var tableVersions = req.responseXML.getElementsByTagName("oldBackboneVersion");
2462 
2463  //make a new backbone with old versions of everything except Group Alias
2464  var tableMap = "tableList=";
2465  var name;
2466  for(var i=0;i<configNames.length;++i)
2467  {
2468  name = configNames[i].getAttribute("value");
2469 
2470  if(name == groupAliasName)
2471  {
2472  tableMap += name + "," +
2473  groupAliasVersion + ",";
2474  continue;
2475  }
2476  //else use old member
2477  tableMap += name + "," +
2478  tableVersions[i].getAttribute("value") + ",";
2479  }
2480 
2481  console.log("backbone tableMap",tableMap);
2482 
2483  ConfigurationAPI.saveGroupAndActivate(params[0],tableMap,params[1],params[2],
2484  true /*lookForEquivalent*/);
2485 } // end ConfigurationAPI.newWizBackboneMemberHandler()
2486 
2487 //=====================================================================================
2488 //saveGroupAndActivate
2489 ConfigurationAPI.saveGroupAndActivate = function(groupName,tableMap,
2490  doneHandler,doReturnParams,
2491  lookForEquivalent)
2492 {
2493  DesktopContent.XMLHttpRequest("Request?RequestType=saveNewTableGroup&groupName=" +
2494  groupName +
2495  "&allowDuplicates=" + (lookForEquivalent?"0":"1") +
2496  "&lookForEquivalent=" + (lookForEquivalent?"1":"0") +
2497  "", //end get data
2498  tableMap, //end post data
2499  function(req)
2500  {
2501  var err = DesktopContent.getXMLValue(req,"Error");
2502  var name = DesktopContent.getXMLValue(req,"TableGroupName");
2503  var key = DesktopContent.getXMLValue(req,"TableGroupKey");
2504  var newGroupCreated = true;
2505  if(err)
2506  {
2507  if(!name || !key)
2508  {
2509  Debug.log(err,Debug.HIGH_PRIORITY);
2510  Debug.log("Process interrupted. Failed to create a new group!" +
2511  " Please see details below.",
2512  Debug.HIGH_PRIORITY);
2513 
2514  if(doneHandler) doneHandler(); //error so call done handler
2515  return;
2516  }
2517  else
2518  {
2519  Debug.log(err,Debug.WARN_PRIORITY);
2520  Debug.log("Process interrupted. Failed to create a new group!" +
2521  " (Likely the currently active group already represents what is being requested)\n\n" +
2522  "Going on with existing backbone group, name=" + name + " & key=" + key,
2523  Debug.WARN_PRIORITY);
2524  newGroupCreated = false;
2525  }
2526  }
2527 
2528  //now activate the new group
2529 
2530  DesktopContent.XMLHttpRequest("Request?RequestType=activateTableGroup" +
2531  "&groupName=" + name +
2532  "&groupKey=" + key, "",
2533  function(req)
2534  {
2535  try
2536  {
2537  activateSystemConfigHandler(req);
2538  }
2539  catch(err) {} //ignore error, this is only used by ConfigurationGUI (or anyone implementing this extra handler)
2540 
2541  if(doneHandler)
2542  {
2543  //done so call done handler (and indicate success)
2544  if(!doReturnParams)
2545  doneHandler(); //done so call done handler
2546  else
2547  {
2548  var retParams = {
2549  "groupName" : name,
2550  "groupKey" : key,
2551  "newGroupCreated" : newGroupCreated
2552  }
2553  doneHandler(retParams); //(and indicate success)
2554  }
2555  }
2556  }); //end of activate new backbone handler
2557 
2558  },0,0,true //reqParam, progressHandler, callHandlerOnErr
2559  ); //end of backbone saveNewTableGroup handler
2560 } //end ConfigurationAPI.saveGroupAndActivate
2561 
2562 //=====================================================================================
2563 //getGroupTypeMemberNames
2564 // groupType can be
2565 // Backbone
2566 // Context
2567 // Iterate
2568 //
2569 // on failure, return empty array
2570 // on success, return array of members
2571 ConfigurationAPI.getGroupTypeMemberNames = function(groupType,responseHandler)
2572 {
2573  DesktopContent.XMLHttpRequest("Request?RequestType=get" + groupType + "MemberNames", "",
2574  function (req)
2575  {
2576  var retArr = [];
2577 
2578  var err = DesktopContent.getXMLValue(req,"Error");
2579  if(err)
2580  {
2581  Debug.log(err,Debug.HIGH_PRIORITY);
2582  if(responseHandler) responseHandler(retArr);
2583  return;
2584  }
2585  var memberNames = req.responseXML.getElementsByTagName(groupType + "Member");
2586 
2587  for(var i=0;i<memberNames.length;++i)
2588  retArr[i] = memberNames[i].getAttribute("value");
2589 
2590  Debug.log("Members found for group type " + groupType + " = " + retArr.length);
2591  if(responseHandler) responseHandler(retArr);
2592 
2593  }, //end request handler
2594  0,0,true //reqParam, progressHandler, callHandlerOnErr
2595  ); //end request
2596 
2597 } //end getGroupTypeMemberNames()
2598 
2599 
2600 //=====================================================================================
2601 //bitMapDialog ~~
2602 // shows bitmap dialog at full window size (minus margins)
2603 // on ok, calls <okHandler> with finalBitMapValue parameter
2604 //
2605 // <bitMapParams> is an array olf size 6:
2606 // rows,cols,cellFieldSize,minColor,midColor,maxColor
2607 ConfigurationAPI.bitMapDialog = function(fieldName,bitMapParams,initBitMapValue,okHandler,cancelHandler)
2608 {
2609  Debug.log("ConfigurationAPI bitMapDialog");
2610 
2611  var str = "";
2612 
2613  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
2614  if(!el)
2615  {
2616  el = document.createElement("div");
2617  el.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID);
2618  }
2619  el.style.display = "none";
2620  el.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmapDialog");
2621 
2622  var padding = 10;
2623  var popSz;
2624 
2625 
2626  //create bit map dialog
2627  // - header at top with field name and parameters
2628  // - bitMap, w/mouseover and click and drag (same as windows sw)
2629  // - center vertically
2630  // - OK, CANCEL buttons at top right
2631  // - also upload download buttons
2632 
2633  // Input parameters must match Table Editor handling for bitmaps:
2634  // var _bitMapFieldsArr = [0 "Number of Rows",
2635  // 1 "Number of Columns",
2636  // 2 "Cell Bit-field Size",
2637  // 3 "Min-value Allowed",
2638  // 4 "Max-value Allowed",
2639  // 5 "Value step-size Allowed",
2640  // 6 "Display Aspect H:W",
2641  // 7 "Min-value Cell Color",
2642  // 8 "Mid-value Cell Color",
2643  // 9 "Max-value Cell Color",
2644  // 10 "Absolute Min-value Cell Color",
2645  // 11 "Absolute Max-value Cell Color",
2646  // 12 "Display Rows in Ascending Order",
2647  // 13 "Display Columns in Ascending Order",
2648  // 14 "Snake Double Rows",
2649  // 15 "Snake Double Columns"];
2650 
2651 
2652  //
2653  // Local functions:
2654  // localCreateCancelClickHandler()
2655  // localCreateOkClickHandler()
2656  // localCreateMouseHandler()
2657  // localGetRowCol(x,y)
2658  // el.onmousemove
2659  // el.onmousedown
2660  // el.onmouseup
2661  // el.oncontextmenu
2662  // localSetBitMap(r,c)
2663  // localValidateInputs()
2664  // localInitBitmapData()
2665  // localConvertGridToRowCol(r,c)
2666  // localConvertValueToRGBA(val)
2667  // localConvertFullGridToRowCol()
2668  // localConvertFullRowColToGrid(srcMatrix)
2669  // localCreateBitmap()
2670  // localCreateGridButtons()
2671  // localCreateHeader()
2672  // ConfigurationAPI.bitMapDialog.localUpdateScroll(i)
2673  // ConfigurationAPI.bitMapDialog.localUpdateTextInput(i)
2674  // ConfigurationAPI.bitMapDialog.localUpdateButtonInput(i,dir)
2675  // ConfigurationAPI.bitMapDialog.localDownloadCSV()
2676  // ConfigurationAPI.bitMapDialog.locaPopupUploadCSV()
2677  // ConfigurationAPI.bitMapDialog.locaUploadCSV()
2678  // localPaint()
2679  // localOptimizeAspectRatio()
2680 
2681  var rows, cols;
2682 
2683  var bitFieldSize;
2684  var bitMask;
2685 
2686  var minValue, maxValue;
2687  var midValue; //used for color calcs
2688  var stepValue;
2689 
2690  var forcedAspectH, forcedAspectW;
2691 
2692  var minValueColor, midValueColor, maxValueColor;
2693  var ceilValueColor, floorValueColor;
2694 
2695  var doDisplayRowsAscending, doDisplayColsAscending;
2696  var doSnakeColumns, doSnakeRows;
2697 
2698  //validate and load input params
2699  if(!localValidateInputs())
2700  {
2701  Debug.log("Input parameters array to the Bitmap Dialog was as follows:\n " +
2702  bitMapParams, Debug.HIGH_PRIORITY);
2703  Debug.log("Input parameters to the Bitmap Dialog are invalid. Aborting.", Debug.HIGH_PRIORITY);
2704  return cancelHandler();
2705  }
2706 
2707  //give 5 pixels extra for each digit necessary to label rows
2708  var numberDigitW = 8, numberDigitH = 12;
2709  var axisPaddingExtra = numberDigitW;
2710  function localCalcExtraAxisPadding() {
2711  var lrows = rows;
2712  while((lrows /= 10) > 1) axisPaddingExtra += numberDigitW;
2713  } localCalcExtraAxisPadding();
2714  var butttonSz = 20;
2715  var axisPaddingMargin = 5;
2716  var axisPadding = axisPaddingMargin + axisPaddingExtra + axisPaddingMargin + butttonSz + axisPaddingMargin;
2717  var bmpGridThickness = 1;
2718  var bmpBorderSize = 1;
2719 
2720 
2721  var hdr; //header element
2722  var hdrX;
2723  var hdrY;
2724  var hdrW;
2725  var hdrH;
2726 
2727  var bmp; //bitmap element
2728  var bmpGrid; //bitmap grid element
2729  var allRowBtns, allColBtns, allBtn;
2730  var rowLeftNums, rowRightNums, colTopNums, colBottomNums;
2731  var bmpCanvas, bmpContext; //used to generate 2D bitmap image src
2732  var bmpData; //bitmap data shown and returned. 2D array
2733  var bmpDataImage; //visual interpretation of bitmap data
2734  var bmpX;
2735  var bmpY;
2736  var bmpW;
2737  var bmpH;
2738  var bmpOverlay;
2739  var cursorInfo, hdrCursorInfo;
2740 
2741  var cellW;
2742  var cellH;
2743 
2744  var clickColors = []; //2 element array: 0=left-click, 1=right-click
2745  var clickValues = []; //2 element array: 0=left-click, 1=right-click
2746 
2747 
2748  localCreateHeader(); //create header element content
2749  localCreateBitmap(); //create bitmap element and data
2750  localCreateGridButtons(); //create all buttons
2751 
2752  localInitBitmapData(); //load bitmap data from input string
2753 
2754  localPaint();
2755  window.addEventListener("resize",localPaint);
2756 
2757  document.body.appendChild(el); //add element to body
2758  el.style.display = "block";
2759 
2760 
2761  //:::::::::::::::::::::::::::::::::::::::::
2762  //localCreateCancelClickHandler ~~
2763  // create cancel onclick handler
2764  function localCreateCancelClickHandler()
2765  {
2766  document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID +
2767  "-cancel").onclick = function(event) {
2768  Debug.log("Cancel click");
2769  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
2770  if(el) el.parentNode.removeChild(el); //close popup
2771  window.removeEventListener("resize",localPaint); //remove paint listener
2772  cancelHandler(); //empty array indicates nothing done
2773  return false;
2774  }; //end submit onmouseup handler
2775  } localCreateCancelClickHandler();
2776 
2777  //:::::::::::::::::::::::::::::::::::::::::
2778  //localCreateOkClickHandler ~~
2779  // create OK onclick handler
2780  function localCreateOkClickHandler()
2781  {
2782  var convertFunc = localConvertFullGridToRowCol;
2783  document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID +
2784  "-ok").onclick = function(event) {
2785  Debug.log("OK click");
2786  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
2787  if(el) el.parentNode.removeChild(el); //close popup
2788  window.removeEventListener("resize",localPaint); //remove paint listener
2789 
2790  var transGrid = convertFunc();
2791  var dataJsonStr = "[\n";
2792  for(var r=0;r<transGrid.length;++r)
2793  {
2794  if(r) dataJsonStr += ",\n";
2795  dataJsonStr += "\t[";
2796  for(var c=0;c<transGrid[0].length;++c)
2797  {
2798  if(c) dataJsonStr += ",";
2799  dataJsonStr += transGrid[r][c];
2800  }
2801  dataJsonStr += "]";
2802  }
2803  dataJsonStr += "\n]";
2804  okHandler(dataJsonStr); //empty array indicates nothing done
2805  return false;
2806  }; //end submit onmouseup handler
2807  } localCreateOkClickHandler();
2808 
2809  //:::::::::::::::::::::::::::::::::::::::::
2810  //localCreateMouseHandler ~~
2811  // create mouseover handler
2812  function localCreateMouseHandler()
2813  {
2814  var stopProp = false; //used to stop contextmenu propagation
2815  var rLast = -1, cLast = -1; //to stop redoing calculations in mouse over
2816  //-2 is special all buttons
2817  var buttonDown = -1; //0 - left, 1 - middle, 2 - right
2818 
2819  //::::::::
2820  //localGetRowCol ~~
2821  // returns -1 in r and c for nothing interesting
2822  // returns -2 for special all buttons
2823  // else returns r,c of cell identified by x,y
2824  function localGetRowCol(x,y) {
2825  x -= popSz.x + bmpX + 1;
2826  y -= popSz.y + bmpY + 1;
2827  var r = (y/cellH)|0;
2828  if(y < 0) r = -1; //handle negative fractions clipping to 0
2829  var c = (x/cellW)|0;
2830  if(x < 0) c = -1; //handle negative fractions clipping to 0
2831  var inRowBtnsX = (x >= - axisPaddingMargin - bmpBorderSize - butttonSz) &&
2832  (x <= - axisPaddingMargin - bmpBorderSize);
2833  var inColBtnsY = (y >= bmpH + axisPaddingMargin) &&
2834  (y <= bmpH + axisPaddingMargin + butttonSz + bmpBorderSize*2);
2835 
2836  //Debug.log("i x,y " + x + "," + y);
2837  //Debug.log("i r,c " + r + "," + c);
2838  //Debug.log("inRowBtnsX " + inRowBtnsX);
2839  //Debug.log("inColBtnsY " + inColBtnsY);
2840 
2841  //handle row buttons
2842  if(inRowBtnsX && r >= 0 && r < rows)
2843  return {"r":r, "c":-2};
2844  else if(inColBtnsY && c >= 0 && c < cols) //handle col buttons
2845  return {"r":-2, "c":c};
2846  else if(inRowBtnsX && inColBtnsY) //handle all button
2847  return {"r":-2, "c":-2};
2848  else if(r < 0 || c < 0 || r >= rows || c >= cols)
2849  return {"r":-1, "c":-1}; //is nothing
2850  return {"r":r, "c":c}; //else, is a cell
2851  } //end localGetRowCol()
2852 
2853  //::::::::
2854  //el.onmousemove ~~
2855  el.onmousemove = function(event) {
2856  var cell = localGetRowCol(event.pageX,event.pageY);
2857  var r = cell.r, c = cell.c;
2858 
2859  var cursorT = (event.pageX - popSz.x - bmpX);
2860  if(cursorT < 0) cursorT = 0;
2861  if(cursorT > bmpW) cursorT = bmpW;
2862 
2863  cursorInfo.style.left = (event.pageX - popSz.x +
2864  //(c >= cols/2?-cursorInfo.innerHTML.length*8-20:2)) +
2865  //smooth transition from left-most to right-most info position above cursor
2866  (cursorT)/bmpW*(-cursorInfo.innerHTML.length*8-20) + (bmpW-cursorT)/bmpW*(2))+
2867  "px";
2868  cursorInfo.style.top = (event.pageY - popSz.y - 35) + "px";
2869 
2870  //center header cursor info
2871  hdrCursorInfo.style.left = (bmpX + bmpW/2 +
2872  (-332)/2) + "px"; //hdrCursorInfo.style.width = "320px"; + padding*2 + border*2
2873  hdrCursorInfo.style.top = (bmpY - 45) + "px";
2874 
2875 
2876  if(rLast == r && cLast == c)
2877  return; //already done for this cell, so prevent excessive work
2878  rLast = r; cLast = c;
2879 
2880  if(r == -1 || c == -1) //handle no select case
2881  {
2882  //mouse off interesting things
2883  rLast = -1; cLast = -1;
2884  bmpOverlay.style.display = "none";
2885  cursorInfo.style.display = "none";
2886  hdrCursorInfo.style.display = "none";
2887  return;
2888  }
2889 
2890  cursorInfo.style.display = "block";
2891  //hdrCursorInfo.style.display = "block"; //removed from display.. could delete if unneeded?
2892 
2893  var transRC;
2894  var infoStr;
2895 
2896  //handle row buttons
2897  if(r != -2 && c == -2)
2898  {
2899  if(doSnakeColumns)
2900  transRC = localConvertGridToRowCol(r,
2901  doDisplayColsAscending?0:cols-1);
2902  else
2903  transRC = localConvertGridToRowCol(r,0);
2904 
2905  //make mouse over bitmap
2906  {
2907  bmpOverlay.src = ConfigurationAPI.getOnePixelPngData([216,188,188]);//canvas.toDataURL();
2908 
2909  bmpOverlay.style.left = (bmpX - axisPaddingMargin - bmpBorderSize - butttonSz) + "px";
2910  bmpOverlay.style.top = (bmpY + r*cellH - 1 + (r?bmpGridThickness+bmpBorderSize*2:0)) + "px";
2911  bmpOverlay.style.width = (butttonSz) + "px";
2912  bmpOverlay.style.height = (cellH - (r?bmpGridThickness+bmpBorderSize*2:0)) + "px";
2913  bmpOverlay.style.display = "block";
2914  }
2915 
2916  infoStr = "Set all pixels in row " + transRC[0] + ".";
2917  }
2918  else if(r == -2 && c != -2) //handle col buttons
2919  {
2920  if(doSnakeRows)
2921  transRC = localConvertGridToRowCol(
2922  doDisplayRowsAscending?0:rows-1,c);
2923  else
2924  transRC = localConvertGridToRowCol(0,c);
2925 
2926 
2927  //make mouse over bitmap
2928  {
2929  bmpOverlay.src = ConfigurationAPI.getOnePixelPngData([216,188,188]);//canvas.toDataURL();
2930 
2931  bmpOverlay.style.left = (bmpX + c*cellW - 1 + (c?bmpGridThickness+bmpBorderSize*2:0)) + "px";
2932  bmpOverlay.style.top = (bmpY + bmpH + axisPaddingMargin - bmpBorderSize) + "px";
2933  bmpOverlay.style.width = (cellW + 1 - (c?bmpGridThickness+bmpBorderSize*2:0)) + "px";
2934  bmpOverlay.style.height = (butttonSz) + "px";
2935  bmpOverlay.style.display = "block";
2936  }
2937 
2938  infoStr = "Set all pixels in column " + transRC[1] + ".";
2939  }
2940  else if(r == -2 && c == -2) //handle all button
2941  {
2942  //make mouse over bitmap
2943  {
2944  bmpOverlay.src = ConfigurationAPI.getOnePixelPngData([216,188,188]);//canvas.toDataURL();
2945 
2946  bmpOverlay.style.left = (bmpX - axisPaddingMargin - bmpBorderSize - butttonSz) + "px";
2947  bmpOverlay.style.top = (bmpY + bmpH + axisPaddingMargin - bmpBorderSize) + "px";
2948  bmpOverlay.style.width = (butttonSz) + "px";
2949  bmpOverlay.style.height = (butttonSz) + "px";
2950  bmpOverlay.style.display = "block";
2951  }
2952 
2953  infoStr = "Set all pixels.";
2954  }
2955  else //pixel case
2956  {
2957  transRC = localConvertGridToRowCol(r,c);
2958 
2959  //have mouse over bitmap
2960  {
2961  //make a partial alpha overlay that lightens or darkens
2962  // depending on pixel color
2963  var overClr = (bmpDataImage.data[(r*cols+c)*4+0] +
2964  bmpDataImage.data[(r*cols+c)*4+1] +
2965  bmpDataImage.data[(r*cols+c)*4+2]) < (256+128)?255:0;
2966 
2967  bmpOverlay.src = ConfigurationAPI.getOnePixelPngData(
2968  [overClr,overClr,overClr,100]);
2969 
2970  bmpOverlay.style.left = (bmpX + c*cellW) + "px";
2971  bmpOverlay.style.top = (bmpY + r*cellH) + "px";
2972  bmpOverlay.style.width = (cellW) + "px";
2973  bmpOverlay.style.height = (cellH) + "px";
2974  bmpOverlay.style.display = "block";
2975  }
2976 
2977  //position cursor info
2978  infoStr = "Value = " + bmpData[r][c] + " @ (Row,Col) = (" +
2979  transRC[0] + "," + transRC[1] + ")";
2980  }
2981  cursorInfo.innerHTML = infoStr;
2982  hdrCursorInfo.innerHTML = infoStr;
2983 
2984  //Debug.log("r,c " + r + "," + c);
2985  if(r == -2 && c == -2)
2986  return; //prevent mouse over execution of all button (assume it's accidental)
2987 
2988  if(buttonDown >= 0)
2989  {
2990  stopProp = true;
2991  localSetBitMap(r,c); //set bitmap data
2992  }
2993 
2994  } //end mouse move
2995 
2996  //::::::::
2997  //el.onmousedown ~~
2998  el.onmousedown = function(event) {
2999 
3000  var cell = localGetRowCol(event.pageX,event.pageY);
3001  var r = cell.r, c = cell.c;
3002 
3003  //Debug.log("click which " + event.which);
3004  //Debug.log("click button " + event.button);
3005  buttonDown = event.button;
3006 
3007  if(r == -1 || c == -1) //handle no select case
3008  {
3009  rLast = -1; cLast = -1; //reset for mouse move
3010  stopProp = false;
3011  return;
3012  }
3013 
3014  rLast = r; cLast = c;
3015  localSetBitMap(r,c); //set bitmap data
3016 
3017  stopProp = true;
3018  event.stopPropagation();
3019 
3020  } //end mouse down
3021 
3022  //::::::::
3023  //el.onmouseup ~~
3024  el.onmouseup = function(event) {
3025  //Debug.log("click up ");
3026  buttonDown = -1;
3027  } //end mouse up
3028 
3029  //::::::::
3030  //el.oncontextmenu ~~
3031  el.oncontextmenu = function(event) {
3032  //Debug.log("click stopProp " + stopProp);
3033 
3034  if(stopProp)
3035  {
3036  stopProp = false;
3037  event.stopPropagation();
3038  return false;
3039  }
3040  } //end oncontextmenu
3041 
3042  //::::::::
3043  //localSetBitMap ~~
3044  function localSetBitMap(r,c) {
3045 
3046  Debug.log("set r,c " + buttonDown + " @ " + r + "," + c );
3047  buttonDown = buttonDown?1:0; // 0=left-click, 1=right-click
3048 
3049  var maxr = r==-2?rows-1:r;
3050  var minr = r==-2?0:r;
3051  var maxc = c==-2?cols-1:c;
3052  var minc = c==-2?0:c;
3053 
3054  for(r=minr;r<=maxr;++r)
3055  for(c=minc;c<=maxc;++c)
3056  {
3057  bmpData[r][c] = clickValues[buttonDown];
3058  bmpDataImage.data[(r*cols + c)*4 + 0] =
3059  clickColors[buttonDown][0];
3060  bmpDataImage.data[(r*cols + c)*4 + 1] =
3061  clickColors[buttonDown][1];
3062  bmpDataImage.data[(r*cols + c)*4 + 2] =
3063  clickColors[buttonDown][2];
3064  bmpDataImage.data[(r*cols + c)*4 + 3] =
3065  clickColors[buttonDown][3];
3066  }
3067 
3068  bmpContext.putImageData(bmpDataImage,0,0);
3069  bmp.src = bmpCanvas.toDataURL();
3070  }// end localSetBitMap
3071 
3072  } localCreateMouseHandler();
3073 
3074  //:::::::::::::::::::::::::::::::::::::::::
3075  //localValidateInputs ~~
3076  // returns false if inputs are invalid
3077  // else true.
3078  function localValidateInputs() {
3079 
3080  //veryify bitmap params is expected size
3081  if(bitMapParams.length != 16)
3082  {
3083  Debug.log("Illegal input parameters, expecting 16 parameters and count is " + bitMapParams.length + ". There is a mismatch in Table Editor handling of BitMap fields (contact an admin to fix)." +
3084  "\nHere is a printout of the input parameters: " + bitMapParams,Debug.HIGH_PRIORITY);
3085  return false;
3086  }
3087  var DEFAULT = "DEFAULT";
3088 
3089  rows = bitMapParams[0]|0;
3090  cols = bitMapParams[1]|0;
3091  bitFieldSize = bitMapParams[2]|0;
3092 
3093  //js can only handle 31 bits unsigned!! hopefully no one needs it?
3094 
3095  if(rows < 1 || rows >= 1<<30)
3096  {
3097  Debug.log("Illegal input parameters, rows of " + rows + " is illegal. " +
3098  "(rows possible values are from 1 to " + ((1<<30)-1) + ".)",Debug.HIGH_PRIORITY);
3099  return false;
3100  }
3101  if(cols < 1 || cols >= 1<<30)
3102  {
3103  Debug.log("Illegal input parameters, cols of " + cols + " is illegal. " +
3104  "(cols possible values are from 1 to " + ((1<<30)-1) + ".)",Debug.HIGH_PRIORITY);
3105  return false;
3106  }
3107  if(bitFieldSize < 1 || bitFieldSize > 31)
3108  {
3109  Debug.log("Illegal input parameters, bitFieldSize of " + bitFieldSize + " is illegal. " +
3110  "(bitFieldSize possible values are from 1 to " + (31) + ".)",Debug.HIGH_PRIORITY);
3111  return false;
3112  }
3113 
3114 
3115  if(bitFieldSize > 30)
3116  {
3117  bitMask = 0;
3118  for(var i=0;i<bitFieldSize;++i)
3119  bitMask |= 1 << i;
3120  }
3121  else
3122  bitMask = (1<<bitFieldSize) - 1; //wont work for 31 bits (JS is always signed)
3123 
3124  minValue = bitMapParams[3] == "DEFAULT" || bitMapParams[3] == ""?0:(bitMapParams[3]|0);
3125  maxValue = bitMapParams[4] == "DEFAULT" || bitMapParams[4] == ""?bitMask:(bitMapParams[4]|0);
3126  if(maxValue < minValue)
3127  maxValue = bitMask;
3128  midValue = (maxValue + minValue)/2; //used for color calcs
3129  stepValue = bitMapParams[5] == "DEFAULT" || bitMapParams[5] == ""?1:(bitMapParams[5]|0);
3130 
3131  if(minValue < 0 || minValue > bitMask)
3132  {
3133  Debug.log("Illegal input parameters, minValue of " + minValue + " is illegal. " +
3134  "(minValue possible values are from 0 to " + bitMask + ".)",Debug.HIGH_PRIORITY);
3135  return false;
3136  }
3137  if(maxValue < 0 || maxValue > bitMask)
3138  {
3139  Debug.log("Illegal input parameters, maxValue of " + maxValue + " is illegal. " +
3140  "(maxValue possible values are from 0 to " + bitMask + ".)",Debug.HIGH_PRIORITY);
3141  return false;
3142  }
3143  if(minValue > maxValue)
3144  {
3145  Debug.log("Illegal input parameters, minValue > maxValue is illegal.",Debug.HIGH_PRIORITY);
3146  return false;
3147  }
3148  if(stepValue < 1 || stepValue > maxValue - minValue)
3149  {
3150  Debug.log("Illegal input parameters, stepValue of " + stepValue + " is illegal. " +
3151  "(stepValue possible values are from 1 to " + (maxValue - minValue) + ".)",Debug.HIGH_PRIORITY);
3152  return false;
3153  }
3154  if((((maxValue-minValue)/stepValue)|0) != (maxValue-minValue)/stepValue)
3155  {
3156  Debug.log("Illegal input parameters, maxValue of " + maxValue +
3157  " must be an integer number of stepValue (stepValue=" + stepValue +
3158  ") steps away from minValue (minValue=" + minValue + ").",Debug.HIGH_PRIORITY);
3159  return false;
3160  }
3161 
3162  if(bitMapParams[6] != "" &&
3163  bitMapParams[6] != DEFAULT)
3164  {
3165  forcedAspectH = bitMapParams[6].split(':');
3166  if(forcedAspectH.length != 2)
3167  {
3168  Debug.log("Illegal input parameter, expecting ':' in string defining cell display aspect ratio " +
3169  "Height:Width (e.g. 100:150)." +
3170  "\nInput aspect ratio string '" + bitMapParams[6] + "' is invalid.",Debug.HIGH_PRIORITY);
3171  return false;
3172  }
3173  forcedAspectW = forcedAspectH[1].trim()|0;
3174  forcedAspectH = forcedAspectH[0].trim()|0;
3175  }
3176  else //default to 1:1
3177  forcedAspectW = forcedAspectH = 1;
3178 
3179 
3180  //colors
3181  minValueColor = bitMapParams[7] == DEFAULT || bitMapParams[7] == ""?"red":bitMapParams[7];
3182  midValueColor = bitMapParams[8] == DEFAULT || bitMapParams[8] == ""?"yellow":bitMapParams[8];
3183  maxValueColor = bitMapParams[9] == DEFAULT || bitMapParams[9] == ""?"green":bitMapParams[9];
3184  floorValueColor = bitMapParams[10] == DEFAULT || bitMapParams[10] == ""?minValueColor:bitMapParams[10];
3185  ceilValueColor = bitMapParams[11] == DEFAULT || bitMapParams[11] == ""?maxValueColor:bitMapParams[11];
3186 
3187  //convert to arrays
3188  minValueColor = DesktopContent.getColorAsRGBA(minValueColor).split("(")[1].split(")")[0].split(",");
3189  midValueColor = DesktopContent.getColorAsRGBA(midValueColor).split("(")[1].split(")")[0].split(",");
3190  maxValueColor = DesktopContent.getColorAsRGBA(maxValueColor).split("(")[1].split(")")[0].split(",");
3191  ceilValueColor = DesktopContent.getColorAsRGBA(ceilValueColor).split("(")[1].split(")")[0].split(",");
3192  floorValueColor = DesktopContent.getColorAsRGBA(floorValueColor).split("(")[1].split(")")[0].split(",");
3193 
3194  //load bools
3195  doDisplayRowsAscending = bitMapParams[12] == "Yes"?1:0;
3196  doDisplayColsAscending = bitMapParams[13] == "Yes"?1:0;
3197  doSnakeColumns = bitMapParams[14] == "Yes"?1:0;
3198  doSnakeRows = bitMapParams[15] == "Yes"?1:0;
3199 
3200  if(doSnakeColumns && doSnakeRows)
3201  {
3202  Debug.log("Can not have a bitmap that snakes both rows and columns, please choose one or the other (or neither).",Debug.HIGH_PRIORITY);
3203  return false;
3204  }
3205 
3206 
3207  return true;
3208  }
3209 
3210  //:::::::::::::::::::::::::::::::::::::::::
3211  //localInitBitmapData ~~
3212  // load bitmap data from input string <initBitMapValue>
3213  // and initialize bmpDataImage
3214  // treat <initBitMapValue> as JSON 2D array string
3215  function localInitBitmapData()
3216  {
3217  //create empty array for bmpData
3218  bmpData = [];
3219 
3220  try
3221  {
3222  var jsonMatrix = JSON.parse(initBitMapValue);
3223 
3224  //create place holder 2D array for fill
3225  for(var r=0;r<rows;++r)
3226  {
3227  bmpData.push([]); //create empty row array
3228 
3229  for(var c=0;c<cols;++c)
3230  bmpData[r][c] = 0;
3231  }
3232  localConvertFullRowColToGrid(jsonMatrix); //also sets bmpDataImage
3233  }
3234  catch(err)
3235  {
3236  Debug.log("The input initial value of the bitmap is illegal JSON format. " +
3237  "See error below: \n\n" + err,Debug.HIGH_PRIORITY);
3238  Debug.log("Defaulting to initial bitmap with min-value fill.",Debug.HIGH_PRIORITY);
3239 
3240  //min-value fill
3241  var color;
3242  for(var r=0;r<rows;++r)
3243  {
3244  bmpData.push([]); //create empty row array
3245 
3246  for(var c=0;c<cols;++c)
3247  {
3248  bmpData[r][c] = minValue; //min-value entry in column
3249 
3250  color = localConvertValueToRGBA(bmpData[r][c]);
3251  bmpDataImage.data[(r*cols + c)*4+0]=color[0];
3252  bmpDataImage.data[(r*cols + c)*4+1]=color[1];
3253  bmpDataImage.data[(r*cols + c)*4+2]=color[2];
3254  bmpDataImage.data[(r*cols + c)*4+3]=color[3];
3255  }
3256  }
3257 
3258  bmpContext.putImageData(bmpDataImage,0,0);
3259  bmp.src = bmpCanvas.toDataURL();
3260  }
3261  }
3262 
3263  //:::::::::::::::::::::::::::::::::::::::::
3264  //localConvertGridToRowCol ~~
3265  // grid row col is always 0,0 in top left
3266  // but there might be translation for user (imagine snaked columns)
3267  // inputs: doDisplayRowsAscending, doDisplayColsAscending, doSnakeColumns, doSnakeRows,
3268  // ...rows, cols
3269  // return translated row,col
3270  function localConvertGridToRowCol(r,c)
3271  {
3272  var retVal = [r,c];
3273  if(!doDisplayRowsAscending) //reverse row order so flip row
3274  retVal[0] = rows - 1 - retVal[0];
3275  if(!doDisplayColsAscending) //reverse col order so flip col
3276  retVal[1] = cols - 1 - retVal[1];
3277  if(doSnakeRows && retVal[0]%2 == 1) //snake row so flip col
3278  retVal[1] = cols + (cols - 1 - retVal[1]);
3279  if(doSnakeColumns && retVal[1]%2 == 1) //snake col so flip row
3280  retVal[0] = rows + (rows - 1 - retVal[0]);
3281 
3282  return retVal;
3283  }
3284 
3285  //:::::::::::::::::::::::::::::::::::::::::
3286  //localConvertValueToRGBA ~~
3287  // conver bitfield value to RGBA based on input parameters
3288  function localConvertValueToRGBA(val)
3289  {
3290  if(val >= maxValue)
3291  return [ceilValueColor[0],
3292  ceilValueColor[1],
3293  ceilValueColor[2],
3294  255]; //always max alpha
3295 
3296  if(val <= minValue)
3297  return [floorValueColor[0],
3298  floorValueColor[1],
3299  floorValueColor[2],
3300  255]; //always max alpha
3301 
3302  if(val == midValue) //avoid dividing by 0 in blend
3303  return [midValueColor[0],
3304  midValueColor[1],
3305  midValueColor[2],
3306  255]; //always max alpha
3307 
3308  //blend lower half
3309  var t;
3310  if(val <= midValue)
3311  {
3312  t = (val - minValue)/(midValue - minValue);
3313  return [minValueColor[0]*(1-t) + t*midValueColor[0],
3314  minValueColor[1]*(1-t) + t*midValueColor[1],
3315  minValueColor[2]*(1-t) + t*midValueColor[2],
3316  255]; //always max alpha
3317  }
3318  //blend upper half
3319  //if(val >= midValue)
3320  {
3321  t = (val - midValue)/(maxValue - midValue);
3322  return [midValueColor[0]*(1-t) + t*maxValueColor[0],
3323  midValueColor[1]*(1-t) + t*maxValueColor[1],
3324  midValueColor[2]*(1-t) + t*maxValueColor[2],
3325  255]; //always max alpha
3326  }
3327  }
3328 
3329 
3330  //:::::::::::::::::::::::::::::::::::::::::
3331  //localConvertFullGridToRowCol ~~
3332  // convert bmpData matrix to a matrix with translated Row,Col pairs
3333  function localConvertFullGridToRowCol()
3334  {
3335  var retArr = [];
3336  var convertedRC;
3337  for(var r=0;r<rows;++r)
3338  for(var c=0;c<cols;++c)
3339  {
3340  convertedRC = localConvertGridToRowCol(r,c);
3341  //if doSnakeColumns, odd columns are considered to be in even column
3342  if(doSnakeColumns)
3343  convertedRC[1] = (convertedRC[1]/2)|0;
3344  //if doSnakeRows, odd rows are considered to be in even row
3345  if(doSnakeRows)
3346  convertedRC[0] = (convertedRC[0]/2)|0;
3347 
3348  if(retArr[convertedRC[0]] === undefined)
3349  retArr[convertedRC[0]] = []; //create row for first time
3350  retArr[convertedRC[0]][convertedRC[1]] = bmpData[r][c];
3351  }
3352  return retArr;
3353  }
3354 
3355  //:::::::::::::::::::::::::::::::::::::::::
3356  //localConvertFullRowColToGrid ~~
3357  // convert a matrix with translated Row,Col pairs to bmpData matrix
3358  // updates bmpDataImage also and bmp display
3359  function localConvertFullRowColToGrid(srcMatrix)
3360  {
3361  var convertedRC;
3362  var color;
3363  var noErrors = true;
3364  for(var r=0;r<rows;++r)
3365  for(var c=0;c<cols;++c)
3366  {
3367  convertedRC = localConvertGridToRowCol(r,c);
3368 
3369  //if doSnakeColumns, odd columns are considered to be in even column
3370  if(doSnakeColumns)
3371  convertedRC[1] = (convertedRC[1]/2)|0;
3372  //if doSnakeRows, odd rows are considered to be in even row
3373  if(doSnakeRows)
3374  convertedRC[0] = (convertedRC[0]/2)|0;
3375  try
3376  {
3377  bmpData[r][c] = srcMatrix[convertedRC[0]][convertedRC[1]]|0;
3378  if(bmpData[r][c] < minValue)
3379  throw("There was an illegal value less than minValue: " +
3380  bmpData[r][c] + " < " + minValue + " @ (row,col) = (" +
3381  convertedRC[0] + "," + convertedRC[0] + ")");
3382  if(bmpData[r][c] > maxValue)
3383  throw("There was an illegal value greater than maxValue: " +
3384  bmpData[r][c] + " > " + maxValue + " @ (row,col) = (" +
3385  convertedRC[0] + "," + convertedRC[0] + ")");
3386  if((((bmpData[r][c]-minValue)/stepValue)|0) != (bmpData[r][c]-minValue)/stepValue)
3387  throw("There was an illegal value not following stepValue from minValue: " +
3388  bmpData[r][c] + " != " +
3389  (stepValue*(((bmpData[r][c]-minValue)/stepValue)|0)) +
3390  " @ (row,col) = (" +
3391  convertedRC[0] + "," + convertedRC[0] + ")");
3392  color = localConvertValueToRGBA(bmpData[r][c]);
3393  bmpDataImage.data[(r*cols + c)*4+0]=color[0];
3394  bmpDataImage.data[(r*cols + c)*4+1]=color[1];
3395  bmpDataImage.data[(r*cols + c)*4+2]=color[2];
3396  bmpDataImage.data[(r*cols + c)*4+3]=color[3];
3397  }
3398  catch(err)
3399  {noErrors = false;} //ignore errors
3400  }
3401  bmpContext.putImageData(bmpDataImage,0,0);
3402  bmp.src = bmpCanvas.toDataURL();
3403 
3404  if(!noErrors)
3405  throw("There was a mismatch in row/col dimensions. Input matrix was " +
3406  "dimension [row,col] = [" + srcMatrix.length + "," +
3407  (srcMatrix.length?srcMatrix[0].length:0) + "]");
3408  }
3409 
3410  //:::::::::::::::::::::::::::::::::::::::::
3411  //localCreateBitmap ~~
3412  // create bitmap
3413  function localCreateBitmap()
3414  {
3415  bmp = document.createElement("img");
3416  bmp.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap");
3417 
3418  bmpGrid = document.createElement("div"); //div of row and col grid divs
3419  bmpGrid.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid");
3420 
3421  bmpOverlay = document.createElement("img");
3422  bmpOverlay.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-overlay");
3423 
3424  cursorInfo = document.createElement("div"); //div of row and col grid divs
3425  cursorInfo.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-cursor-info");
3426  hdrCursorInfo = document.createElement("div"); //div of row and col grid divs
3427  hdrCursorInfo.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-cursor-hdrInfo");
3428 
3429  //create divs for r,c text display
3430  rowLeftNums = document.createElement("div");
3431  rowRightNums = document.createElement("div");
3432  colTopNums = document.createElement("div");
3433  colBottomNums = document.createElement("div");
3434  rowLeftNums.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-numbers-rowLeft");
3435  rowRightNums.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-numbers-rowRight");
3436  colTopNums.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-numbers-colTop");
3437  colBottomNums.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-numbers-colBottom");
3438 
3439  var tmpEl;
3440 
3441  //group creation of row/col elements
3442  {
3443  bmpCanvas=document.createElement("canvas");
3444  bmpCanvas.width = cols;
3445  bmpCanvas.height = rows;
3446  bmpContext = bmpCanvas.getContext("2d");
3447 
3448  if(bmpDataImage) delete bmpDataImage;
3449  bmpDataImage = bmpContext.createImageData(cols,rows);
3450 
3451  //add outside box div as child 0
3452  tmpEl = document.createElement("div");
3453  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-box");
3454  bmpGrid.appendChild(tmpEl);
3455 
3456  for(var i=0;i<rows;++i)
3457  {
3458  if(i < rows - 1) //add internal row divs to start
3459  {
3460  tmpEl = document.createElement("div");
3461  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-row-dark");
3462  bmpGrid.appendChild(tmpEl);
3463  tmpEl = document.createElement("div");
3464  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-row");
3465  bmpGrid.appendChild(tmpEl);
3466  }
3467 
3468  for(var j=0;j<cols;++j)
3469  {
3470  if(i == rows-1 & j < cols-1) //add internal col divs at end
3471  {
3472  tmpEl = document.createElement("div");
3473  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-col-dark");
3474  bmpGrid.appendChild(tmpEl);
3475  tmpEl = document.createElement("div");
3476  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-col");
3477  bmpGrid.appendChild(tmpEl);
3478  }
3479  }
3480  }
3481 
3482  bmpContext.putImageData(bmpDataImage,0,0);
3483  bmp.src = bmpCanvas.toDataURL();
3484  }
3485 
3486  bmp.style.position = "absolute";
3487  bmp.draggable = false; //prevent dragging
3488 
3489  bmpGrid.style.position = "absolute";
3490 
3491  bmpOverlay.style.display = "none";
3492  bmpOverlay.style.position = "absolute";
3493  bmpOverlay.draggable = false; //prevent dragging
3494 
3495  cursorInfo.style.position = "absolute";
3496  cursorInfo.style.display = "none";
3497  hdrCursorInfo.style.position = "absolute";
3498  hdrCursorInfo.style.display = "none";
3499  hdrCursorInfo.style.width = "320px";
3500 
3501  rowLeftNums.style.position = "absolute";
3502  rowRightNums.style.position = "absolute";
3503  colTopNums.style.position = "absolute";
3504  colBottomNums.style.position = "absolute";
3505 
3506  el.appendChild(bmp);
3507  el.appendChild(bmpGrid);
3508  el.appendChild(bmpOverlay);
3509 
3510  el.appendChild(hdrCursorInfo); //insert hdrInfo first so cursorInfo goes over top of it
3511  el.appendChild(cursorInfo);
3512 
3513  el.appendChild(rowLeftNums);
3514  el.appendChild(rowRightNums);
3515  el.appendChild(colTopNums);
3516  el.appendChild(colBottomNums);
3517  }
3518 
3519  //:::::::::::::::::::::::::::::::::::::::::
3520  //localCreateGridButtons ~~
3521  // create all (row,col) buttons
3522  function localCreateGridButtons()
3523  {
3524  allRowBtns = document.createElement("div"); //div of all row button divs
3525 
3526  allColBtns = document.createElement("div"); //div of all col button divs
3527 
3528  allBtn = document.createElement("div"); //div of all button
3529  allBtn.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-btn-all");
3530 
3531  var tmpEl;
3532  for(var i=0;i<rows;++i)
3533  {
3534  tmpEl = document.createElement("div");
3535  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-btn-all");
3536  tmpEl.style.position = "absolute";
3537  allRowBtns.appendChild(tmpEl);
3538  }
3539  for(var i=0;i<cols;++i)
3540  {
3541  tmpEl = document.createElement("div");
3542  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-btn-all");
3543  tmpEl.style.position = "absolute";
3544  allColBtns.appendChild(tmpEl);
3545  }
3546 
3547  allRowBtns.style.position = "absolute";
3548  el.appendChild(allRowBtns);
3549  allColBtns.style.position = "absolute";
3550  el.appendChild(allColBtns);
3551  allBtn.style.position = "absolute";
3552  el.appendChild(allBtn);
3553  }
3554 
3555  //:::::::::::::::::::::::::::::::::::::::::
3556  //localCreateHeader ~~
3557  // create header
3558  function localCreateHeader()
3559  {
3560  hdr = document.createElement("div");
3561  hdr.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-header");
3562 
3563  var str = "";
3564 
3565  str += "<div style='float:left; margin: 0 0 20px 0;'>"; //field name and info container
3566  str += "<div style='float:left; '>";
3567  str += fieldName;
3568 // fieldName = fieldName.split('\n');
3569 // str += "Target UID/Field: &quot;" +
3570 // fieldName[0].split(" : ")[1] + "/" +
3571 // fieldName[1].split(" : ")[0] + "&quot;";
3572  str += "</div>";
3573 
3574  str += "<div style='float:left; margin-left: 50px;'>";
3575  str += "Number of [Rows,Cols]: " + "[" + rows + "," + cols + "]";
3576  str += "</div>";
3577  str += "</div>";//end field name and info container
3578 
3579  str += "<div style='float:right; '>";
3580  str += "<a id='" +
3581  ConfigurationAPI._POP_UP_DIALOG_ID +
3582  "-cancel' href='#'>Cancel</a>";
3583  str += "</div>";
3584 
3585  str += "<div id='clearDiv'></div>";
3586 
3587  str += "<div style='float:right; margin: 40px 20px -50px 0;'>";
3588  str += "<a id='" +
3589  ConfigurationAPI._POP_UP_DIALOG_ID +
3590  "-ok' href='#'>OK</a>";
3591  str += "</div>";
3592 
3593  str += "<div style='float:left; margin: 0 0 0 0;'>";
3594  for(var clickIndex=0;clickIndex<2;++clickIndex)
3595  {
3596  str += "<div style='float:left; margin: 5px 0 0 0;'>";
3597  str += "<div style='float:left; width:180px; text-align:right; margin-top: 3px;'>";
3598  str += (clickIndex?"Right":"Left") + "-Click Value:";
3599  str += "</div>";
3600  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3601  "-bitmap-scrollbar' style='float:left;' " +
3602  "type='range' min='" + minValue +
3603  "' max='" + maxValue + "' value='" + (clickIndex?maxValue:minValue) +
3604  "' step='" + stepValue +
3605  "' oninput='ConfigurationAPI.bitMapDialog.localUpdateScroll(" + clickIndex + ")' />";
3606  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3607  "-bitmap-btnInput' style='float:left; margin: 0 1px 0 5px;' " +
3608  "type='button' value='<' " +
3609  "onmousedown='ConfigurationAPI.bitMapDialog.localUpdateButtonInput(" + clickIndex + ",0,0)' " +
3610  "onmouseup='ConfigurationAPI.bitMapDialog.localUpdateButtonInput(" + clickIndex + ",0,1)' " +
3611  "/> ";
3612  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3613  "-bitmap-btnInput' style='float:left;' " +
3614  "type='button' value='>' " +
3615  "onmousedown='ConfigurationAPI.bitMapDialog.localUpdateButtonInput(" + clickIndex + ",1,0)' " +
3616  "onmouseup='ConfigurationAPI.bitMapDialog.localUpdateButtonInput(" + clickIndex + ",1,1)' " +
3617  "/> ";
3618  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3619  "-bitmap-textInput' style='float:left; margin: 0 5px 0 5px; width: 50px;' " +
3620  "type='text' " + //value come from scroll update at start
3621  "onchange='ConfigurationAPI.bitMapDialog.localUpdateTextInput(" + clickIndex + ",1)' " +
3622  "onkeydown='ConfigurationAPI.bitMapDialog.localUpdateTextInput(" + clickIndex + ",0)' " +
3623  "onkeyup='ConfigurationAPI.bitMapDialog.localUpdateTextInput(" + clickIndex + ",0)' " +
3624  "/>";
3625  str += "<img class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3626  "-bitmap-colorSample' style='float:left;width:25px; height:25px; margin: -2px 0 2px 0;' " +
3627  "ondragstart='return false;' " + //ondragstart for firefox
3628  "draggable='false'" + //draggable for chrome
3629  "'/>";
3630 
3631 
3632  str += "</div>";
3633 
3634  str += "<div id='clearDiv'></div>";
3635  }
3636  str += "</div>";
3637 
3638  //add download upload buttons
3639  str += "<div style='float:left; margin: 5px 0 0 40px;'>";
3640  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3641  "-bitmap-btnCsv' style='float:left;' " +
3642  "type='button' value='Download as CSV' " +
3643  "onclick='ConfigurationAPI.bitMapDialog.localDownloadCSV()' " +
3644  "/> ";
3645  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3646  "-bitmap-btnCsv' style='float:left; margin: 0 0 0 10px;' " +
3647  "type='button' value='Upload CSV' " +
3648  "onclick='ConfigurationAPI.bitMapDialog.locaPopupUploadCSV()' " +
3649  "/> ";
3650  str += "</div>";
3651 
3652  hdr.innerHTML = str;
3653  hdr.style.overflowY = "auto";
3654  hdr.style.position = "absolute";
3655 
3656  var scrollEls = hdr.getElementsByClassName(ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-scrollbar");
3657  var textInputEls = hdr.getElementsByClassName(ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-textInput");
3658  var colorSampleEls = hdr.getElementsByClassName(ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-colorSample");
3659 
3660 
3661  //::::::::::
3662  //localUpdateScroll ~~
3663  ConfigurationAPI.bitMapDialog.localUpdateScroll = function(i)
3664  {
3665  Debug.log("localUpdateScroll " + i);
3666 
3667  clickValues[i] = scrollEls[i].value|0;
3668  clickColors[i] = localConvertValueToRGBA(clickValues[i]);
3669 
3670  textInputEls[i].value = clickValues[i];
3671  colorSampleEls[i].src = ConfigurationAPI.getOnePixelPngData(clickColors[i]);
3672  }; //end localUpdateScroll
3673 
3674  //::::::::::
3675  //localUpdateTextInput ~~
3676  ConfigurationAPI.bitMapDialog.localUpdateTextInput = function(i,finalChange)
3677  {
3678  Debug.log("localUpdateTextInput " + textInputEls[i].value + " " + finalChange);
3679 
3680  clickValues[i] = textInputEls[i].value|0;
3681 
3682  if(finalChange)
3683  {
3684  if(clickValues[i] < minValue) clickValues[i] = minValue;
3685  if(clickValues[i] > maxValue) clickValues[i] = maxValue;
3686  clickValues[i] = (((clickValues[i]-minValue)/stepValue)|0)*stepValue + minValue; //lock to step
3687  textInputEls[i].value = clickValues[i]; //fix value
3688  }
3689  else //try to continue with change, but if invalid just return
3690  {
3691  if(clickValues[i] < minValue) return;
3692  if(clickValues[i] > maxValue) return;
3693  if((((clickValues[i]-minValue)/stepValue)|0) != (clickValues[i]-minValue)/stepValue)
3694  return; //no locked to step value
3695  Debug.log("displaying change");
3696  }
3697  clickColors[i] = localConvertValueToRGBA(clickValues[i]);
3698 
3699  scrollEls[i].value = clickValues[i];
3700  colorSampleEls[i].src = ConfigurationAPI.getOnePixelPngData(clickColors[i]);
3701  }; //end localUpdateTextInput
3702 
3703  //::::::::::
3704  //localUpdateButtonInput ~~
3705  var mouseDownTimer = 0;
3706  ConfigurationAPI.bitMapDialog.localUpdateButtonInput = function(i,dir,mouseUp,delay)
3707  {
3708  window.clearInterval(mouseDownTimer);
3709  if(mouseUp) //mouse up
3710  {
3711  Debug.log("cancel mouse down");
3712  return;
3713  }
3714  //else mouse is down so set repeat interval
3715  mouseDownTimer = window.setInterval(function()
3716  {
3717  //faster and faster
3718  if(delay > 50) delay -= 50;
3719  ConfigurationAPI.bitMapDialog.localUpdateButtonInput(i,dir,0,50); //same as this call
3720  },delay!==undefined?delay:300);
3721 
3722  Debug.log("localUpdateButtonInput " + textInputEls[i].value + " " + dir);
3723 
3724  clickValues[i] = clickValues[i] + (dir?stepValue:-stepValue);
3725  if(clickValues[i] < minValue) clickValues[i] = minValue;
3726  if(clickValues[i] > maxValue) clickValues[i] = maxValue;
3727 
3728  clickColors[i] = localConvertValueToRGBA(clickValues[i]);
3729 
3730  textInputEls[i].value = clickValues[i];
3731  scrollEls[i].value = clickValues[i];
3732  colorSampleEls[i].src = ConfigurationAPI.getOnePixelPngData(clickColors[i]);
3733 
3734  }; //end localUpdateButtonInput
3735 
3736  //::::::::::
3737  //localDownloadCSV ~~
3738  // online people complain that this doesn't work for big files.
3739  // Note: if files are too big, could have server create csv file
3740  // then link to <a href='/WebPath/download.csv' download></a>
3741  // Note: href must be encodeURI(dataStr) if data not already encoded
3742  ConfigurationAPI.bitMapDialog.localDownloadCSV = function()
3743  {
3744  var transGrid = localConvertFullGridToRowCol();
3745  console.log(transGrid);
3746 
3747  var dataStr = "data:text/csv;charset=utf-8,";
3748 
3749  for(var r=0;r<transGrid.length;++r)
3750  {
3751  if(r) dataStr += encodeURI("\n"); //encoded \n
3752  for(var c=0;c<transGrid[0].length;++c)
3753  {
3754  if(c) dataStr += ",";
3755  dataStr += transGrid[r][c];
3756  }
3757  }
3758 
3759  Debug.log("ConfigurationAPI.bitMapDialog.localDownloadCSV dataStr=" + dataStr);
3760 
3761  var link = document.createElement("a");
3762  link.setAttribute("href", dataStr); //double encode, so encoding remains in CSV
3763  link.setAttribute("style", "display:none");
3764  link.setAttribute("download", _currentConfigName + "_" +
3765  fieldName + "_download.csv");
3766  document.body.appendChild(link); // Required for FF
3767 
3768  link.click(); // This will download the data file named "my_data.csv"
3769 
3770  link.parentNode.removeChild(link);
3771  }; //end localDownloadCSV
3772 
3773 
3774 
3775  //::::::::::
3776  //locaUploadCSV ~~
3777  ConfigurationAPI.bitMapDialog._csvUploadDataStr; //uploaded csv table ends up here
3778  ConfigurationAPI.bitMapDialog.locaUploadCSV = function()
3779  {
3780  Debug.log("locaUploadCSV ConfigurationAPI.bitMapDialog._csvUploadDataStr = " + ConfigurationAPI.bitMapDialog._csvUploadDataStr);
3781  var srcDataStr = ConfigurationAPI.bitMapDialog._csvUploadDataStr.split('\n');
3782  var src = []; //src = [r][c]
3783  for(var i=0;i<srcDataStr.length;++i)
3784  src.push(srcDataStr[i].split(','));
3785  console.log(src);
3786 
3787  try
3788  {
3789  localConvertFullRowColToGrid(src);
3790 
3791  Debug.log("Successfully uploaded CSV file to bitmap!", Debug.INFO_PRIORITY);
3792 
3793  //on succes remove popup
3794  el = document.getElementById("popUpDialog");
3795  if(el) el.parentNode.removeChild(el);
3796  }
3797  catch(err)
3798  {
3799  Debug.log("Errors occured during upload. Bitmap may not reflect contents of CSV file." +
3800  "\nHere is the error description: \n" + err, Debug.HIGH_PRIORITY);
3801 
3802  //enable button so upload can be tried again
3803  document.getElementById('popUpDialog-submitButton').disabled = false;
3804  }
3805  }
3806 
3807  //::::::::::
3808  //locaPopupUploadCSV ~~
3809  ConfigurationAPI.bitMapDialog.locaPopupUploadCSV = function()
3810  {
3811  Debug.log("ConfigurationAPI.bitMapDialog.locaPopupUploadCSV");
3812  ConfigurationAPI.bitMapDialog._csvUploadDataStr = ""; //clear previous upload
3813 
3814  var str = "";
3815 
3816  var pel = document.getElementById("popUpDialog");
3817  if(!pel)
3818  {
3819  pel = document.createElement("div");
3820  pel.setAttribute("id", "popUpDialog");
3821  }
3822  pel.style.display = "none";
3823 
3824  //set position and size
3825  var w = 380;
3826  var h = 195;
3827  ConfigurationAPI.setPopUpPosition(pel,w /*w*/,h /*h*/);
3828 
3829  var str = "<a id='" +
3830  "popUpDialog" + //clear upload string on cancel!
3831  "-header' href='#' onclick='javascript:ConfigurationAPI.bitMapDialog._csvUploadDataStr = \"\"; var pel = document.getElementById(" +
3832  "\"popUpDialog\"); if(pel) pel.parentNode.removeChild(pel); return false;'>Cancel</a><br><br>";
3833 
3834  str += "<div id='popUpDialog-div'>";
3835 
3836  str += "Please choose a CSV formatted data file (i.e. commas for columns, and new lines for rows) " +
3837  "to upload:<br><br>";
3838 
3839  str += "<center>";
3840 
3841  str += "<input type='file' id='popUpDialog-fileUpload' " +
3842  "accept='.csv' enctype='multipart/form-data' />";
3843 
3844  // done with special handling
3845  // continue with pop-up prompt
3846  str += "</center></div><br><br>"; //close main popup div
3847 
3848  var onmouseupJS = "";
3849  onmouseupJS += "document.getElementById(\"popUpDialog-submitButton\").disabled = true;";
3850  onmouseupJS += "ConfigurationAPI.bitMapDialog.locaUploadCSV();";
3851 
3852  str += "<input id='popUpDialog-submitButton' disabled type='button' onmouseup='" +
3853  onmouseupJS + "' " +
3854  "value='Upload File' title='" +
3855  "Upload the chosen file to replace the row,col data in the current bitmap." +
3856  "'/>";
3857 
3858  pel.innerHTML = str;
3859  el.appendChild(pel); //add element to bitmap div
3860  pel.style.display = "block";
3861 
3862  document.getElementById('popUpDialog-fileUpload').addEventListener(
3863  'change', function(evt) {
3864  var files = evt.target.files;
3865  var file = files[0];
3866  var reader = new FileReader();
3867  reader.onload = function() {
3868  //store uploaded file and enable button
3869  ConfigurationAPI.bitMapDialog._csvUploadDataStr = this.result;
3870  Debug.log("ConfigurationAPI.bitMapDialog._csvUploadDataStr = " + ConfigurationAPI.bitMapDialog._csvUploadDataStr);
3871  document.getElementById('popUpDialog-submitButton').disabled = false;
3872  }
3873  reader.readAsText(file);
3874  }, false);
3875 
3876  }; //end locaUploadCSV
3877 
3878 
3879  el.appendChild(hdr);
3880 
3881  ConfigurationAPI.bitMapDialog.localUpdateScroll(0);
3882  ConfigurationAPI.bitMapDialog.localUpdateScroll(1);
3883 
3884  } //end localCreateHeader()
3885 
3886  //:::::::::::::::::::::::::::::::::::::::::
3887  //localPaint ~~
3888  // called every time window is resized
3889  function localPaint()
3890  {
3891  Debug.log("localPaint");
3892 
3893  popSz = ConfigurationAPI.setPopUpPosition(el,undefined,undefined,padding,undefined,
3894  30 /*margin*/, true /*doNotResize*/);
3895 
3896  hdrW = popSz.w;
3897  //axisPadding = 40 + axisPaddingExtra;
3898  hdrX = padding;
3899  hdrY = padding;
3900  hdrW = popSz.w;
3901  hdrH = 150;
3902  bmpX = padding;
3903  bmpY = hdrY+hdrH+padding;
3904  bmpW = popSz.w - 2*axisPadding;
3905  bmpH = popSz.h - hdrH - padding - 2*axisPadding;
3906 
3907  cellW = bmpW/cols;
3908  cellH = bmpH/rows;
3909 
3910  localOptimizeAspectRatio(); //sets up bmpX,Y based on aspect ratio (inputs are cellW/cellH, bmpW/bmpH, rows/cols)
3911 
3912  //place header
3913  hdr.style.left = hdrX + "px";
3914  hdr.style.top = hdrY + "px";
3915  hdr.style.width = hdrW + "px";
3916  hdr.style.height = hdrH + "px";
3917 
3918  //place bitmap
3919  bmp.style.left = bmpX + "px";
3920  bmp.style.top = bmpY + "px";
3921  bmp.style.width = bmpW + "px";
3922  bmp.style.height = bmpH + "px";
3923 
3924 
3925  //place bitmap grid and buttons
3926  {
3927 
3928  bmpGrid.style.left = (bmpX-bmpBorderSize) + "px";
3929  bmpGrid.style.top = (bmpY-bmpBorderSize) + "px";
3930  bmpGrid.style.width = (bmpW) + "px";
3931  bmpGrid.style.height = (bmpH) + "px";
3932 
3933  var bmpGridChildren = bmpGrid.childNodes;
3934 
3935  //place all rows div
3936  allRowBtns.style.left = (bmpX - bmpBorderSize - axisPaddingMargin - bmpBorderSize - butttonSz) + "px";
3937  allRowBtns.style.top = (bmpY - bmpBorderSize) + "px";
3938  //place all cols div
3939  allColBtns.style.left = (bmpX - bmpBorderSize) + "px";
3940  allColBtns.style.top = (bmpY + bmpH + axisPaddingMargin - bmpBorderSize*2) + "px";
3941  //place all div
3942  allBtn.style.left = (bmpX - bmpBorderSize - axisPaddingMargin - bmpBorderSize - butttonSz) + "px";
3943  allBtn.style.top = (bmpY + bmpH + axisPaddingMargin - bmpBorderSize*2) + "px";
3944  allBtn.style.width = butttonSz + "px";
3945  allBtn.style.height = butttonSz + "px";
3946 
3947  var allRowsChildren = allRowBtns.childNodes;
3948  var allColsChildren = allColBtns.childNodes;
3949 
3950 
3951  //place number divs
3952  rowLeftNums.style.left = (bmpX - bmpBorderSize - axisPaddingMargin - bmpBorderSize - butttonSz + (- bmpBorderSize - axisPaddingMargin - axisPaddingExtra)) + "px";
3953  rowLeftNums.style.top = (bmpY - bmpBorderSize) + "px";
3954  rowRightNums.style.left = (bmpX + bmpW + axisPaddingMargin + bmpBorderSize) + "px";
3955  rowRightNums.style.top = (bmpY - bmpBorderSize) + "px";
3956  colTopNums.style.left = (bmpX - bmpBorderSize) + "px";
3957  colTopNums.style.top = (bmpY - bmpBorderSize*2 - numberDigitH) + "px";
3958  colBottomNums.style.left = (bmpX - bmpBorderSize) + "px";
3959  colBottomNums.style.top = (bmpY + bmpH + bmpBorderSize + axisPaddingMargin + bmpBorderSize + butttonSz + bmpBorderSize) + "px";
3960  rowLeftNums.innerHTML = ""; //clear all children
3961  rowRightNums.innerHTML = ""; //clear all children
3962  colTopNums.innerHTML = ""; //clear all children
3963  colBottomNums.innerHTML = ""; //clear all children
3964 
3965  var thresholdNumberSpacing = 100; //once threshold is reached another number is shown
3966  var numberLoc = []; //used to keep track of spacing
3967  var oldNumberLoc = [-thresholdNumberSpacing,-thresholdNumberSpacing]; //used to keep track of spacing
3968  var numberEl;
3969  var translatedRC;
3970 
3971  //outer box
3972  bmpGridChildren[0].style.left = 0 + "px";
3973  bmpGridChildren[0].style.top = 0 + "px";
3974  bmpGridChildren[0].style.width = (bmpW) + "px";
3975  bmpGridChildren[0].style.height = (bmpH) + "px";
3976 
3977  //place rows
3978  for(var i=0;i<rows;++i)
3979  {
3980  if(i<rows-1)
3981  {
3982  //dark
3983  bmpGridChildren[1+i*2].style.left = bmpBorderSize + "px";
3984  bmpGridChildren[1+i*2].style.top = ((i+1)*cellH) + "px";//((i+1)*cellH + bmpBorderSize) + "px";
3985  bmpGridChildren[1+i*2].style.width = (bmpW) + "px";
3986  bmpGridChildren[1+i*2].style.height = (bmpGridThickness+bmpBorderSize*2) + "px";
3987 
3988  //light
3989  bmpGridChildren[1+i*2+1].style.left = 0 + "px";
3990  bmpGridChildren[1+i*2+1].style.top = ((i+1)*cellH + bmpBorderSize) + "px";//((i+1)*cellH + bmpBorderSize*2) + "px";
3991  bmpGridChildren[1+i*2+1].style.width = (bmpW + bmpBorderSize*2) + "px";
3992  bmpGridChildren[1+i*2+1].style.height = bmpGridThickness + "px";//((doSnakeRows && i%2 == 1)?0:bmpGridThickness) + "px";
3993 
3994  bmpGridChildren[1+i*2+1].style.backgroundColor = //change color if snaking
3995  (doSnakeRows && i%2 == 1)?"rgb(100,100,100)":"#efeaea";
3996  }
3997 
3998  //row button
3999  allRowsChildren[i].style.left = 0 + "px";
4000  allRowsChildren[i].style.top = (i*cellH + (i?bmpGridThickness+bmpBorderSize*2-1:0)) + "px";
4001  allRowsChildren[i].style.width = (butttonSz) + "px";
4002  allRowsChildren[i].style.height = (cellH - 1 + (i?-bmpBorderSize*2:0)) + "px";
4003 
4004  //numbers
4005  {
4006  numberLoc[0] = (i*cellH - 1 + cellH/2 - numberDigitH/2 + (i?bmpGridThickness+bmpBorderSize*2:0));
4007 
4008  //rowLeft numbers
4009  translatedRC = localConvertGridToRowCol(i,0);
4010  if(numberLoc[0] - oldNumberLoc[0] >= thresholdNumberSpacing &&
4011  translatedRC[0]%5 == 0)
4012  {
4013  //add a number
4014  numberEl = document.createElement("div");
4015  numberEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-number");
4016  numberEl.innerHTML = translatedRC[0];
4017  numberEl.style.top = numberLoc[0] + "px";
4018  numberEl.style.width = axisPaddingExtra + "px";
4019  rowLeftNums.appendChild(numberEl);
4020  oldNumberLoc[0] = numberLoc[0];
4021  }
4022 
4023  //rowRight numbers
4024  translatedRC = localConvertGridToRowCol(i,cols>1?1:0);
4025  if(numberLoc[0] - oldNumberLoc[1] >= thresholdNumberSpacing &&
4026  translatedRC[0]%5 == 0)
4027  {
4028  //add a number
4029  numberEl = document.createElement("div");
4030  numberEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-number");
4031  numberEl.innerHTML = translatedRC[0];
4032  numberEl.style.top = numberLoc[0] + "px";
4033  numberEl.style.width = axisPaddingExtra + "px";
4034  rowRightNums.appendChild(numberEl);
4035  oldNumberLoc[1] = numberLoc[0];
4036  }
4037  }
4038  }
4039 
4040  oldNumberLoc = [-thresholdNumberSpacing,-thresholdNumberSpacing]; //reset, used to keep track of spacing
4041  //place cols
4042  for(var i=0;i<cols;++i)
4043  {
4044  if(i<cols-1)
4045  {
4046  //if snaking cols, then darken every other
4047  // by making light width = 0
4048 
4049  //dark
4050  bmpGridChildren[1+(rows-1)*2+i*2].style.top = bmpBorderSize + "px";
4051  bmpGridChildren[1+(rows-1)*2+i*2].style.left = ((i+1)*cellW + bmpBorderSize) + "px";
4052  bmpGridChildren[1+(rows-1)*2+i*2].style.height = (bmpH) + "px";
4053  bmpGridChildren[1+(rows-1)*2+i*2].style.width = (bmpGridThickness+bmpBorderSize*2) + "px";
4054 
4055  //light
4056  bmpGridChildren[1+(rows-1)*2+i*2+1].style.top = 0 + "px";
4057  bmpGridChildren[1+(rows-1)*2+i*2+1].style.left = ((i+1)*cellW + bmpBorderSize*2) + "px";
4058  bmpGridChildren[1+(rows-1)*2+i*2+1].style.height = (bmpH + bmpBorderSize*2) + "px";
4059  bmpGridChildren[1+(rows-1)*2+i*2+1].style.width = bmpGridThickness + "px"; //((doSnakeColumns && i%2 == 1)?0:bmpGridThickness) + "px";
4060 
4061  bmpGridChildren[1+(rows-1)*2+i*2+1].style.backgroundColor = //change color if snaking
4062  (doSnakeColumns && i%2 == 1)?"rgb(100,100,100)":"#efeaea";
4063  }
4064 
4065  //row button
4066  allColsChildren[i].style.left = (i*cellW - 1 + (i?bmpGridThickness+bmpBorderSize*2:0)) + "px";
4067  allColsChildren[i].style.top = 0 + "px";
4068  allColsChildren[i].style.width = (cellW + 1 - (i?bmpGridThickness+bmpBorderSize*2:0)) + "px";
4069  allColsChildren[i].style.height = (butttonSz) + "px";
4070 
4071  //numbers
4072  {
4073  numberLoc[0] = (i*cellW + cellW/2 - axisPaddingExtra/2 + (i?bmpGridThickness+bmpBorderSize*2:0));
4074 
4075  //colTop numbers
4076  translatedRC = localConvertGridToRowCol(0,i);
4077  if(numberLoc[0] - oldNumberLoc[0] >= thresholdNumberSpacing &&
4078  translatedRC[1]%5 == 0)
4079  {
4080  //add a number
4081  numberEl = document.createElement("div");
4082  numberEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-number");
4083  numberEl.innerHTML = translatedRC[1];
4084  numberEl.style.left = numberLoc[0] + "px";
4085  numberEl.style.width = axisPaddingExtra + "px";
4086  colTopNums.appendChild(numberEl);
4087  oldNumberLoc[0] = numberLoc[0];
4088  }
4089 
4090  //colBottom numbers
4091  translatedRC = localConvertGridToRowCol(rows>1?1:0,i);
4092  if(numberLoc[0] - oldNumberLoc[1] >= thresholdNumberSpacing &&
4093  translatedRC[1]%5 == 0)
4094  {
4095  //add a number
4096  numberEl = document.createElement("div");
4097  numberEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-number");
4098  numberEl.innerHTML = translatedRC[1];
4099  numberEl.style.left = numberLoc[0] + "px";
4100  numberEl.style.width = axisPaddingExtra + "px";
4101  colBottomNums.appendChild(numberEl);
4102  oldNumberLoc[1] = numberLoc[0];
4103  }
4104  }
4105  }
4106  }
4107 
4108 
4109 
4110 
4111 
4112 
4113  } //end localPaint()
4114 
4115  //:::::::::::::::::::::::::::::::::::::::::
4116  //localOptimizeAspectRatio ~~
4117  // inputs are cellW/cellH, bmpW/bmpH, rows/cols
4118  //
4119  // optimize aspect ratio for viewing window
4120  // hdr and bmp positions are known after this
4121  function localOptimizeAspectRatio()
4122  {
4123  var cellSkew = (cellW>cellH)?cellW/cellH:cellH/cellW;
4124  var MAX_SKEW = 3;
4125 
4126 
4127  if(forcedAspectH !== undefined)
4128  {
4129  var offAspectH = forcedAspectH/cellH;
4130  var offAspectW = forcedAspectW/cellW;
4131 
4132  Debug.log("Adjusting skew factor = " + forcedAspectH + "-" + forcedAspectW);
4133 
4134  if(offAspectH < offAspectW) //height is too big
4135  bmpH = bmpW/cols*forcedAspectH/forcedAspectW*rows;
4136  else //width is too big
4137  bmpW = bmpH/rows*forcedAspectW/forcedAspectH*cols;
4138  }
4139  else if(cellSkew > MAX_SKEW) //re-adjust bitmap
4140  {
4141  var adj = cellSkew/MAX_SKEW;
4142  //to much skew in cell shape.. so let's adjust
4143  Debug.log("Adjusting skew factor = " + adj);
4144  if(cellW > cellH)
4145  {
4146  bmpW /= adj;
4147  }
4148  else
4149  bmpH /= adj;
4150  }
4151  //recalculate new cells
4152  cellW = bmpW/cols;
4153  cellH = bmpH/rows;
4154 
4155  //center bitmap
4156  bmpX = padding + (popSz.w-bmpW)/2;
4157  bmpY = bmpY + (popSz.h-bmpY-bmpH)/2;
4158  hdrY = bmpY - padding - hdrH;
4159  } //end localOptimizeAspectRatio()
4160 }
4161 
4162 
4163 //=====================================================================================
4164 //getDateString ~~
4165 // Example call from linux timestamp:
4166 // groupCreationTime = ConfigurationAPI.getDateString(new Date((groupCreationTime|0)*1000));
4167 ConfigurationAPI.getDateString;
4168 {
4169 ConfigurationAPI.getDateStringDayArr_ = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
4170 ConfigurationAPI.getDateStringMonthArr_ = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
4171 ConfigurationAPI.getDateString = function(date)
4172 {
4173  var dateStr = "";
4174 
4175  dateStr += ConfigurationAPI.getDateStringDayArr_[date.getDay()];
4176  dateStr += " ";
4177  dateStr += ConfigurationAPI.getDateStringMonthArr_[date.getMonth()];
4178  dateStr += " ";
4179  dateStr += date.getDate();
4180  dateStr += " ";
4181  dateStr += date.getHours();
4182  dateStr += ":";
4183  dateStr += ((date.getMinutes()<10)?"0":"") + date.getMinutes();
4184  dateStr += ":";
4185  dateStr += ((date.getSeconds()<10)?"0":"") + date.getSeconds();
4186  dateStr += " ";
4187  dateStr += date.getFullYear();
4188  dateStr += " ";
4189  dateStr += date.toLocaleTimeString([],{timeZoneName: "short"}).split(" ")[2];
4190  return dateStr;
4191 }
4192 }
4193 
4194 //=====================================================================================
4195 //setCaretPosition ~~
4196 ConfigurationAPI.setCaretPosition = function(elem, caretPos, endPos)
4197 {
4198  elem.focus();
4199  elem.setSelectionRange(caretPos, endPos);
4200 }
4201 
4202 //=====================================================================================
4203 //ConfigurationAPI.removeAllPopUps()
4204 ConfigurationAPI.removeAllPopUps = function()
4205 {
4206  //remove all existing dialogs
4207  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
4208  while(el)
4209  {
4210  el.parentNode.removeChild(el); //close popup
4211  el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
4212  }
4213 } //end ConfigurationAPI.removeAllPopUps()
4214 
4215 //=====================================================================================
4216 //setPopUpPosition ~~
4217 // centers element based on width and height constraint
4218 //
4219 // Note: assumes a padding and border size if not specified
4220 // Note: if w,h not specified then fills screen (minus margin)
4221 // Note: offsetUp and can be used to position the popup vertically (for example if the dialog is expected to grow, then give positive offsetUp to compensate)
4222 ConfigurationAPI.setPopUpPosition = function(el,w,h,padding,border,margin,doNotResize,offsetUp)
4223 {
4224  Debug.log("ConfigurationAPI.setPopUpPosition");
4225 
4226  if(padding === undefined) padding = 10;
4227  if(border === undefined) border = 1;
4228  if(margin === undefined) margin = 0;
4229 
4230  var x,y;
4231 
4232  //:::::::::::::::::::::::::::::::::::::::::
4233  //popupResize ~~
4234  // set position and size
4235  ConfigurationAPI.setPopUpPosition.stopPropagation = function(event) {
4236  //Debug.log("stop propagation");
4237  event.stopPropagation();
4238  }
4239 
4240  //:::::::::::::::::::::::::::::::::::::::::
4241  //popupResize ~~
4242  // set position and size
4243  ConfigurationAPI.setPopUpPosition.popupResize = function() {
4244 
4245  try //check if element still exists
4246  {
4247  if(!el) //if element no longer exists.. then remove listener and exit
4248  {
4249  window.removeEventListener("resize",ConfigurationAPI.setPopUpPosition.popupResize);
4250  window.removeEventListener("scroll",ConfigurationAPI.setPopUpPosition.popupResize);
4251  return;
4252  }
4253  }
4254  catch(err) {return;} //do nothing on errors
4255 
4256  //else resize el
4257  //Debug.log("ConfigurationAPI.setPopUpPosition.popupResize");
4258 
4259 
4260  var ww = DesktopContent.getWindowWidth()-(padding+border)*2;
4261  var wh = DesktopContent.getWindowHeight()-(padding+border)*2;
4262 
4263  //ww & wh are max window size at this point
4264 
4265  var ah = el.offsetHeight;//actual height, in case of adjustments
4266 
4267  if(w === undefined || h === undefined)
4268  {
4269  w = ww-(margin)*2;
4270  h = wh-(margin)*2;
4271  }
4272  //else w,h are inputs and margin is ignored
4273 
4274  x = (DesktopContent.getWindowScrollLeft() + ((ww-w)/2));
4275  y = (DesktopContent.getWindowScrollTop() + ((wh-h)/2)) - (offsetUp|0);
4276  if(y > 110) y -= 100; //bias up (looks nicer)
4277 
4278  if(y<DesktopContent.getWindowScrollTop()+margin)//+padding)
4279  y = DesktopContent.getWindowScrollTop()+margin;//+padding; //don't let it bottom out though
4280 
4281  //if dialog is smaller than window, allow scrolling to see the whole thing
4282  if(w > ww-margin-padding)
4283  x = -DesktopContent.getWindowScrollLeft();
4284  if(ah > wh-margin-padding)
4285  y = -DesktopContent.getWindowScrollTop();
4286 
4287  el.style.left = (x|0) + "px";
4288  el.style.top = (y|0) + "px";
4289  };
4290  ConfigurationAPI.setPopUpPosition.popupResize();
4291 
4292  //window width and height are not manipulated on resize, only setup once
4293  el.style.width = (w|0) + "px";
4294  el.style.height = (h|0) + "px";
4295 
4296  if(!doNotResize)
4297  {
4298  window.addEventListener("resize",ConfigurationAPI.setPopUpPosition.popupResize);
4299  window.addEventListener("scroll",ConfigurationAPI.setPopUpPosition.popupResize);
4300  }
4301  el.addEventListener("keydown",ConfigurationAPI.setPopUpPosition.stopPropagation);
4302  el.addEventListener("mousemove",ConfigurationAPI.setPopUpPosition.stopPropagation);
4303  el.addEventListener("mousemove",DesktopContent.mouseMove);
4304 
4305  el.style.overflow = "auto";
4306 
4307  return {"w" : w, "h" : h, "x" : x, "y" : y};
4308 }
4309 
4310 
4311 //=====================================================================================
4312 //getOnePixelPngData ~~
4313 // alpha is optional, will assume full 255 alpha
4314 ConfigurationAPI.getOnePixelPngData = function(rgba)
4315 {
4316  if(ConfigurationAPI.getOnePixelPngData.canvas === undefined)
4317  {
4318  //create only first time this functino is called
4319  ConfigurationAPI.getOnePixelPngData.canvas = document.createElement("canvas");
4320  ConfigurationAPI.getOnePixelPngData.canvas.width = 1;
4321  ConfigurationAPI.getOnePixelPngData.canvas.height = 1;
4322  ConfigurationAPI.getOnePixelPngData.ctx = ConfigurationAPI.getOnePixelPngData.canvas.getContext("2d");
4323  ConfigurationAPI.getOnePixelPngData.bmpOverlayData = ConfigurationAPI.getOnePixelPngData.ctx.createImageData(1,1);
4324  }
4325 
4326  ConfigurationAPI.getOnePixelPngData.bmpOverlayData.data[0]=rgba[0];
4327  ConfigurationAPI.getOnePixelPngData.bmpOverlayData.data[1]=rgba[1];
4328  ConfigurationAPI.getOnePixelPngData.bmpOverlayData.data[2]=rgba[2];
4329  ConfigurationAPI.getOnePixelPngData.bmpOverlayData.data[3]=rgba[3]!==undefined?rgba[3]:255;
4330 
4331  ConfigurationAPI.getOnePixelPngData.ctx.putImageData(
4332  ConfigurationAPI.getOnePixelPngData.bmpOverlayData,0,0);
4333  return ConfigurationAPI.getOnePixelPngData.canvas.toDataURL();
4334 }
4335 
4336 
4337 //=====================================================================================
4338 //createEditableFieldElement ~~
4339 //
4340 // Creates div element with editable and highlight features.
4341 // Note: set ConfigurationAPI.editableField_SELECTED_COLOR_ = "transparent" to disable highlight features
4342 //
4343 // Input field must be a value node.
4344 // Input object is single field as returned from ConfigurationAPI.getFieldsOfRecords
4345 //
4346 // fieldIndex is unique integer for the field
4347 // depthIndex (optional) is indicator of depth, e.g. for tree display
4348 //
4349 // Field := {}
4350 // obj.fieldTableName
4351 // obj.fieldUID
4352 // obj.fieldColumnName
4353 // obj.fieldRelativePath
4354 // obj.fieldColumnType
4355 // obj.fieldColumnDataType
4356 // obj.fieldColumnDataChoicesArr[]
4357 // obj.fieldColumnDefaultValue
4358 //
4359 ConfigurationAPI.editableFieldEditingCell_ = 0;
4360 ConfigurationAPI.editableFieldEditingIdString_;
4361 ConfigurationAPI.editableFieldEditingNodeType_;
4362 ConfigurationAPI.editableFieldEditingOldValue_;
4363 ConfigurationAPI.editableFieldEditingInitValue_;
4364 ConfigurationAPI.editableFieldHoveringCell_ = 0;
4365 ConfigurationAPI.editableFieldHoveringIdString_;
4366 ConfigurationAPI.editableFieldSelectedIdString_ = 0;
4367 ConfigurationAPI.editableFieldHandlersSubscribed_ = false;
4368 ConfigurationAPI.editableFieldMouseIsSelecting_ = false;
4369 ConfigurationAPI.editableField_SELECTED_COLOR_ = "rgb(251, 245, 53)";
4370 ConfigurationAPI.createEditableFieldElement = function(fieldObj,fieldIndex,
4371  depthIndex /*optional*/)
4372 {
4373  var str = "";
4374  var depth = depthIndex|0;
4375  var uid = fieldIndex|0;
4376 
4377  if(!ConfigurationAPI.editableFieldHandlersSubscribed_)
4378  {
4379  ConfigurationAPI.editableFieldHandlersSubscribed_ = true;
4380 
4381  //be careful to not override the window.onmousemove DesktopContent action
4382  DesktopContent.mouseMoveSubscriber(ConfigurationAPI.handleEditableFieldBodyMouseMove);
4383  }
4384 
4385  var fieldEl = document.createElement("div");
4386  fieldEl.setAttribute("class", "ConfigurationAPI-EditableField");
4387  fieldEl.setAttribute("id", "ConfigurationAPI-EditableField-" +
4388  ( depth + "-" + uid ));
4389 
4390  Debug.log("Field type " + fieldObj.fieldColumnType);
4391  //console.log(fieldObj);
4392 
4393  var valueType = fieldObj.fieldColumnType;
4394  var choices = fieldObj.fieldColumnDataChoicesArr;
4395  var value = fieldObj.fieldColumnDefaultValue;
4396  var path = fieldObj.fieldRelativePath;
4397  var nodeName = fieldObj.fieldColumnName;
4398  fieldObj.depthIndex = depth;
4399  fieldObj.fieldIndex = uid;
4400  fieldObj.fieldColumnValue = value; //track last stable value
4401 
4402  //if childLink, look up isGroupLink,childLinkIndex,linkId
4403  // in matching field
4404  var isGroupLink,childLinkIndex,linkId;
4405  if(valueType.indexOf("ChildLink") == 0)
4406  {
4407  Debug.log("Looking up matching link pair for " + nodeName);
4408 
4409  childLinkIndex = valueType.split('-')[1];
4410  console.log("childLinkIndex",childLinkIndex);
4411 
4412  //should only be one other field with this childLinkIndex
4413  for(var i=0;i<_fields.length;++i)
4414  if(_fields[i].fieldColumnType.indexOf("ChildLink") == 0 &&
4415  (_fields[i].fieldColumnType[("ChildLink").length] == 'U' ||
4416  _fields[i].fieldColumnType[("ChildLink").length] == 'G') &&
4417  childLinkIndex == _fields[i].fieldColumnType.split('-')[1])
4418  {
4419  Debug.log("Found matching pair field " +
4420  _fields[i].fieldColumnName);
4421  if(_fields[i].fieldColumnType[("ChildLink").length] == 'U')
4422  isGroupLink = false; //UID link
4423  else
4424  isGroupLink = true; //GroupID link
4425  linkId = _fields[i].fieldColumnDefaultValue;
4426  break;
4427  }
4428 
4429  if(isGroupLink === undefined)
4430  {
4431  Debug.log("Invalid table! Could not find matching child link columns for " +
4432  nodeName, Debug.HIGH_PRIORITY);
4433  return;
4434  }
4435  } //end special child link handling
4436 
4437  return ConfigurationAPI.fillEditableFieldElement(fieldEl,uid,
4438  depth,nodeName,value,valueType,choices,path,
4439  isGroupLink,childLinkIndex,linkId);
4440 } //end createEditableFieldElement()
4441 
4442 //=====================================================================================
4443 //getEditableFieldValue ~~
4444 // return value is the string value
4445 // loosely based ConfigurationGUI editTreeNodeOK()
4446 ConfigurationAPI.getEditableFieldValue = function(fieldObj,fieldIndex,depthIndex /*optional*/)
4447 {
4448  //Debug.log("getEditableFieldValue " + fieldObj.fieldColumnName + " of type " +
4449  // fieldObj.fieldColumnType);
4450 
4451  ConfigurationAPI.handleEditableFieldEditOK(); //make sure OK|Cancel closed
4452 
4453  //make depthIndex and fieldIndex optional
4454  var depth = fieldObj.depthIndex === undefined?
4455  (depthIndex|0):fieldObj.depthIndex;
4456  var uid = fieldObj.fieldIndex === undefined?
4457  (fieldIndex|0):fieldObj.fieldIndex;
4458 
4459  var fieldEl = document.getElementById("editableFieldNode-Value-leafNode-" +
4460  ( depth + "-" + uid ));
4461  if(!fieldEl)
4462  {
4463  Debug.log("getEditableFieldValue Error! Invalid target field element '" +
4464  ( depth + "-" + uid ), Debug.HIGH_PRIORITY);
4465  return;
4466  }
4467 
4468  var valueType = fieldObj.fieldColumnType;
4469  var value = fieldEl.textContent;
4470 
4471  //Debug.log("get Value " + value);
4472  return value;
4473 } //end getEditableFieldValue()
4474 
4475 //=====================================================================================
4476 //setEditableFieldValue ~~
4477 // set value of a single field element as specified by:
4478 // fieldObj (as returned from ConfigurationAPI.getFieldsOfRecords)
4479 // fieldIndex is unique integer for the field
4480 // depthIndex (optional) is indicator of depth, e.g. for tree display
4481 //
4482 // input value is expected to be a string value
4483 ConfigurationAPI.setEditableFieldValue = function(fieldObj,value,fieldIndex,depthIndex /*optional*/)
4484 {
4485  //Debug.log("setEditableFieldValue " + fieldObj.fieldColumnName + " = " + value);
4486 
4487  //make depthIndex and fieldIndex optional
4488  var depth = fieldObj.depthIndex === undefined?
4489  (depthIndex|0):fieldObj.depthIndex;
4490  var uid = fieldObj.fieldIndex === undefined?
4491  (fieldIndex|0):fieldObj.fieldIndex;
4492 
4493  var fieldEl = document.getElementById("ConfigurationAPI-EditableField-" +
4494  ( depth + "-" + uid ));
4495  if(!fieldEl)
4496  {
4497  Debug.log("setEditableFieldValue Error! Invalid target field element '" +
4498  ( depth + "-" + uid ), Debug.HIGH_PRIORITY);
4499  return;
4500  }
4501  var valueType = fieldObj.fieldColumnType;
4502  var choices = fieldObj.fieldColumnDataChoicesArr;
4503  var path = fieldObj.fieldRelativePath;
4504  var nodeName = fieldObj.fieldColumnName;
4505  fieldObj.fieldColumnValue = value; //track last stable value
4506 
4507  //if childLink, look up isGroupLink,childLinkIndex,linkId
4508  // in matching field
4509  var isGroupLink,childLinkIndex,linkId;
4510  if(valueType.indexOf("ChildLink") == 0)
4511  {
4512  Debug.log("Looking up matching link pair for " + nodeName);
4513 
4514  childLinkIndex = valueType.split('-')[1];
4515  console.log("childLinkIndex",childLinkIndex);
4516 
4517  //should only be one other field with this childLinkIndex
4518  for(var i=0;i<_fields.length;++i)
4519  if(_fields[i].fieldColumnType.indexOf("ChildLink") == 0 &&
4520  (_fields[i].fieldColumnType[("ChildLink").length] == 'U' ||
4521  _fields[i].fieldColumnType[("ChildLink").length] == 'G') &&
4522  childLinkIndex == _fields[i].fieldColumnType.split('-')[1])
4523  {
4524  Debug.log("Found matching pair field " +
4525  _fields[i].fieldColumnName);
4526  if(_fields[i].fieldColumnType[("ChildLink").length] == 'U')
4527  isGroupLink = false; //UID link
4528  else
4529  isGroupLink = true; //GroupID link
4530  linkId = _fields[i].fieldColumnValue;
4531  break;
4532  }
4533 
4534  if(isGroupLink === undefined)
4535  {
4536  Debug.log("Invalid table! Could not find matching child link columns for " +
4537  nodeName, Debug.HIGH_PRIORITY);
4538  return;
4539  }
4540  } //end special child link handling
4541 
4542  return ConfigurationAPI.fillEditableFieldElement(fieldEl,uid,
4543  depth,nodeName,value,valueType,choices,path,
4544  isGroupLink,childLinkIndex,linkId);
4545 } //end setEditableFieldValue()
4546 
4547 //=====================================================================================
4548 //fillEditableFieldElement ~~
4549 // helper to fill element used by setEditableFieldValue and createEditableFieldElement
4550 //
4551 // Caller should append extra parameters (isGroupLink,childLinkIndex,linkId) to ChildLink valueType
4552 // to guide subset links.
4553 ConfigurationAPI.fillEditableFieldElement = function(fieldEl,uid,
4554  depth,nodeName,value,valueType,choices,path,
4555  isGroupLink,childLinkIndex,linkId)
4556 {
4557  var str = "";
4558 
4559  var pathHTML = path;
4560  //make path html safe
4561  pathHTML = pathHTML.replace(/</g, "&lt");
4562  pathHTML = pathHTML.replace(/>/g, "&gt");
4563 
4564  str += "<div class='editableFieldNode-Path' style='display:none' id='editableFieldNode-path-" +
4565  ( depth + "-" + uid ) + "'>" + // end path id
4566  pathHTML + //save path for future use.. and a central place to edit when changes occur
4567  "</div>";
4568 
4569  //track if this is a child link with fixed choice
4570  var childLinkFixedChoice = false; //init, but if it is, then change type handling to fixed choice style
4571  var isChildLink = valueType.indexOf("ChildLink") == 0;
4572 
4573  if(valueType == "FixedChoiceData" ||
4574  (isChildLink && choices.length > 1))
4575  {
4576  //track if this is a child link with fixed choice
4577  childLinkFixedChoice = valueType.indexOf("ChildLink") == 0;
4578 
4579  //add CSV choices div
4580  str +=
4581  "<div class='editableFieldNode-FixedChoice-CSV' style='display:none' " +
4582  "id='editableFieldNode-FixedChoice-CSV-" +
4583  ( depth + "-" + uid ) + "'>";
4584 
4585  for(var j=0;j<choices.length;++j)
4586  {
4587  if(j) str += ",";
4588  str += choices[j];
4589  }
4590  str += "</div>";
4591 
4592 
4593  }
4594  else if(valueType == "BitMap")
4595  {
4596  //add bitmap params div
4597  str +=
4598  "<div class='editableFieldNode-BitMap-Params' style='display:none' " +
4599  "id='editableFieldNode-BitMap-Params-" +
4600  ( depth + "-" + uid ) + "'>";
4601 
4602  for(var j=1;j<choices.length;++j) //skip the first DEFAULT param
4603  {
4604  if(j-1) str += ";"; //assume no ';' in fields, so likely no issue to replace ; with ,
4605  str += choices[j].replace(/;/g,","); //change all ; to , for split safety
4606  }
4607  str += "</div>";
4608  }
4609 
4610  //normal value node and edit icon
4611  {
4612  //start value node
4613  str +=
4614  "<div class='editableFieldNode-Value editableFieldNode-ValueType-" +
4615  //if it is a fixed choice child link, then change type handling to fixed choice style
4616  (childLinkFixedChoice?"ChildLinkFixedChoice":valueType) +
4617  "' " +
4618  "id='editableFieldNode-Value-" +
4619  (depth + "-" + uid) + "' " +
4620 
4621  "onclick='ConfigurationAPI.handleEditableFieldClick(" +
4622  depth + "," + uid + "," +
4623  "0,\"value\")' " +
4624 
4625  "onmousemove='ConfigurationAPI.handleEditableFieldHover(" +
4626  depth + "," + uid + "," +
4627  "event)' " +
4628 
4629  ">";
4630 
4631  titleStr = "~ Leaf Value Node ~\n";
4632  titleStr += "Path: \t" + path + nodeName + "\n";
4633 
4634  //left side of value
4635  str +=
4636  "<div style='float:left' title='" + titleStr + "'>" +
4637  "<b class='editableFieldNode-Value-leafNode-fieldName bold-header'>" +
4638  nodeName + "</b>" +
4639  "</div><div style='float:left'>&nbsp;:</div>";
4640 
4641  //normal edit icon
4642  str +=
4643  "<div class='editableFieldNode-Value-editIcon' id='editableFieldNode-Value-editIcon-" +
4644  (depth + "-" + uid) + "' " +
4645  "onclick='ConfigurationAPI.handleEditableFieldClick(" +
4646  depth + "," + uid + "," +
4647  "1,\"value\"); event.stopPropagation();' " +
4648  "title='Edit the value of this node.' " +
4649  "></div>";
4650  }
4651 
4652  str += "<div style='float:left; margin-left:9px;' id='editableFieldNode-Value-leafNode-" +
4653  (depth + "-" + uid) +
4654  "' class='" +
4655  "editableFieldNode-Value-leafNode-ColumnName-" + nodeName +
4656  "' " +
4657  ">";
4658 
4659 
4660  if(valueType == "OnOff" ||
4661  valueType == "YesNo" ||
4662  valueType == "TrueFalse")
4663  {
4664  //colorize true false
4665  str += "<div style='float:left'>";
4666  str += value;
4667  str += "</div>";
4668 
4669  var color = (value == "On" || value == "Yes" || value == "True")?
4670  "rgb(16, 204, 16)":"rgb(255, 0, 0);";
4671  str += "<div style='width:10px;height:10px;" +
4672  "background-color:" + color + ";" +
4673  "float: left;" +
4674  "border-radius: 7px;" +
4675  "border: 2px solid white;" +
4676  "margin: 2px 0 0 6px;" +
4677  "'></div>";
4678  }
4679  else if(valueType == "Timestamp")
4680  str += ConfigurationAPI.getDateString(new Date((value|0)*1000));
4681  else
4682  str += value;
4683 
4684  str += "</div>";
4685 
4686  //make links to child subset configuration editor
4687  if(isChildLink &&
4688  value.indexOf("Table") == value.length - ("Table").length)
4689  {
4690  //make record alias with spaces instead of all one word
4691  // and remove table
4692  var recordAlias = "";
4693  for(var c=0;c<value.length - ("Table").length;++c)
4694  {
4695  if(c && c+1 < value.length &&
4696  (value[c] >= 'A' &&
4697  value[c] <= 'Z') &&
4698  (value[c+1] >= 'a' &&
4699  value[c+1] <= 'z'))
4700  recordAlias += ' ';
4701  recordAlias += value[c];
4702  }
4703  if(recordAlias.length && recordAlias[recordAlias.length-1] != 's')
4704  recordAlias += 's'; //make plural
4705 
4706  var newWindowStr = "/WebPath/html/ConfigurationGUI_subset.html?urn=" +
4707  DesktopContent._localUrnLid +
4708  "&subsetBasePath=" + value +
4709  "&groupingFieldList=AUTO" +
4710  "&recordAlias=" + recordAlias +
4711  "&editableFieldList=" + "!*CommentDescription";
4712 
4713  //include special parameters to focus on link targets
4714  if(isGroupLink)
4715  {
4716  //for group link, pre-select GroupID value
4717  newWindowStr += "&selectedGroupIDs=" +
4718  encodeURIComponent(
4719  childLinkIndex + "=" +
4720  linkId);
4721 
4722  if(childLinkIndex.split(' ').length >= 2)
4723  childLinkIndex = childLinkIndex.split(' ')[1]; //allow for targeting index syntax
4724  //add childLinkIndex to be used by getting groupId values in target table
4725  str += "<div id='editableFieldNode-ChildLink-groupIndex-" +
4726  (depth + "-" + uid) + "' " +
4727  " style='display:none;' >" +
4728  childLinkIndex + "</div>";
4729  }
4730  else
4731  {
4732  //for unique link, presect UID record
4733  newWindowStr += "&selectedRecords=" + linkId;
4734  }
4735 
4736 
4737  str += "<div style='float:left; margin-left:9px;' " +
4738  " id='editableFieldNode-ChildLink-SubConfigLinkWindow-" +
4739  (depth + "-" + uid) + "' " +
4740  " class='" +
4741  "editableFieldNode-ChildLink-SubConfigLink" +
4742  "' " +
4743  "onclick='" +
4744  "event.stopPropagation(); " +
4745  "DesktopContent.openNewWindow(" +
4746  "\"" + value +
4747  " Subset-Configuration\",\"\",\"" +
4748  //windowPath
4749  newWindowStr +
4750  "\",false /*unique*/);" +
4751  "'" +
4752  " title='Open " + value + " subset configuration in a new desktop window.' " +
4753  ">Open Window</div>";
4754 
4755  str += "<div style='float:left; margin-left:9px;' " +
4756  " id='editableFieldNode-ChildLink-SubConfigLinkTab-" +
4757  (depth + "-" + uid) + "' " +
4758  " class='" +
4759  "editableFieldNode-ChildLink-SubConfigLink" +
4760  "' " +
4761  "onclick='" +
4762  "event.stopPropagation(); " +
4763  "DesktopContent.openNewBrowserTab(" +
4764  "\"" + value +
4765  " Subset-Configuration\",\"\",\"" +
4766  //windowPath
4767  newWindowStr +
4768  "\",false /*unique*/);" +
4769  "'" +
4770  " title='Open " + value + " subset configuration in a new browser tab.' " +
4771  ">Open Tab</div>";
4772  }
4773 
4774  //Debug.log(str);
4775 
4776  fieldEl.innerHTML = str;
4777 
4778  //check if this field is currently the selected field
4779  // if so, setup select color
4780  if(ConfigurationAPI.editableFieldSelectedIdString_ == (depth + "-" + uid))
4781  fieldEl.getElementsByClassName("editableFieldNode-Value")[0].style.backgroundColor =
4782  ConfigurationAPI.editableField_SELECTED_COLOR_;
4783 
4784  return fieldEl;
4785 } //end fillEditableFieldElement()
4786 
4787 //=====================================================================================
4788 //handleEditableFieldClick ~~
4789 // handler for click event for editable field elements
4790 //
4791 // copied from ConfigurationGUI.html handleTreeNodeClick() ..but only
4792 // defines functionality for value nodes.
4793 ConfigurationAPI.handleEditableFieldClick = function(depth,uid,editClick,type)
4794 {
4795  var idString = depth + "-" + uid;
4796  ConfigurationAPI.editableFieldEditingIdString_ = idString;
4797 
4798  Debug.log("handleEditableFieldClick editClick " + editClick);
4799  Debug.log("handleEditableFieldClick idString " + idString);
4800 
4801  var el = document.getElementById("editableFieldNode-Value-" + idString);
4802 
4803  if(!el)
4804  {
4805  Debug.log("Invalid element pointed to by idString. Ignoring and exiting.");
4806  return;
4807  }
4808 
4809  if(ConfigurationAPI.editableFieldHoveringCell_)
4810  {
4811  //Debug.log("handleTreeNodeClick editClick clearing ");
4812  ConfigurationAPI.handleEditableFieldBodyMouseMove();
4813  }
4814 
4815  if(ConfigurationAPI.editableFieldEditingCell_) //already have the edit box open, cancel it
4816  {
4817  if(ConfigurationAPI.editableFieldEditingCell_ == el) //if same cell do nothing
4818  return true;
4819  ConfigurationAPI.handleEditableFieldEditOK(); //if new cell, click ok on old cell before continuing
4820  }
4821 
4822  var path = document.getElementById("editableFieldNode-path-" + idString).textContent;
4823 
4824  // Debug.log("handleEditableFieldClick el " + el.innerHTML);
4825  //Debug.log("handleEditableFieldClick idString " + idString);
4826  //Debug.log("handleEditableFieldClick uid " + uid);
4827  // Debug.log("handleEditableFieldClick nodeName " + nodeName);
4828  Debug.log("handleEditableFieldClick path " + path);
4829  //Debug.log("handleEditableFieldClick editClick " + editClick);
4830  Debug.log("handleEditableFieldClick type " + type);
4831 
4832  //determine type clicked:
4833  // - value
4834  //
4835  //allow different behavior for each depending on single or edit(2x) click
4836  // - value
4837  // 1x = select node
4838  // 2x = edit record Value mode (up/down tab/shtab enter esc active)
4839  //
4840  //on tree node edit OK
4841  // - value
4842  // save value back to field element
4843  //
4844  //on tree node edit cancel
4845  // return previous value back to field element
4846 
4847 
4848  //==================
4849  //take action based on editClick and type string:
4850  // - value
4851  //
4852  //params:
4853  // (el,depth,uid,path,editClick,type,delayed)
4854 
4855  if(editClick) //2x click
4856  {
4857 
4858  if(type == "value")
4859  {
4860  //edit ID (no keys active)
4861  Debug.log("edit value mode");
4862 
4863  selectThisTreeNode(idString,type);
4864  //==================
4865  function selectThisTreeNode(idString,type)
4866  {
4867  //edit column entry in record
4868  // data type matters here, also don't edit author, timestamp
4869  var el = document.getElementById("editableFieldNode-Value-leafNode-" + idString);
4870  var vel = document.getElementById("editableFieldNode-Value-" + idString);
4871 
4872  //if value node, dataType is in element class name
4873  var colType = vel.className.split(' ')[1].split('-');
4874  if(colType[1] == "ValueType")
4875  colType = colType[2];
4876 
4877  var fieldName = el.className.substr(("editableFieldNode-Value-leafNode-ColumnName-").length);
4878 
4879  Debug.log("fieldName=" + fieldName);
4880  Debug.log("colType=" + colType);
4881 
4882  if(colType == "Author" ||
4883  colType == "Timestamp")
4884  {
4885  Debug.log("Can not edit Author or Timestamp fields.",
4886  Debug.WARN_PRIORITY);
4887  return false;
4888  }
4889  else if(colType == "GroupID")
4890  DesktopContent.tooltip("GroupID Editing",
4891  "The GroupID field places this record in one or more " +
4892  "parent group link collections. The value must match the parent's value " +
4893  "in the parent's LinkGroupID field. \n\nTo speficify that this record is in " +
4894  "more than one group, use the '|' (vertical bar) character. For example, " +
4895  "'Parent0Group | Parent1Group' would place this record in two groups (the " +
4896  "Parent0Group and Parent1Group).");
4897 
4898  var str = "";
4899  var optionIndex = -1;
4900 
4901 
4902  if(colType == "YesNo" ||
4903  colType == "TrueFalse" ||
4904  colType == "OnOff") //if column type is boolean, use dropdown
4905  {
4906  type += "-bool";
4907  ConfigurationAPI.editableFieldEditingOldValue_ = el.innerHTML;
4908 
4909  var initVal = el.childNodes[0].textContent;
4910  ConfigurationAPI.editableFieldEditingInitValue_ = initVal;
4911 
4912  var boolVals = [];
4913  if(colType == "YesNo")
4914  boolVals = ["No","Yes"];
4915  else if(colType == "TrueFalse")
4916  boolVals = ["False","True"];
4917  else if(colType == "OnOff")
4918  boolVals = ["Off","On"];
4919 
4920 
4921  str += "<select onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
4922  "onmousedown='ConfigurationAPI.editableFieldMouseIsSelecting_ = true; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);' " +
4923  "onmouseup='ConfigurationAPI.editableFieldMouseIsSelecting_ = false; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_); event.stopPropagation();' " +
4924  "onclick='event.stopPropagation();'" +
4925  "style='margin:-8px -2px -2px -1px; height:" + (el.offsetHeight+6) + "px'>";
4926  for(var i=0;i<boolVals.length;++i)
4927  {
4928  str += "<option value='" + boolVals[i] + "'>";
4929  str += boolVals[i]; //can display however
4930  str += "</option>";
4931  if(boolVals[i] == initVal)
4932  optionIndex = i; //get starting sel index
4933  }
4934  str += "</select>";
4935  if(optionIndex == -1) optionIndex = 0; //use False option by default
4936  }
4937  else if(colType == "FixedChoiceData" ||
4938  colType == "ChildLinkFixedChoice")
4939  {
4940  if(colType == "ChildLinkFixedChoice")
4941  type += "-childLink";
4942 
4943  ConfigurationAPI.editableFieldEditingOldValue_ = el.textContent;
4944  ConfigurationAPI.editableFieldEditingInitValue_ = ConfigurationAPI.editableFieldEditingOldValue_;
4945 
4946  var allowFixedChoiceArbitraryEdit = false;
4947  var optionCount = -1;
4948  optionIndex = 0; //default to default
4949 
4950  str += "<div onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
4951  "onmouseup='event.stopPropagation();' " +
4952  "onclick='event.stopPropagation();' " +
4953  "style='" +
4954  "white-space:nowrap;" +
4955  "margin:-3px -2px -2px -1px;" +
4956  "height:" + (el.offsetHeight+6) + "px'>";
4957 
4958  str += "<select onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
4959  "id='fixedChoice-editSelectBox' " +
4960  "onmouseup='event.stopPropagation();' " +
4961  "onclick='event.stopPropagation();' " +
4962  "style='" +
4963  "float:left;" +
4964  "margin:-2px -2px -2px -1px; height:" +
4965  (el.offsetHeight+6) + "px'>";
4966 
4967  //default value is assumed in list
4968 
4969  var vel = document.getElementById("editableFieldNode-FixedChoice-CSV-" +
4970  idString);
4971  var choices = vel.textContent.split(',');
4972 
4973  var isChildLinkFixedChoice = colType == "ChildLinkFixedChoice";
4974 
4975  if(isChildLinkFixedChoice)
4976  {
4977  try
4978  {
4979  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkTab-" +
4980  (depth + "-" + uid) ).style.display = "none";
4981  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkWindow-" +
4982  (depth + "-" + uid) ).style.display = "none";
4983  }
4984  catch(e) {} //ignore errors
4985  }
4986 
4987  if(choices.length > 1 &&
4988  choices[1].indexOf("arbitraryBool=") == 0)
4989  {
4990  //found arbitraryBool flag
4991  allowFixedChoiceArbitraryEdit =
4992  choices[1][("arbitraryBool=").length] == "1"?
4993  true:false;
4994  Debug.log("allowFixedChoiceArbitraryEdit " + allowFixedChoiceArbitraryEdit);
4995  }
4996 
4997  for(var i=0;i<choices.length;++i)
4998  {
4999  if(i == 0 && isChildLinkFixedChoice && !allowFixedChoiceArbitraryEdit)
5000  {
5001  //skip default if child link fixed choice does not allow arbitrary edit
5002  continue;
5003  }
5004  else if(i==1)//check for arbitraryBool flag
5005  {
5006  if(choices[i].indexOf("arbitraryBool=") == 0)
5007  {
5008  //found arbitraryBool flag, skip it
5009  continue;
5010  }
5011  else
5012  {
5013  //else no flag found, so treat as fixed choice option
5014  ++optionCount; //count as an option in dropdown
5015  }
5016  }
5017  else
5018  ++optionCount; //count as an option in dropdown
5019 
5020 
5021  str += "<option>";
5022  str += decodeURIComponent(choices[i]); //can display however
5023  str += "</option>";
5024  if(decodeURIComponent(choices[i])
5025  == ConfigurationAPI.editableFieldEditingOldValue_)
5026  optionIndex = optionCount; //save selected index
5027  }
5028  str += "</select>";
5029 
5030  if(allowFixedChoiceArbitraryEdit)
5031  {
5032  var ww = (el.offsetWidth-6);
5033  if(ww < 150) ww = 150;
5034  str += "<input type='text' " +
5035  "id='fixedChoice-editTextBox' " +
5036  "style='display:none;" +
5037  "float:left;" +
5038  "margin:-2px 0 -" + (el.offsetHeight+6) + "px 0;" +
5039  "width:" +
5040  ww + "px; height:" + (el.offsetHeight+6) + "px" +
5041  "' " + //end style
5042  "></input>";
5043 
5044  str += "<div style='display:block;" +
5045  "margin: -2px 0 -7px 14px;" +
5046  "' " +
5047  "class='editableFieldNode-Value-editIcon' id='fixedChoice-editIcon" +
5048  "' " +
5049  "onclick='ConfigurationAPI.handleEditableFieldFixedChoiceEditToggle();' " +
5050  "title='Toggle free-form editing' " +
5051  "></div>";
5052  }
5053  str += "</div>";
5054  }
5055  else if(colType == "BitMap")
5056  {
5057  Debug.log("Handling bitmap select");
5058 
5059  ConfigurationAPI.editableFieldEditingOldValue_ = el.textContent;
5060 
5061  //let API bitmap dialog handle it
5062  ConfigurationAPI.bitMapDialog(
5063  //_editingCellElOldTitle, //field name
5064  "Target Field: &quot;" +
5065  fieldName_ + "&quot;",
5066  document.getElementById("editableFieldNode-BitMap-Params-" +
5067  idString).textContent.split(';'),
5068  ConfigurationAPI.editableFieldEditingOldValue_,
5069  function(val)
5070  {
5071  Debug.log("yes " + val);
5072  el.innerHTML = "";
5073  el.appendChild(document.createTextNode(val));
5074  ConfigurationAPI.editableFieldEditingCell_ = el;
5075 
5076  type += "-bitmap";
5077  editTreeNodeOK();
5078 
5079  },
5080  function() //cancel handler
5081  {
5082  //remove the editing cell selection
5083  Debug.log("cancel bitmap");
5084  ConfigurationAPI.editableFieldEditingCell_ = 0;
5085  });
5086  return true;
5087  }
5088  else if(colType == "MultilineData")
5089  {
5090  ConfigurationAPI.editableFieldEditingOldValue_ = el.textContent;
5091  ConfigurationAPI.editableFieldEditingInitValue_ = ConfigurationAPI.editableFieldEditingOldValue_;
5092 
5093  str += "<textarea rows='4' onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' cols='50' style='font-size: 14px; " +
5094  "margin:-8px -2px -2px -1px;width:" +
5095  (el.offsetWidth-6) + "px; height:" + (el.offsetHeight-8) + "px' ";
5096  str += " onmousedown='ConfigurationAPI.editableFieldMouseIsSelecting_ = true; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);' " +
5097  "onmouseup='ConfigurationAPI.editableFieldMouseIsSelecting_ = false; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);event.stopPropagation();' " +
5098  "onclick='event.stopPropagation();'" +
5099  ">";
5100  str += ConfigurationAPI.editableFieldEditingOldValue_;
5101  str += "</textarea>";
5102  }
5103  else // normal cells, with text input
5104  {
5105  ConfigurationAPI.editableFieldEditingOldValue_ = el.textContent;
5106  ConfigurationAPI.editableFieldEditingInitValue_ = ConfigurationAPI.editableFieldEditingOldValue_;
5107 
5108 
5109  if(colType == "GroupID") //track type if it is groupid field
5110  type += "-groupid";
5111  else if(colType == "ChildLinkGroupID" || colType == "ChildLinkUID")
5112  {
5113  type += "-" + colType;
5114 
5115  //get unique values of the groupID field, for all records
5116 
5117  //assume table is in value before this field
5118  var tableIdString = depth + "-" + ((uid|0)-1);
5119  var tableEl = document.getElementById("editableFieldNode-Value-leafNode-" +
5120  tableIdString);
5121  if(tableEl)
5122  {
5123  //put str now, just to give user visual feedback from click
5124  // because loading select box make take a little time
5125  ConfigurationAPI.editableFieldEditingCell_ = el;
5126  ConfigurationAPI.editableFieldEditingNodeType_ = type;
5127  el.innerHTML = str +
5128  localFinishUpTextValueCell();
5129  if(colType == "ChildLinkUID")
5130  {
5131  localHandleDropdownForLinkUID(tableEl); //localFinishUpSelectCell() called at completion
5132  return;
5133  }
5134  else
5135  {
5136  //pass groupID column index to look for in target table
5137 
5138  var indexEl = document.getElementById(
5139  "editableFieldNode-ChildLink-groupIndex-" +
5140  tableIdString);
5141  if(indexEl)
5142  {
5143  localHandleDropdownForLinkGroupID(
5144  tableEl.innerText,
5145  indexEl.innerText); //localFinishUpSelectCell() called at completion
5146  return;
5147  }
5148  else
5149  Debug.log("Could not find matching group link index at ",tableIdString);
5150  }
5151 
5152  }
5153  else
5154  Debug.log("Could not find matching table field at ",tableIdString);
5155  }
5156 
5157  str += localFinishUpTextValueCell();
5158 
5159 
5160  }
5161 
5162  localFinishUpSelectCell();
5163  return;
5164 
5165 
5167  function localFinishUpTextValueCell()
5168  {
5169  Debug.log("localFinishUpTextValueCell()");
5170 
5171 
5172  var ow = el.offsetWidth+6;
5173  if(ow < 150) //force a minimum input width
5174  ow = 150;
5175  var str = "";
5176  str += "<input type='text' onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event);' style='margin:-8px -2px -2px -1px;width:" +
5177  (ow) + "px; height:" + (el.offsetHeight>20?el.offsetHeight:20) + "px' value='";
5178  str += ConfigurationAPI.editableFieldEditingOldValue_;
5179  str += "' onmousedown='ConfigurationAPI.editableFieldMouseIsSelecting_ = true; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);' " +
5180  "onmouseup='ConfigurationAPI.editableFieldMouseIsSelecting_ = false; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);event.stopPropagation();' " +
5181  "onclick='event.stopPropagation();'" +
5182  ">";
5183  return str;
5184  } //end localFinishUpTextValueCell()
5185 
5187  function localFinishUpSelectCell()
5188  {
5189  Debug.log("localFinishUpSelectCell()");
5190 
5191  str += ConfigurationAPI._OK_CANCEL_DIALOG_STR;
5192 
5193  el.innerHTML = str;
5194 
5195 
5196  //handle default selection
5197  if(colType == "YesNo" ||
5198  colType == "TrueFalse" ||
5199  colType == "OnOff") //if column type is boolean, use dropdown
5200  { //select initial value
5201  el.getElementsByTagName("select")[0].selectedIndex = optionIndex;
5202  el.getElementsByTagName("select")[0].focus();
5203  }
5204  else if(colType == "FixedChoiceData" ||
5205  colType == "ChildLinkFixedChoice" ||
5206  colType == "ChildLinkGroupID" ||
5207  colType == "ChildLinkUID")
5208  {
5209  try
5210  {
5211  el.getElementsByTagName("select")[0].selectedIndex = optionIndex;
5212  el.getElementsByTagName("select")[0].focus();
5213  }
5214  catch(e) {
5215  //ignore error and assume text box
5216  ConfigurationAPI.setCaretPosition(el.getElementsByTagName("input")[0],0,ConfigurationAPI.editableFieldEditingOldValue_.length);
5217  }
5218 
5219  }
5220  else if(colType == "MultilineData")
5221  ConfigurationAPI.setCaretPosition(el.getElementsByTagName("textarea")[0],0,ConfigurationAPI.editableFieldEditingOldValue_.length);
5222  else //select text in new input
5223  ConfigurationAPI.setCaretPosition(el.getElementsByTagName("input")[0],0,ConfigurationAPI.editableFieldEditingOldValue_.length);
5224 
5225 
5226  //wrapping up
5227  ConfigurationAPI.editableFieldEditingCell_ = el;
5228  ConfigurationAPI.editableFieldEditingNodeType_ = type;
5229  } //end localFinishUpSelectCell()
5230 
5232  function localHandleDropdownForLinkUID()
5233  {
5234  Debug.log("localHandleDropdownForLinkUID()",tableEl.innerText);
5235 
5236  //get all UIDs
5237  ConfigurationAPI.getSubsetRecords(tableEl.innerText,
5238  undefined /*filterList*/,
5239  function (records)
5240  {
5241 
5242  Debug.logv({records});
5243  if(!records || !records.length)
5244  {
5245  //could not find any records so just show text value
5246  Debug.log("Aborting link group ID search.");
5247  str += localFinishUpTextValueCell();
5248  localFinishUpSelectCell();
5249  return;
5250  }
5251 
5252  //create dropdown
5253  str += "<div onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
5254  "onmouseup='event.stopPropagation();' " +
5255  "onclick='event.stopPropagation();' " +
5256  "style='" +
5257  "white-space:nowrap;" +
5258  "margin:-3px -2px -2px -1px;" +
5259  "height:" + (el.offsetHeight+6) + "px'>";
5260 
5261  str += "<select onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
5262  "id='fixedChoice-editSelectBox' " +
5263  "onmouseup='event.stopPropagation();' " +
5264  "onclick='event.stopPropagation();' " +
5265  "style='" +
5266  "float:left;" +
5267  "margin:-2px -2px -2px -1px; height:" +
5268  (el.offsetHeight+6) + "px'>";
5269 
5270  for (var i = 0; i < records.length; ++i)
5271  {
5272  str += "<option value='" + records[i]
5273  + "'>";
5274  str += records[i]; //can display however
5275  str += "</option>";
5276  if (records[i]
5277  == ConfigurationAPI.editableFieldEditingOldValue_)
5278  optionIndex = i; //get starting sel index
5279  }
5280  //if not found, add current tmp value (probably user created recently)
5281  if(optionIndex == -1 &&
5282  ConfigurationAPI.editableFieldEditingOldValue_.length)
5283  {
5284  str += "<option value='" + encodeURIComponent(
5285  ConfigurationAPI.editableFieldEditingOldValue_)
5286  + "'>";
5287  str += encodeURIComponent(ConfigurationAPI.editableFieldEditingOldValue_); //can display however
5288  str += "</option>";
5289  optionIndex = records.length;
5290  }
5291  str += "</select>";
5292 
5293  //add arbitrary edit
5294  if (true)
5295  {
5296  var ww = (el.offsetWidth-6);
5297  if(ww < 150) ww = 150;
5298  str += "<input type='text' " +
5299  "id='fixedChoice-editTextBox' " +
5300  "style='display:none;" +
5301  "float:left;" +
5302  "margin:-2px 0 -" + (el.offsetHeight+6) + "px 0;" +
5303  "width:" +
5304  ww + "px; height:" + (el.offsetHeight+6) + "px" +
5305  "' " + //end style
5306  "></input>";
5307 
5308  str += "<div style='display:block;" +
5309  "margin: -2px 0 -7px 14px;" +
5310  "' " +
5311  "class='editableFieldNode-Value-editIcon' id='fixedChoice-editIcon" +
5312  "' " +
5313  "onclick='ConfigurationAPI.handleEditableFieldFixedChoiceEditToggle();' " +
5314  "title='Toggle free-form editing' " +
5315  "></div>";
5316  }
5317 
5318  str += "</div>";
5319 
5320  //finish up
5321  Debug.log("Finishing up select cell handling...");
5322  localFinishUpSelectCell();
5323  }); //end getSubsetRecords
5324 
5325  //successfully requested records
5326  return true;
5327 
5328  } //end localHandleDropdownForLinkUID()
5329 
5331  function localHandleDropdownForLinkGroupID(targetTable,childLinkIndex)
5332  {
5333  Debug.log("localHandleDropdownForLinkGroupID()",
5334  targetTable,
5335  childLinkIndex);
5336 
5337  //ready to get matching groupID field in destination table
5338  DesktopContent.XMLHttpRequest("Request?RequestType=getSpecificTable" +
5339  "&dataOffset=0&chunkSize=" + 0 +
5340  "&tableName=" + targetTable +
5341  "&version=" + "-1", //get mockup
5342  "",
5343  function (req)
5344  {
5345 
5346  var err = DesktopContent.getXMLValue(req, "Error");
5347  if (err)
5348  {
5349  Debug.log(err, Debug.HIGH_PRIORITY);
5350 
5351  Debug.log("There were errors loading the mockup version of '" +
5352  targetTable + "'.\n\n" +
5353  "Please see the detailed Errors below:", Debug.HIGH_PRIORITY);
5354 
5355  //try to power through
5356  }
5357 
5358  var warns = DesktopContent.getXMLValue(req, "TableWarnings");
5359  if (warns)
5360  Debug.log("There were warnings found when loading table '" +
5361  targetTable +
5362  "' (you can treat them as informational):\n\n" +
5363  warns, Debug.WARN_PRIORITY);
5364 
5365  var colTypeEls = req.responseXML.getElementsByTagName("CurrentVersionColumnHeaders")[0].getElementsByTagName("ColumnType");
5366  var colHdrs = req.responseXML.getElementsByTagName("CurrentVersionColumnHeaders")[0].getElementsByTagName("ColumnHeader");
5367 
5368  Debug.log("Looking for groupID column in linked to table matching " +
5369  childLinkIndex);
5370 
5371  var groupIdCol = -1;
5372  var groupIdField;
5373  for (var i = 0; i < colTypeEls.length; ++i)
5374  if (colTypeEls[i].getAttribute("value").split('-')[1] == childLinkIndex)
5375  {
5376  groupIdField = colHdrs[i].getAttribute("value");
5377  Debug.log("Found link groupid col " + i + " - " + groupIdField);
5378  groupIdCol = i;
5379  break;
5380  }
5381 
5382  var groupIds = [];
5383  if (groupIdCol == -1)
5384  {
5385  Debug.log("No matching GroupID column found at linked to table!", Debug.HIGH_PRIORITY);
5386  }
5387  else
5388  {
5389 
5390  //get unique values of the groupID field, for all records
5391  // a la ConfigurationGUI.html:L7868
5392  ConfigurationAPI.getSubsetRecords(tableEl.innerText,
5393  undefined /*filterList*/,
5394  function (records)
5395  {
5396 
5397  Debug.logv({records});
5398  if(!records || !records.length)
5399  {
5400  //could not find any records so just show text value
5401  Debug.log("Aborting link group ID search.");
5402  str += localFinishUpTextValueCell();
5403  localFinishUpSelectCell();
5404  return;
5405  }
5406 
5407  ConfigurationAPI.getUniqueFieldValuesForRecords(
5408  tableEl.innerText,
5409  records,
5410  groupIdField,
5411  function (obj)
5412  {
5413  Debug.logv({obj});
5414 
5415  if(!obj || !obj.length)
5416  {
5417  //could not find any records so just show text value
5418  Debug.log("Aborting link group ID search.");
5419  str += localFinishUpTextValueCell();
5420  localFinishUpSelectCell();
5421  return;
5422  }
5423 
5424  try
5425  {
5426  groupIds = obj[0].fieldUniqueValueArray;
5427  }
5428  catch (e)
5429  {
5430  Debug.log("Caught error: " + e);
5431  }
5432 
5433  localFinishGroupIdDropdown();
5434 
5435  }); //end getUniqueFieldValuesForRecords
5436  }); //end getSubsetRecords
5437  }
5438 
5440  function localFinishGroupIdDropdown()
5441  {
5442  //create dropdown
5443  str += "<div onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
5444  "onmouseup='event.stopPropagation();' " +
5445  "onclick='event.stopPropagation();' " +
5446  "style='" +
5447  "white-space:nowrap;" +
5448  "margin:-3px -2px -2px -1px;" +
5449  "height:" + (el.offsetHeight+6) + "px'>";
5450 
5451  str += "<select onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
5452  "id='fixedChoice-editSelectBox' " +
5453  "onmouseup='event.stopPropagation();' " +
5454  "onclick='event.stopPropagation();' " +
5455  "style='" +
5456  "float:left;" +
5457  "margin:-2px -2px -2px -1px; height:" +
5458  (el.offsetHeight+6) + "px'>";
5459  for (var i = 0; i < groupIds.length; ++i)
5460  {
5461  str += "<option value='" + groupIds[i]
5462  + "'>";
5463  str += groupIds[i]; //can display however
5464  str += "</option>";
5465  if (groupIds[i]
5466  == ConfigurationAPI.editableFieldEditingOldValue_)
5467  optionIndex = i; //get starting sel index
5468  }
5469  //if not found, add current tmp value (probably user created recently)
5470  if(optionIndex == -1 &&
5471  ConfigurationAPI.editableFieldEditingOldValue_.length)
5472  {
5473  str += "<option value='" + encodeURIComponent(
5474  ConfigurationAPI.editableFieldEditingOldValue_)
5475  + "'>";
5476  str += encodeURIComponent(ConfigurationAPI.editableFieldEditingOldValue_); //can display however
5477  str += "</option>";
5478  optionIndex = groupIds.length;
5479  }
5480  str += "</select>";
5481 
5482  //add arbitrary edit
5483  if (true)
5484  {
5485  var ww = (el.offsetWidth-6);
5486  if(ww < 150) ww = 150;
5487  str += "<input type='text' " +
5488  "id='fixedChoice-editTextBox' " +
5489  "style='display:none;" +
5490  "float:left;" +
5491  "margin:-2px 0 -" + (el.offsetHeight+6) + "px 0;" +
5492  "width:" +
5493  ww + "px; height:" + (el.offsetHeight+6) + "px" +
5494  "' " + //end style
5495  "></input>";
5496 
5497  str += "<div style='display:block;" +
5498  "margin: -2px 0 -7px 14px;" +
5499  "' " +
5500  "class='editableFieldNode-Value-editIcon' id='fixedChoice-editIcon" +
5501  "' " +
5502  "onclick='ConfigurationAPI.handleEditableFieldFixedChoiceEditToggle();' " +
5503  "title='Toggle free-form editing' " +
5504  "></div>";
5505  }
5506 
5507  str += "</div>";
5508 
5509  //finish up
5510  Debug.log("Finishing up group-id select cell handling...");
5511  localFinishUpSelectCell();
5512 
5513  } //end localFinishGroupIdDropdown
5514  }, //end getSpecificTable request handler
5515  0, 0, true //reqParam, progressHandler, callHandlerOnErr
5516  ); //end XML Request for getSpecificTable
5517  } //end localHandleDropdownForLinkGroupID()
5518 
5519  } //end selectThisTreeNode();
5520  }
5521  else
5522  {
5523  Debug.log("This should be impossible - tell a developer how you got here!", Debug.HIGH_PRIORITY);
5524  return;
5525  }
5526  }
5527  else //1x click
5528  {
5529  if(type == "value")
5530  {
5531  //Mark selected
5532  Debug.log("Toggling selection of target field " + idString);
5533 
5534  //remove previously selected
5535  var vel;
5536  if(ConfigurationAPI.editableFieldSelectedIdString_ &&
5537  (vel = document.getElementById("editableFieldNode-Value-" +
5538  ConfigurationAPI.editableFieldSelectedIdString_)))
5539  vel.style.backgroundColor = "transparent";
5540 
5541  //add newly selected
5542  vel = document.getElementById("editableFieldNode-Value-" +
5543  idString);
5544  if(ConfigurationAPI.editableFieldSelectedIdString_ == idString)
5545  {
5546  //same field was clicked
5547  // so toggle (deselect) the previously selected field
5548  ConfigurationAPI.editableFieldSelectedIdString_ = undefined;
5549  return;
5550  }
5551  vel.style.backgroundColor = ConfigurationAPI.editableField_SELECTED_COLOR_;
5552  ConfigurationAPI.editableFieldSelectedIdString_ = idString;
5553  }
5554  else
5555  {
5556  Debug.log("This should be impossible - tell a developer how you got here!", Debug.HIGH_PRIORITY);
5557  return;
5558  }
5559  }
5560 
5561 } //end handleEditableFieldClick()
5562 
5563 //=====================================================================================
5564 //getSelectedEditableFieldIndex ~~
5565 // returns the unique field index value for the selected field
5566 // if no selected field
5567 // returns -1
5568 ConfigurationAPI.getSelectedEditableFieldIndex = function()
5569 {
5570  if(!ConfigurationAPI.editableFieldSelectedIdString_)
5571  return -1;
5572 
5573  var idStr = ConfigurationAPI.editableFieldSelectedIdString_;
5574  return idStr.split('-')[1]; // depth + "-" + fieldId
5575 } //end getSelectedEditableFieldIndex()
5576 
5577 //=====================================================================================
5578 //handleEditableFieldHover ~~
5579 // handler for mousemove event for editable field elements
5580 ConfigurationAPI.handleEditableFieldHover = function(depth,uid,event)
5581 {
5582  var idString = depth + "-" + uid;
5583 
5584  //Debug.log("handleEditableFieldHover idString " + idString);
5585 
5586  event.stopPropagation();
5587  DesktopContent.mouseMove(event); //keep desktop content happy
5588 
5589 
5590  if(ConfigurationAPI.editableFieldEditingCell_) return; //no setting while editing
5591 
5592  var el = document.getElementById("editableFieldNode-Value-editIcon-" + idString);
5593  if(ConfigurationAPI.editableFieldHoveringCell_ == el) return;
5594 
5595  if(ConfigurationAPI.editableFieldHoveringCell_)
5596  {
5597  //Debug.log("bodyMouseMoveHandler clearing ");
5598  bodyMouseMoveHandler();
5599  }
5600 
5601  //Debug.log("handleTreeNodeMouseMove setting ");
5602  ConfigurationAPI.editableFieldHoveringIdString_ = idString;
5603  ConfigurationAPI.editableFieldHoveringCell_ = el;
5604  ConfigurationAPI.editableFieldHoveringCell_.style.display = "block";
5605  var vel = document.getElementById("editableFieldNode-Value-" +
5606  ConfigurationAPI.editableFieldHoveringIdString_);
5607  vel.style.backgroundColor = "rgb(218, 194, 194)";
5608 } //end handleEditableFieldHover()
5609 
5610 //=====================================================================================
5611 //handleEditableFieldFixedChoiceEditToggle ~~
5612 ConfigurationAPI.handleEditableFieldFixedChoiceEditToggle = function()
5613 {
5614  Debug.log("handleEditableFieldFixedChoiceEditToggle");
5615 
5616  var sel = document.getElementById("fixedChoice-editSelectBox");
5617  var tel = document.getElementById("fixedChoice-editTextBox");
5618 
5619  Debug.log("sel.style.display " + sel.style.display);
5620  if(sel.style.display == "none")
5621  {
5622  sel.style.display = "block";
5623  tel.style.display = "none";
5624  }
5625  else
5626  {
5627  tel.style.width = ((sel.offsetWidth>150?sel.offsetWidth:150)-2) + "px";
5628  tel.parentNode.style.width = ((sel.offsetWidth>150?sel.offsetWidth:150)+50) + "px"; //div may need to expand to accommodate pencil in display
5629  sel.style.display = "none";
5630 
5631  if(tel.value == "") //fill with oldvalue for user convenience, if blank
5632  tel.value = ConfigurationAPI.editableFieldEditingOldValue_;
5633 
5634  tel.style.display = "block";
5635  ConfigurationAPI.setCaretPosition(tel,0,tel.value.length);
5636  }
5637 } //end handleEditableFieldFixedChoiceEditToggle()
5638 
5639 //=====================================================================================
5640 //handleEditableFieldBodyMouseMove ~~
5641 ConfigurationAPI.handleEditableFieldBodyMouseMove = function(e)
5642 {
5643  if(ConfigurationAPI.editableFieldHoveringCell_)
5644  {
5645  //Debug.log("bodyMouseMoveHandler clearing ");
5646  ConfigurationAPI.editableFieldHoveringCell_.style.display = "none";
5647  ConfigurationAPI.editableFieldHoveringCell_ = 0;
5648 
5649  var vel = document.getElementById("editableFieldNode-Value-" +
5650  ConfigurationAPI.editableFieldHoveringIdString_);
5651  if(vel)
5652  {
5653  if(ConfigurationAPI.editableFieldHoveringIdString_ ==
5654  ConfigurationAPI.editableFieldSelectedIdString_)
5655  vel.style.backgroundColor = ConfigurationAPI.editableField_SELECTED_COLOR_;
5656  else
5657  vel.style.backgroundColor = "transparent";
5658  }
5659  }
5660 } //end handleEditableFieldBodyMouseMove()
5661 
5662 //=====================================================================================
5663 //handleEditableFieldKeyDown ~~
5664 // copied from ConfigurationGUI keyHandler but modified for only value cells
5665 ConfigurationAPI.handleEditableFieldKeyDown = function(e,keyEl)
5666 {
5667  var TABKEY = 9;
5668  var ENTERKEY = 13;
5669  var UPKEY = 38;
5670  var DNKEY = 40;
5671  var ESCKEY = 27;
5672  //Debug.log("key " + e.keyCode);
5673 
5674  var shiftIsDown;
5675  if (window.event)
5676  {
5677  key = window.event.keyCode;
5678  shiftIsDown = !!window.event.shiftKey; // typecast to boolean
5679  }
5680  else
5681  {
5682  key = e.which;
5683  shiftIsDown = !!e.shiftKey; // typecast to boolean
5684  }
5685  //Debug.log("shift=" + shiftIsDown);
5686 
5687 
5688  //handle text area specially
5689  if(!shiftIsDown)
5690  {
5691  var tel;
5692  if(ConfigurationAPI.editableFieldEditingCell_ &&
5693  (tel = ConfigurationAPI.editableFieldEditingCell_.getElementsByTagName("textarea")).length)
5694  {
5695  tel = tel[0];
5696  //handle special keys for text area
5697  if(e.keyCode == TABKEY)
5698  {
5699  Debug.log("tab.");
5700  if(e.preventDefault)
5701  e.preventDefault();
5702 
5703  var i = tel.selectionStart;
5704  var j = tel.selectionEnd;
5705  tel.value = tel.value.substr(0,i) +
5706  '\t' + tel.value.substr(j);
5707  tel.selectionStart = tel.selectionEnd = j+1;
5708  }
5709  return false; //done if text area was identified
5710  }
5711  }
5712 
5713  //tab key jumps to next cell with a CANCEL
5714  // (enter key does same except saves/OKs value)
5715  // shift is reverse jump
5716  if(e.keyCode == TABKEY || e.keyCode == ENTERKEY ||
5717  e.keyCode == UPKEY || e.keyCode == DNKEY)
5718  {
5719  //this.value += " ";
5720  if(e.preventDefault)
5721  e.preventDefault();
5722 
5723  //save idString
5724  var idString = ConfigurationAPI.editableFieldEditingIdString_;
5725 
5726  ConfigurationAPI.handleEditableFieldEditOK();
5727 
5728 
5729  //enter key := done
5730  //tab/shift+tab := move to next field at depth
5731  //down/up := move to next field at depth
5732 
5733 
5734  if(e.keyCode == ENTERKEY) //dont move to new cell
5735  return false;
5736 
5737  var depth = idString.split('-')[0];
5738  var uid = idString.split('-')[1];
5739 
5740  if((!shiftIsDown && e.keyCode == TABKEY) || e.keyCode == DNKEY) //move to next field
5741  ++uid;
5742  else if((shiftIsDown && e.keyCode == TABKEY) || e.keyCode == UPKEY) //move to prev field
5743  --uid;
5744  if(uid < 0) return false; //no more fields, do nothing
5745 
5746  //assume handleEditableFieldClick handles invalid uids on high side gracefully
5747  ConfigurationAPI.handleEditableFieldClick(depth,uid,1,"value");
5748  Debug.log("new uid=" + uid);
5749 
5750  return false;
5751  }
5752  else if(e.keyCode == ESCKEY)
5753  {
5754  if(e.preventDefault)
5755  e.preventDefault();
5756  ConfigurationAPI.handleEditableFieldEditCancel();
5757  return false;
5758  }
5759  else if((e.keyCode >= 48 && e.keyCode <= 57) ||
5760  (e.keyCode >= 96 && e.keyCode <= 105))// number 0-9
5761  {
5762  //if child link cell or boolean cell
5763  var sel;
5764  if((sel = ConfigurationAPI.editableFieldEditingCell_.getElementsByTagName("select")).length)
5765  {
5766  if(keyEl) //if input element, use it
5767  sel = keyEl;
5768  else
5769  sel = sel[sel.length-1]; //assume the last select in the cell is the select
5770 
5771  //select based on number
5772  var selNum;
5773  if(e.keyCode >= 96)
5774  selNum = e.keyCode - 96;
5775  else
5776  selNum = e.keyCode - 48;
5777 
5778  sel.selectedIndex = selNum % (sel.options.length);
5779  sel.focus();
5780 
5781  Debug.log("number select =" + sel.selectedIndex);
5782  if(sel.onchange) //if onchange implemented call it
5783  sel.onchange();
5784  }
5785  }
5786 } //end handleEditableFieldKeyDown()
5787 
5788 //=====================================================================================
5789 //handleEditableFieldEditCancel ~~
5790 // copied from ConfigurationGUI editCellCancel but modified for only value cells
5791 ConfigurationAPI.handleEditableFieldEditCancel = function()
5792 {
5793  if(!ConfigurationAPI.editableFieldEditingCell_) return;
5794  Debug.log("handleEditableFieldEditCancel type " + ConfigurationAPI.editableFieldEditingNodeType_);
5795 
5796  try //try to return link visibility
5797  {
5798  var idSplit = ConfigurationAPI.editableFieldEditingCell_.id.split('-');
5799  var depth = idSplit[idSplit.length-2];
5800  var uid = idSplit[idSplit.length-1];
5801  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkTab-" +
5802  (depth + "-" + uid) ).style.display = "block";
5803  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkWindow-" +
5804  (depth + "-" + uid) ).style.display = "block";
5805  }
5806  catch(e) {} //ignore error
5807 
5808 
5809  if(ConfigurationAPI.editableFieldEditingNodeType_ == "value-bool")
5810  {
5811  //take old value as HTML for bool values
5812  ConfigurationAPI.editableFieldEditingCell_.innerHTML = ConfigurationAPI.editableFieldEditingOldValue_;
5813  }
5814  else
5815  {
5816  ConfigurationAPI.editableFieldEditingCell_.innerHTML = "";
5817  ConfigurationAPI.editableFieldEditingCell_.appendChild(
5818  document.createTextNode(ConfigurationAPI.editableFieldEditingOldValue_));
5819  }
5820 
5821  ConfigurationAPI.editableFieldEditingCell_ = 0;
5822 } //end handleEditableFieldEditCancel()
5823 
5824 //=====================================================================================
5825 //handleEditableFieldEditOK ~~
5826 // copied from ConfigurationGUI editCellOK but modified for only value cells
5827 ConfigurationAPI.handleEditableFieldEditOK = function()
5828 {
5829  if(!ConfigurationAPI.editableFieldEditingCell_) return;
5830  Debug.log("handleEditableFieldEditOK type " + ConfigurationAPI.editableFieldEditingNodeType_);
5831 
5832  try //try to return link visibility
5833  {
5834  var idSplit = ConfigurationAPI.editableFieldEditingCell_.id.split('-');
5835  var depth = idSplit[idSplit.length-2];
5836  var uid = idSplit[idSplit.length-1];
5837  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkTab-" +
5838  (depth + "-" + uid) ).style.display = "block";
5839  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkWindow-" +
5840  (depth + "-" + uid) ).style.display = "block";
5841  }
5842  catch(e) {} //ignore error
5843 
5844  var el = ConfigurationAPI.editableFieldEditingCell_;
5845  var type = ConfigurationAPI.editableFieldEditingNodeType_;
5846 
5848  //localEditTreeNodeOKRequestsComplete
5849  function localEditTreeNodeOKRequestsComplete(newValue)
5850  {
5851  // all value types, clear the cell first
5852  el.innerHTML = "";
5853 
5854 
5855  if(type == "value" ||
5856  type == "value-bitmap" ||
5857  type == "value-groupid" ||
5858  type == "value-childLink" ||
5859  type == "value-ChildLinkGroupID" ||
5860  type == "value-ChildLinkUID")
5861  {
5862  //MultilineData, select, and normal
5863  //also bitmap data (do nothing would be ok, value already set)
5864  el.appendChild(document.createTextNode(decodeURIComponent(newValue)));
5865  }
5866  else if(type == "value-bool")
5867  {
5868  var str = "";
5869 
5870  //colorize true false
5871  str += "<div style='float:left'>";
5872  str += newValue;
5873  str += "</div>";
5874 
5875  var color = (newValue == "On" || newValue == "Yes" || newValue == "True")?
5876  "rgb(16, 204, 16)":"rgb(255, 0, 0);";
5877  str += "<div style='width:10px;height:10px;" +
5878  "background-color:" + color + ";" +
5879  "float: left;" +
5880  "border-radius: 7px;" +
5881  "border: 2px solid white;" +
5882  "margin: 2px 0 0 6px;" +
5883  "'></div>";
5884  el.innerHTML = str;
5885  }
5886  else //unrecognized type!?
5887  {
5888  Debug.err("Unrecognizd tree edit type '" +
5889  type + "'! Should be impossible!");
5890  ConfigurationAPI.handleEditableFieldEditCancel(); return;
5891  }
5892 
5893  //requests are done, so end editing
5894  ConfigurationAPI.editableFieldEditingCell_ = 0;
5895  } //end localEditTreeNodeOKRequestsComplete
5897 
5898 
5899  if(
5900  type == "value" ||
5901  type == "value-bool" ||
5902  type == "value-bitmap" ||
5903  type == "value-groupid" ||
5904  type == "value-childLink" ||
5905  type == "value-ChildLinkGroupID" ||
5906  type == "value-ChildLinkUID")
5907 
5908  {
5909  var newValue;
5910 
5911  if(type == "value-bool")
5912  {
5913  var sel = el.getElementsByTagName("select")[0];
5914  newValue = sel.options[sel.selectedIndex].value;
5915  }
5916  else if(type == "value-bitmap")
5917  {
5918  newValue = encodeURIComponent(el.textContent);
5919  }
5920  else //value (normal, select, or multiline data)
5921  {
5922  var sel;
5923  if((sel = el.getElementsByTagName("textarea")).length) //for MultilineData
5924  newValue = sel[0].value; //assume the first textarea in the cell is the textarea
5925  else if((sel = el.getElementsByTagName("select")).length) //for FixedChoiceData
5926  {
5927  //check if displayed
5928  if(sel[0].style.display == "none")
5929  {
5930  //if not displayed assume first input field is ours!
5931  //take "normal cell" approach from below
5932  newValue = el.getElementsByTagName("input")[0].value;
5933  }
5934  else
5935  newValue = sel[0].options[sel[0].selectedIndex].value; //assume the first select dropbox in the cell is the one
5936  }
5937  else
5938  newValue = el.getElementsByTagName("input")[0].value;
5939 
5940  newValue = encodeURIComponent(newValue.trim());
5941 
5942  if(type == "value-childLink")
5943  {
5944  Debug.log("Checking link value against active tables");
5945  var found = false;
5946  for(var activeTable in ConfigurationAPI._activeTables)
5947  if(newValue == activeTable)
5948  {
5949  found = true; break;
5950  }
5951  if(!found)
5952  {
5953  Debug.warn("We noticed the child link target table '" +
5954  newValue + "' was not found in the list of active tables. " +
5955  "You may be able to ignore this issue, or your table group might fail to activate during State Machine transitions. " +
5956  "\n\n" +
5957  "To add a table to a group, in the Configuration Editor GUI, go to go to the " +
5958  "group view, then click 'Add/Remove/Modify Member Tables.' You " +
5959  "can then add or remove tables and save the new group." +
5960  "\n\n" +
5961  "OR!!! Click the following button to add the table '" + newValue +
5962  "' to the Configuration Group: " +
5963  "<input type='button' style='color:black !important;' " +
5964  "title='Click to add table to the active Configuration Group' " +
5965  "onclick='ConfigurationAPI.addTableToConfigurationGroup(\"" +
5966  newValue + "\"); Debug.closeErrorPop();event.stopPropagation(); ' value='Add Table'>" +
5967  "</input>");
5968  }
5969  } //end childLink special handling
5970  } //end standard value handling
5971 
5972  Debug.log("CfgGUI editTreeNodeOK editing " + type + " node = " +
5973  newValue);
5974 
5975  if(ConfigurationAPI.editableFieldEditingInitValue_ == newValue)
5976  {
5977  Debug.log("No change. Do nothing.");
5978  ConfigurationAPI.handleEditableFieldEditCancel();
5979  return;
5980  }
5981 
5982 
5983  // if saved successfully
5984  // update value in field
5985 
5986  localEditTreeNodeOKRequestsComplete(newValue);
5987 
5988  }
5989  else //unrecognized type!?
5990  {
5991  Debug.err("Unrecognizd tree edit type '" +
5992  type + "'! Should be impossible!");
5993  ConfigurationAPI.handleEditableFieldEditCancel(); return;
5994  }
5995 } //end handleEditableFieldEditOK()
5996 
5997 
5998 //=====================================================================================
5999 //hasClass ~~
6000 ConfigurationAPI.hasClass = function(ele,cls)
6001 {
6002  return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
6003 }
6004 
6005 //=====================================================================================
6006 //addClass ~~
6007 ConfigurationAPI.addClass = function(ele,cls)
6008 {
6009  if (!ConfigurationAPI.hasClass(ele,cls)) ele.className += " "+cls;
6010 }
6011 
6012 //=====================================================================================
6013 //removeClass ~~
6014 ConfigurationAPI.removeClass = function(ele,cls)
6015 {
6016  if (ConfigurationAPI.hasClass(ele,cls))
6017  {
6018  var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
6019  ele.className=ele.className.replace(reg,'');
6020  }
6021 }
6022 
6023 
6024 //=====================================================================================
6025 //addSubsetRecords ~~
6026 // Takes as input a base path where the desired records should be created.
6027 //
6028 // <modifiedTables> is an array of Table objects (as returned from
6029 // ConfigurationAPI.setFieldValuesForRecords)
6030 //
6031 // when complete, the responseHandler is called with an array parameter.
6032 // on failure, the array will be empty.
6033 // on success, the array will be an array of Table objects
6034 // Table := {}
6035 // obj.tableName
6036 // obj.tableVersion
6037 // obj.tableComment
6038 //
6039 ConfigurationAPI.addSubsetRecords = function(subsetBasePath,
6040  recordArr,responseHandler,modifiedTablesIn,silenceErrors)
6041 {
6042  var modifiedTablesListStr = "";
6043  for(var i=0;modifiedTablesIn && i<modifiedTablesIn.length;++i)
6044  {
6045  if(i) modifiedTablesListStr += ",";
6046  modifiedTablesListStr += modifiedTablesIn[i].tableName + "," +
6047  modifiedTablesIn[i].tableVersion;
6048  }
6049 
6050  var recordListStr = "";
6051  if(Array.isArray(recordArr))
6052  for(var i=0;i<recordArr.length;++i)
6053  {
6054  if(i) recordListStr += ",";
6055  recordListStr += encodeURIComponent(recordArr[i]);
6056  }
6057  else //handle single record case
6058  recordListStr = encodeURIComponent(recordArr);
6059 
6060  DesktopContent.XMLHttpRequest("Request?RequestType=addTreeNodeRecords" +
6061  "&tableGroup=" +
6062  "&tableGroupKey=-1", //end get data
6063  "startPath=/" + subsetBasePath +
6064  "&recordList=" + recordListStr +
6065  "&modifiedTables=" + modifiedTablesListStr, //end post data
6066  function(req)
6067  {
6068  var modifiedTables = [];
6069 
6070  var err = DesktopContent.getXMLValue(req,"Error");
6071  if(err)
6072  {
6073  if(!silenceErrors)
6074  Debug.log(err,Debug.HIGH_PRIORITY);
6075  responseHandler(modifiedTables,err);
6076  return;
6077  }
6078 
6079  //console.log(req);
6080 
6081  //modifiedTables
6082  var tableNames = req.responseXML.getElementsByTagName("NewActiveTableName");
6083  var tableVersions = req.responseXML.getElementsByTagName("NewActiveTableVersion");
6084  var tableComments = req.responseXML.getElementsByTagName("NewActiveTableComment");
6085  var tableVersion;
6086 
6087  //add only temporary version
6088  for(var i=0;i<tableNames.length;++i)
6089  {
6090  tableVersion = DesktopContent.getXMLValue(tableVersions[i])|0; //force integer
6091  if(tableVersion >= -1) continue; //skip unless temporary
6092  var obj = {};
6093  obj.tableName = DesktopContent.getXMLValue(tableNames[i]);
6094  obj.tableVersion = DesktopContent.getXMLValue(tableVersions[i]);
6095  obj.tableComment = DesktopContent.getXMLValue(tableComments[i]);
6096  modifiedTables.push(obj);
6097  }
6098  responseHandler(modifiedTables);
6099 
6100  }, //handler
6101  0, //handler param
6102  0,true); //progressHandler, callHandlerOnErr
6103 
6104 } // end ConfigurationAPI.addSubsetRecords()
6105 
6106 
6107 //=====================================================================================
6108 //deleteSubsetRecords ~~
6109 // Takes as input a base path where the desired records should be deleted.
6110 //
6111 // <modifiedTables> is an array of Table objects (as returned from
6112 // ConfigurationAPI.setFieldValuesForRecords)
6113 //
6114 // when complete, the responseHandler is called with an array parameter.
6115 // on failure, the array will be empty.
6116 // on success, the array will be an array of Table objects
6117 // Table := {}
6118 // obj.tableName
6119 // obj.tableVersion
6120 // obj.tableComment
6121 //
6122 ConfigurationAPI.deleteSubsetRecords = function(subsetBasePath,
6123  recordArr,responseHandler,modifiedTablesIn,silenceErrors)
6124 {
6125  var modifiedTablesListStr = "";
6126  for(var i=0;modifiedTablesIn && i<modifiedTablesIn.length;++i)
6127  {
6128  if(i) modifiedTablesListStr += ",";
6129  modifiedTablesListStr += modifiedTablesIn[i].tableName + "," +
6130  modifiedTablesIn[i].tableVersion;
6131  }
6132 
6133  var recordListStr = "";
6134  var recordCount = 1;
6135  if(Array.isArray(recordArr))
6136  {
6137  for(var i=0;i<recordArr.length;++i)
6138  {
6139  if(i) recordListStr += ",";
6140  recordListStr += encodeURIComponent(recordArr[i]);
6141  }
6142  recordCount = recordArr.length;
6143  }
6144  else //handle single record case
6145  recordListStr = encodeURIComponent(recordArr);
6146 
6147  DesktopContent.XMLHttpRequest("Request?RequestType=deleteTreeNodeRecords" +
6148  "&tableGroup=" +
6149  "&tableGroupKey=-1", //end get data
6150  "startPath=/" + subsetBasePath +
6151  "&recordList=" + recordListStr +
6152  "&modifiedTables=" + modifiedTablesListStr, //end post data
6153  function(req)
6154  {
6155 
6156  var err = DesktopContent.getXMLValue(req,"Error");
6157  var modifiedTables = [];
6158  if(err)
6159  {
6160  if(!silenceErrors)
6161  Debug.log(err,Debug.HIGH_PRIORITY);
6162  responseHandler(modifiedTables,err);
6163  return;
6164  }
6165 
6166  //console.log(req);
6167 
6168  //modifiedTables
6169  var tableNames = req.responseXML.getElementsByTagName("NewActiveTableName");
6170  var tableVersions = req.responseXML.getElementsByTagName("NewActiveTableVersion");
6171  var tableComments = req.responseXML.getElementsByTagName("NewActiveTableComment");
6172  var tableVersion;
6173 
6174  //add only temporary version
6175  for(var i=0;i<tableNames.length;++i)
6176  {
6177  tableVersion = DesktopContent.getXMLValue(tableVersions[i])|0; //force integer
6178  if(tableVersion >= -1) continue; //skip unless temporary
6179  var obj = {};
6180  obj.tableName = DesktopContent.getXMLValue(tableNames[i]);
6181  obj.tableVersion = DesktopContent.getXMLValue(tableVersions[i]);
6182  obj.tableComment = DesktopContent.getXMLValue(tableComments[i]);
6183  modifiedTables.push(obj);
6184  }
6185  responseHandler(modifiedTables,undefined,subsetBasePath,recordCount);
6186 
6187  }, //handler
6188  0, //handler param
6189  0,true); //progressHandler, callHandlerOnErr
6190 
6191 } // end ConfigurationAPI.deleteSubsetRecords()
6192 
6193 //=====================================================================================
6194 //renameSubsetRecords ~~
6195 // Takes as input a base path where the desired records should be renamed.
6196 //
6197 // <modifiedTables> is an array of Table objects (as returned from
6198 // ConfigurationAPI.setFieldValuesForRecords)
6199 //
6200 // when complete, the responseHandler is called with an array parameter.
6201 // on failure, the array will be empty.
6202 // on success, the array will be an array of Table objects
6203 // Table := {}
6204 // obj.tableName
6205 // obj.tableVersion
6206 // obj.tableComment
6207 //
6208 ConfigurationAPI.renameSubsetRecords = function(subsetBasePath,
6209  recordArr,newRecordArr,responseHandler,modifiedTablesIn,silenceErrors)
6210 {
6211  Debug.log("renameSubsetRecords()");
6212 
6213  var modifiedTablesListStr = "";
6214  for(var i=0;modifiedTablesIn && i<modifiedTablesIn.length;++i)
6215  {
6216  if(i) modifiedTablesListStr += ",";
6217  modifiedTablesListStr += modifiedTablesIn[i].tableName + "," +
6218  modifiedTablesIn[i].tableVersion;
6219  }
6220 
6221  var recordListStr = "";
6222  var recordCount = 1;
6223  if(Array.isArray(recordArr))
6224  {
6225  for(var i=0;i<recordArr.length;++i)
6226  {
6227  if(i) recordListStr += ",";
6228  recordListStr += encodeURIComponent(recordArr[i]);
6229  }
6230  recordCount = recordArr.length;
6231  }
6232  else //handle single record case
6233  recordListStr = encodeURIComponent(recordArr);
6234 
6235  var newRecordListStr = "";
6236  var newRecordCount = 1;
6237  if(Array.isArray(newRecordArr))
6238  {
6239  for(var i=0;i<newRecordArr.length;++i)
6240  {
6241  if(i) newRecordListStr += ",";
6242  newRecordListStr += encodeURIComponent(newRecordArr[i]);
6243  }
6244  newRecordCount = newRecordArr.length;
6245  }
6246  else //handle single record case
6247  newRecordListStr = encodeURIComponent(newRecordArr);
6248 
6249  DesktopContent.XMLHttpRequest("Request?RequestType=renameTreeNodeRecords" +
6250  "&tableGroup=" +
6251  "&tableGroupKey=-1", //end get data
6252  "startPath=/" + subsetBasePath +
6253  "&recordList=" + recordListStr +
6254  "&newRecordList=" + newRecordListStr +
6255  "&modifiedTables=" + modifiedTablesListStr, //end post data
6256  function(req)
6257  {
6258 
6259  var err = DesktopContent.getXMLValue(req,"Error");
6260  var modifiedTables = [];
6261  if(err)
6262  {
6263  if(!silenceErrors)
6264  Debug.log(err,Debug.HIGH_PRIORITY);
6265  responseHandler(modifiedTables,err);
6266  return;
6267  }
6268 
6269  //console.log(req);
6270 
6271  //modifiedTables
6272  var tableNames = req.responseXML.getElementsByTagName("NewActiveTableName");
6273  var tableVersions = req.responseXML.getElementsByTagName("NewActiveTableVersion");
6274  var tableComments = req.responseXML.getElementsByTagName("NewActiveTableComment");
6275  var tableVersion;
6276 
6277  //add only temporary version
6278  for(var i=0;i<tableNames.length;++i)
6279  {
6280  tableVersion = DesktopContent.getXMLValue(tableVersions[i])|0; //force integer
6281  if(tableVersion >= -1) continue; //skip unless temporary
6282  var obj = {};
6283  obj.tableName = DesktopContent.getXMLValue(tableNames[i]);
6284  obj.tableVersion = DesktopContent.getXMLValue(tableVersions[i]);
6285  obj.tableComment = DesktopContent.getXMLValue(tableComments[i]);
6286  modifiedTables.push(obj);
6287  }
6288  responseHandler(modifiedTables,undefined,subsetBasePath,recordCount);
6289 
6290  }, //handler
6291  0, //handler param
6292  0,true); //progressHandler, callHandlerOnErr
6293 
6294 } //end ConfigurationAPI.renameSubsetRecords()
6295 
6296 //=====================================================================================
6297 //copySubsetRecords ~~
6298 // Takes as input a base path where the desired records should be copied.
6299 // Incremental unique names will be created by the server.
6300 //
6301 // <modifiedTables> is an array of Table objects (as returned from
6302 // ConfigurationAPI.setFieldValuesForRecords)
6303 //
6304 // when complete, the responseHandler is called with an array parameter.
6305 // on failure, the array will be empty.
6306 // on success, the array will be an array of Table objects
6307 // Table := {}
6308 // obj.tableName
6309 // obj.tableVersion
6310 // obj.tableComment
6311 //
6312 ConfigurationAPI.copySubsetRecords = function(subsetBasePath,
6313  recordArr,numberOfCopies,responseHandler,modifiedTablesIn,silenceErrors)
6314 {
6315  if(!numberOfCopies) numberOfCopies = 1;
6316  Debug.log("copySubsetRecords() " + numberOfCopies);
6317 
6318  var modifiedTablesListStr = "";
6319  for(var i=0;modifiedTablesIn && i<modifiedTablesIn.length;++i)
6320  {
6321  if(i) modifiedTablesListStr += ",";
6322  modifiedTablesListStr += modifiedTablesIn[i].tableName + "," +
6323  modifiedTablesIn[i].tableVersion;
6324  }
6325 
6326  var recordListStr = "";
6327  var recordCount = 1;
6328  if(Array.isArray(recordArr))
6329  {
6330  for(var i=0;i<recordArr.length;++i)
6331  {
6332  if(i) recordListStr += ",";
6333  recordListStr += encodeURIComponent(recordArr[i]);
6334  }
6335  recordCount = recordArr.length;
6336  }
6337  else //handle single record case
6338  recordListStr = encodeURIComponent(recordArr);
6339 
6340  DesktopContent.XMLHttpRequest("Request?RequestType=copyTreeNodeRecords" +
6341  "&tableGroup=" +
6342  "&tableGroupKey=-1" +
6343  "&numberOfCopies=" + numberOfCopies, //end get data
6344  "startPath=/" + subsetBasePath +
6345  "&recordList=" + recordListStr +
6346  "&modifiedTables=" + modifiedTablesListStr, //end post data
6347  function(req)
6348  {
6349 
6350  var err = DesktopContent.getXMLValue(req,"Error");
6351  var modifiedTables = [];
6352  if(err)
6353  {
6354  if(!silenceErrors)
6355  Debug.log(err,Debug.HIGH_PRIORITY);
6356  responseHandler(modifiedTables,err);
6357  return;
6358  }
6359 
6360  //console.log(req);
6361 
6362  //modifiedTables
6363  var tableNames = req.responseXML.getElementsByTagName("NewActiveTableName");
6364  var tableVersions = req.responseXML.getElementsByTagName("NewActiveTableVersion");
6365  var tableComments = req.responseXML.getElementsByTagName("NewActiveTableComment");
6366  var tableVersion;
6367 
6368  //add only temporary version
6369  for(var i=0;i<tableNames.length;++i)
6370  {
6371  tableVersion = DesktopContent.getXMLValue(tableVersions[i])|0; //force integer
6372  if(tableVersion >= -1) continue; //skip unless temporary
6373  var obj = {};
6374  obj.tableName = DesktopContent.getXMLValue(tableNames[i]);
6375  obj.tableVersion = DesktopContent.getXMLValue(tableVersions[i]);
6376  obj.tableComment = DesktopContent.getXMLValue(tableComments[i]);
6377  modifiedTables.push(obj);
6378  }
6379  responseHandler(modifiedTables,undefined,subsetBasePath,recordCount);
6380 
6381  }, //handler
6382  0, //handler param
6383  0,true); //progressHandler, callHandlerOnErr
6384 
6385 } //end ConfigurationAPI.copySubsetRecords()
6386 
6387 //=====================================================================================
6388 //incrementName ~~
6389 ConfigurationAPI.incrementName = function(name)
6390 {
6391  //find last non-numeric
6392  for(var i=name.length-1;i>=0;--i)
6393  if(!(name[i] >= '0' && name[i] <= '9'))
6394  break;
6395  //note: if all numbers, then i is -1, which still works
6396  var num = (name.substr(i+1)|0) + 1;
6397  name = name.substr(0,i+1);
6398  return name + num;
6399 } //end incrementName()
6400 
6401 //=====================================================================================
6402 //createNewRecordName ~~
6403 ConfigurationAPI.createNewRecordName = function(startingName,existingArr)
6404 {
6405  var retVal = startingName;
6406  var found,i;
6407  try
6408  {
6409  var apps = existingArr;
6410  do
6411  {
6412  retVal = ConfigurationAPI.incrementName(retVal);
6413  found = false;
6414  for(i=0;i<apps.length;++i)
6415  if(apps[i] == retVal)
6416  {found = true; break;}
6417  } while(found);
6418  Debug.log("createNewRecordName " + retVal);
6419  }
6420  catch(e)
6421  {
6422  //ignore errors.. assume no all apps
6423  return ConfigurationAPI.incrementName(retVal);
6424  }
6425 
6426  return retVal;
6427 } //end createNewRecordName()
6428 
6429 
6430 
6431 
6432 
6433 
6434 
6435 
6436 
6437 
6438 
6439 
6440 
6441 
6442 
6443 
6444 
6445 
6446 
6447 
6448