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