otsdaq_utilities  v2_04_01
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,tableMap,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=getActiveTableGroups",
146  "", function(req)
147  {
148  responseHandler(ConfigurationAPI.extractActiveGroups(req));
149  },
150  0,0,true //reqParam, progressHandler, callHandlerOnErr
151  ); //end of getActiveTableGroups 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=getTableGroups"
288  +"&doNotReturnMembers=1", //end get data
289  "", //end post data
290  function(req)
291  {
292  Debug.log("getTableGroups handler");
293 
294  retObj.activeGroups = {}; //clear
295  retObj.activeGroups = ConfigurationAPI.extractActiveGroups(req);
296 
297  var groupNames = req.responseXML.getElementsByTagName("TableGroupName");
298  var groupKeys = req.responseXML.getElementsByTagName("TableGroupKey");
299  var groupTypes = req.responseXML.getElementsByTagName("TableGroupType");
300  var groupComments = req.responseXML.getElementsByTagName("TableGroupComment");
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  "&tableGroup=" +
383  "&tableGroupKey=-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  "&tableGroup=" +
440  "&tableGroupKey=-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 == "LinkTableName")
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 == "LinkTableName")
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  "&tableGroup=" +
674  "&tableGroupKey=-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  "&tableGroup=" +
799  "&tableGroupKey=-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  "&tableGroup=" +
884  "&tableGroupKey=-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  "&tableGroup=" +
1009  "&tableGroupKey=-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 getActiveTableGroups 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 affectedGroupTableMap = []; //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 table map
1752  affectedGroupTableMap[i] = "tableList=";
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  affectedGroupTableMap[i] += memberName + "," +
1768  savedTables[k].tableVersion + ",";
1769  break;
1770  }
1771  }
1772  else
1773  affectedGroupTableMap[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 table map
1803  affectedGroupTableMap[i] = "tableList=";
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  affectedGroupTableMap[i] += affectedArr[a] + "," +
1816  savedTables[k].tableVersion + ",";
1817  break;
1818  }
1819  }
1820  else //use existing version
1821  affectedGroupTableMap[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=saveNewTableGroup" +
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(affectedGroupTableMap[i]);
1843 
1844  ++numberOfRequests;
1846  DesktopContent.XMLHttpRequest(reqStr, affectedGroupTableMap[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,"TableGroupKey");
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=saveSpecificTable" +
2215  "&dataOffset=0&chunkSize=0" +
2216  "&tableName=" + 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 tableName = 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 '" + tableName + "-v" +
2250  version + "'",Debug.INFO_PRIORITY);
2251  else
2252  Debug.log("Successfully created new table '" + tableName + "-v" +
2253  version + "'",Debug.INFO_PRIORITY);
2254 
2255  //update saved table version based on result
2256  {
2257  var obj = {};
2258  obj.tableName = tableName;
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=activateTableGroup" +
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 tableVersions = req.responseXML.getElementsByTagName("oldBackboneVersion");
2404 
2405  //make a new backbone with old versions of everything except Group Alias
2406  var tableMap = "tableList=";
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  tableMap += name + "," +
2415  groupAliasVersion + ",";
2416  continue;
2417  }
2418  //else use old member
2419  tableMap += name + "," +
2420  tableVersions[i].getAttribute("value") + ",";
2421  }
2422 
2423  console.log("backbone tableMap",tableMap);
2424 
2425  ConfigurationAPI.saveGroupAndActivate(params[0],tableMap,params[1],params[2],
2426  true /*lookForEquivalent*/);
2427 } // end ConfigurationAPI.newWizBackboneMemberHandler()
2428 
2429 //=====================================================================================
2430 //saveGroupAndActivate
2431 ConfigurationAPI.saveGroupAndActivate = function(groupName,tableMap,doneHandler,doReturnParams,
2432  lookForEquivalent)
2433 {
2434  DesktopContent.XMLHttpRequest("Request?RequestType=saveNewTableGroup&groupName=" +
2435  groupName +
2436  "&allowDuplicates=" + (lookForEquivalent?"0":"1") +
2437  "&lookForEquivalent=" + (lookForEquivalent?"1":"0") +
2438  "", //end get data
2439  tableMap, //end post data
2440  function(req)
2441  {
2442  var err = DesktopContent.getXMLValue(req,"Error");
2443  var name = DesktopContent.getXMLValue(req,"TableGroupName");
2444  var key = DesktopContent.getXMLValue(req,"TableGroupKey");
2445  var newGroupCreated = true;
2446  if(err)
2447  {
2448  if(!name || !key)
2449  {
2450  Debug.log(err,Debug.HIGH_PRIORITY);
2451  Debug.log("Process interrupted. Failed to create a new group!" +
2452  " Please see details below.",
2453  Debug.HIGH_PRIORITY);
2454 
2455  if(doneHandler) doneHandler(); //error so call done handler
2456  return;
2457  }
2458  else
2459  {
2460  Debug.log(err,Debug.WARN_PRIORITY);
2461  Debug.log("Process interrupted. Failed to create a new group!" +
2462  " (Likely the currently active group already represents what is being requested)\n\n" +
2463  "Going on with existing backbone group, name=" + name + " & key=" + key,
2464  Debug.WARN_PRIORITY);
2465  newGroupCreated = false;
2466  }
2467  }
2468 
2469  //now activate the new group
2470 
2471  DesktopContent.XMLHttpRequest("Request?RequestType=activateTableGroup" +
2472  "&groupName=" + name +
2473  "&groupKey=" + key, "",
2474  function(req)
2475  {
2476  try
2477  {
2478  activateSystemConfigHandler(req);
2479  }
2480  catch(err) {} //ignore error, this is only used by ConfigurationGUI (or anyone implementing this extra handler)
2481 
2482  if(doneHandler)
2483  {
2484  //done so call done handler (and indicate success)
2485  if(!doReturnParams)
2486  doneHandler(); //done so call done handler
2487  else
2488  {
2489  var retParams = {
2490  "groupName" : name,
2491  "groupKey" : key,
2492  "newGroupCreated" : newGroupCreated
2493  }
2494  doneHandler(retParams); //(and indicate success)
2495  }
2496  }
2497  }); //end of activate new backbone handler
2498 
2499  },0,0,true //reqParam, progressHandler, callHandlerOnErr
2500  ); //end of backbone saveNewTableGroup handler
2501 } //end ConfigurationAPI.saveGroupAndActivate
2502 
2503 //=====================================================================================
2504 //getGroupTypeMemberNames
2505 // groupType can be
2506 // Backbone
2507 // Context
2508 // Iterate
2509 //
2510 // on failure, return empty array
2511 // on success, return array of members
2512 ConfigurationAPI.getGroupTypeMemberNames = function(groupType,responseHandler)
2513 {
2514  DesktopContent.XMLHttpRequest("Request?RequestType=get" + groupType + "MemberNames", "",
2515  function (req)
2516  {
2517  var retArr = [];
2518 
2519  var err = DesktopContent.getXMLValue(req,"Error");
2520  if(err)
2521  {
2522  Debug.log(err,Debug.HIGH_PRIORITY);
2523  if(responseHandler) responseHandler(retArr);
2524  return;
2525  }
2526  var memberNames = req.responseXML.getElementsByTagName(groupType + "Member");
2527 
2528  for(var i=0;i<memberNames.length;++i)
2529  retArr[i] = memberNames[i].getAttribute("value");
2530 
2531  Debug.log("Members found for group type " + groupType + " = " + retArr.length);
2532  if(responseHandler) responseHandler(retArr);
2533 
2534  }, //end request handler
2535  0,0,true //reqParam, progressHandler, callHandlerOnErr
2536  ); //end request
2537 
2538 }
2539 
2540 
2541 //=====================================================================================
2542 //bitMapDialog ~~
2543 // shows bitmap dialog at full window size (minus margins)
2544 // on ok, calls <okHandler> with finalBitMapValue parameter
2545 //
2546 // <bitMapParams> is an array olf size 6:
2547 // rows,cols,cellFieldSize,minColor,midColor,maxColor
2548 ConfigurationAPI.bitMapDialog = function(fieldName,bitMapParams,initBitMapValue,okHandler,cancelHandler)
2549 {
2550  Debug.log("ConfigurationAPI bitMapDialog");
2551 
2552  var str = "";
2553 
2554  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
2555  if(!el)
2556  {
2557  el = document.createElement("div");
2558  el.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID);
2559  }
2560  el.style.display = "none";
2561  el.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmapDialog");
2562 
2563  var padding = 10;
2564  var popSz;
2565 
2566 
2567  //create bit map dialog
2568  // - header at top with field name and parameters
2569  // - bitMap, w/mouseover and click and drag (same as windows sw)
2570  // - center vertically
2571  // - OK, CANCEL buttons at top right
2572  // - also upload download buttons
2573 
2574  // Input parameters must match Table Editor handling for bitmaps:
2575  // var _bitMapFieldsArr = [0 "Number of Rows",
2576  // 1 "Number of Columns",
2577  // 2 "Cell Bit-field Size",
2578  // 3 "Min-value Allowed",
2579  // 4 "Max-value Allowed",
2580  // 5 "Value step-size Allowed",
2581  // 6 "Display Aspect H:W",
2582  // 7 "Min-value Cell Color",
2583  // 8 "Mid-value Cell Color",
2584  // 9 "Max-value Cell Color",
2585  // 10 "Absolute Min-value Cell Color",
2586  // 11 "Absolute Max-value Cell Color",
2587  // 12 "Display Rows in Ascending Order",
2588  // 13 "Display Columns in Ascending Order",
2589  // 14 "Snake Double Rows",
2590  // 15 "Snake Double Columns"];
2591 
2592 
2593  //
2594  // Local functions:
2595  // localCreateCancelClickHandler()
2596  // localCreateOkClickHandler()
2597  // localCreateMouseHandler()
2598  // localGetRowCol(x,y)
2599  // el.onmousemove
2600  // el.onmousedown
2601  // el.onmouseup
2602  // el.oncontextmenu
2603  // localSetBitMap(r,c)
2604  // localValidateInputs()
2605  // localInitBitmapData()
2606  // localConvertGridToRowCol(r,c)
2607  // localConvertValueToRGBA(val)
2608  // localConvertFullGridToRowCol()
2609  // localConvertFullRowColToGrid(srcMatrix)
2610  // localCreateBitmap()
2611  // localCreateGridButtons()
2612  // localCreateHeader()
2613  // ConfigurationAPI.bitMapDialog.localUpdateScroll(i)
2614  // ConfigurationAPI.bitMapDialog.localUpdateTextInput(i)
2615  // ConfigurationAPI.bitMapDialog.localUpdateButtonInput(i,dir)
2616  // ConfigurationAPI.bitMapDialog.localDownloadCSV()
2617  // ConfigurationAPI.bitMapDialog.locaPopupUploadCSV()
2618  // ConfigurationAPI.bitMapDialog.locaUploadCSV()
2619  // localPaint()
2620  // localOptimizeAspectRatio()
2621 
2622  var rows, cols;
2623 
2624  var bitFieldSize;
2625  var bitMask;
2626 
2627  var minValue, maxValue;
2628  var midValue; //used for color calcs
2629  var stepValue;
2630 
2631  var forcedAspectH, forcedAspectW;
2632 
2633  var minValueColor, midValueColor, maxValueColor;
2634  var ceilValueColor, floorValueColor;
2635 
2636  var doDisplayRowsAscending, doDisplayColsAscending;
2637  var doSnakeColumns, doSnakeRows;
2638 
2639  //validate and load input params
2640  if(!localValidateInputs())
2641  {
2642  Debug.log("Input parameters array to the Bitmap Dialog was as follows:\n " +
2643  bitMapParams, Debug.HIGH_PRIORITY);
2644  Debug.log("Input parameters to the Bitmap Dialog are invalid. Aborting.", Debug.HIGH_PRIORITY);
2645  return cancelHandler();
2646  }
2647 
2648  //give 5 pixels extra for each digit necessary to label rows
2649  var numberDigitW = 8, numberDigitH = 12;
2650  var axisPaddingExtra = numberDigitW;
2651  function localCalcExtraAxisPadding() {
2652  var lrows = rows;
2653  while((lrows /= 10) > 1) axisPaddingExtra += numberDigitW;
2654  } localCalcExtraAxisPadding();
2655  var butttonSz = 20;
2656  var axisPaddingMargin = 5;
2657  var axisPadding = axisPaddingMargin + axisPaddingExtra + axisPaddingMargin + butttonSz + axisPaddingMargin;
2658  var bmpGridThickness = 1;
2659  var bmpBorderSize = 1;
2660 
2661 
2662  var hdr; //header element
2663  var hdrX;
2664  var hdrY;
2665  var hdrW;
2666  var hdrH;
2667 
2668  var bmp; //bitmap element
2669  var bmpGrid; //bitmap grid element
2670  var allRowBtns, allColBtns, allBtn;
2671  var rowLeftNums, rowRightNums, colTopNums, colBottomNums;
2672  var bmpCanvas, bmpContext; //used to generate 2D bitmap image src
2673  var bmpData; //bitmap data shown and returned. 2D array
2674  var bmpDataImage; //visual interpretation of bitmap data
2675  var bmpX;
2676  var bmpY;
2677  var bmpW;
2678  var bmpH;
2679  var bmpOverlay;
2680  var cursorInfo, hdrCursorInfo;
2681 
2682  var cellW;
2683  var cellH;
2684 
2685  var clickColors = []; //2 element array: 0=left-click, 1=right-click
2686  var clickValues = []; //2 element array: 0=left-click, 1=right-click
2687 
2688 
2689  localCreateHeader(); //create header element content
2690  localCreateBitmap(); //create bitmap element and data
2691  localCreateGridButtons(); //create all buttons
2692 
2693  localInitBitmapData(); //load bitmap data from input string
2694 
2695  localPaint();
2696  window.addEventListener("resize",localPaint);
2697 
2698  document.body.appendChild(el); //add element to body
2699  el.style.display = "block";
2700 
2701 
2702  //:::::::::::::::::::::::::::::::::::::::::
2703  //localCreateCancelClickHandler ~~
2704  // create cancel onclick handler
2705  function localCreateCancelClickHandler()
2706  {
2707  document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID +
2708  "-cancel").onclick = function(event) {
2709  Debug.log("Cancel click");
2710  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
2711  if(el) el.parentNode.removeChild(el); //close popup
2712  window.removeEventListener("resize",localPaint); //remove paint listener
2713  cancelHandler(); //empty array indicates nothing done
2714  return false;
2715  }; //end submit onmouseup handler
2716  } localCreateCancelClickHandler();
2717 
2718  //:::::::::::::::::::::::::::::::::::::::::
2719  //localCreateOkClickHandler ~~
2720  // create OK onclick handler
2721  function localCreateOkClickHandler()
2722  {
2723  var convertFunc = localConvertFullGridToRowCol;
2724  document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID +
2725  "-ok").onclick = function(event) {
2726  Debug.log("OK click");
2727  var el = document.getElementById(ConfigurationAPI._POP_UP_DIALOG_ID);
2728  if(el) el.parentNode.removeChild(el); //close popup
2729  window.removeEventListener("resize",localPaint); //remove paint listener
2730 
2731  var transGrid = convertFunc();
2732  var dataJsonStr = "[\n";
2733  for(var r=0;r<transGrid.length;++r)
2734  {
2735  if(r) dataJsonStr += ",\n";
2736  dataJsonStr += "\t[";
2737  for(var c=0;c<transGrid[0].length;++c)
2738  {
2739  if(c) dataJsonStr += ",";
2740  dataJsonStr += transGrid[r][c];
2741  }
2742  dataJsonStr += "]";
2743  }
2744  dataJsonStr += "\n]";
2745  okHandler(dataJsonStr); //empty array indicates nothing done
2746  return false;
2747  }; //end submit onmouseup handler
2748  } localCreateOkClickHandler();
2749 
2750  //:::::::::::::::::::::::::::::::::::::::::
2751  //localCreateMouseHandler ~~
2752  // create mouseover handler
2753  function localCreateMouseHandler()
2754  {
2755  var stopProp = false; //used to stop contextmenu propagation
2756  var rLast = -1, cLast = -1; //to stop redoing calculations in mouse over
2757  //-2 is special all buttons
2758  var buttonDown = -1; //0 - left, 1 - middle, 2 - right
2759 
2760  //::::::::
2761  //localGetRowCol ~~
2762  // returns -1 in r and c for nothing interesting
2763  // returns -2 for special all buttons
2764  // else returns r,c of cell identified by x,y
2765  function localGetRowCol(x,y) {
2766  x -= popSz.x + bmpX + 1;
2767  y -= popSz.y + bmpY + 1;
2768  var r = (y/cellH)|0;
2769  if(y < 0) r = -1; //handle negative fractions clipping to 0
2770  var c = (x/cellW)|0;
2771  if(x < 0) c = -1; //handle negative fractions clipping to 0
2772  var inRowBtnsX = (x >= - axisPaddingMargin - bmpBorderSize - butttonSz) &&
2773  (x <= - axisPaddingMargin - bmpBorderSize);
2774  var inColBtnsY = (y >= bmpH + axisPaddingMargin) &&
2775  (y <= bmpH + axisPaddingMargin + butttonSz + bmpBorderSize*2);
2776 
2777  //Debug.log("i x,y " + x + "," + y);
2778  //Debug.log("i r,c " + r + "," + c);
2779  //Debug.log("inRowBtnsX " + inRowBtnsX);
2780  //Debug.log("inColBtnsY " + inColBtnsY);
2781 
2782  //handle row buttons
2783  if(inRowBtnsX && r >= 0 && r < rows)
2784  return {"r":r, "c":-2};
2785  else if(inColBtnsY && c >= 0 && c < cols) //handle col buttons
2786  return {"r":-2, "c":c};
2787  else if(inRowBtnsX && inColBtnsY) //handle all button
2788  return {"r":-2, "c":-2};
2789  else if(r < 0 || c < 0 || r >= rows || c >= cols)
2790  return {"r":-1, "c":-1}; //is nothing
2791  return {"r":r, "c":c}; //else, is a cell
2792  } //end localGetRowCol()
2793 
2794  //::::::::
2795  //el.onmousemove ~~
2796  el.onmousemove = function(event) {
2797  var cell = localGetRowCol(event.pageX,event.pageY);
2798  var r = cell.r, c = cell.c;
2799 
2800  var cursorT = (event.pageX - popSz.x - bmpX);
2801  if(cursorT < 0) cursorT = 0;
2802  if(cursorT > bmpW) cursorT = bmpW;
2803 
2804  cursorInfo.style.left = (event.pageX - popSz.x +
2805  //(c >= cols/2?-cursorInfo.innerHTML.length*8-20:2)) +
2806  //smooth transition from left-most to right-most info position above cursor
2807  (cursorT)/bmpW*(-cursorInfo.innerHTML.length*8-20) + (bmpW-cursorT)/bmpW*(2))+
2808  "px";
2809  cursorInfo.style.top = (event.pageY - popSz.y - 35) + "px";
2810 
2811  //center header cursor info
2812  hdrCursorInfo.style.left = (bmpX + bmpW/2 +
2813  (-332)/2) + "px"; //hdrCursorInfo.style.width = "320px"; + padding*2 + border*2
2814  hdrCursorInfo.style.top = (bmpY - 45) + "px";
2815 
2816 
2817  if(rLast == r && cLast == c)
2818  return; //already done for this cell, so prevent excessive work
2819  rLast = r; cLast = c;
2820 
2821  if(r == -1 || c == -1) //handle no select case
2822  {
2823  //mouse off interesting things
2824  rLast = -1; cLast = -1;
2825  bmpOverlay.style.display = "none";
2826  cursorInfo.style.display = "none";
2827  hdrCursorInfo.style.display = "none";
2828  return;
2829  }
2830 
2831  cursorInfo.style.display = "block";
2832  //hdrCursorInfo.style.display = "block"; //removed from display.. could delete if unneeded?
2833 
2834  var transRC;
2835  var infoStr;
2836 
2837  //handle row buttons
2838  if(r != -2 && c == -2)
2839  {
2840  if(doSnakeColumns)
2841  transRC = localConvertGridToRowCol(r,
2842  doDisplayColsAscending?0:cols-1);
2843  else
2844  transRC = localConvertGridToRowCol(r,0);
2845 
2846  //make mouse over bitmap
2847  {
2848  bmpOverlay.src = ConfigurationAPI.getOnePixelPngData([216,188,188]);//canvas.toDataURL();
2849 
2850  bmpOverlay.style.left = (bmpX - axisPaddingMargin - bmpBorderSize - butttonSz) + "px";
2851  bmpOverlay.style.top = (bmpY + r*cellH - 1 + (r?bmpGridThickness+bmpBorderSize*2:0)) + "px";
2852  bmpOverlay.style.width = (butttonSz) + "px";
2853  bmpOverlay.style.height = (cellH - (r?bmpGridThickness+bmpBorderSize*2:0)) + "px";
2854  bmpOverlay.style.display = "block";
2855  }
2856 
2857  infoStr = "Set all pixels in row " + transRC[0] + ".";
2858  }
2859  else if(r == -2 && c != -2) //handle col buttons
2860  {
2861  if(doSnakeRows)
2862  transRC = localConvertGridToRowCol(
2863  doDisplayRowsAscending?0:rows-1,c);
2864  else
2865  transRC = localConvertGridToRowCol(0,c);
2866 
2867 
2868  //make mouse over bitmap
2869  {
2870  bmpOverlay.src = ConfigurationAPI.getOnePixelPngData([216,188,188]);//canvas.toDataURL();
2871 
2872  bmpOverlay.style.left = (bmpX + c*cellW - 1 + (c?bmpGridThickness+bmpBorderSize*2:0)) + "px";
2873  bmpOverlay.style.top = (bmpY + bmpH + axisPaddingMargin - bmpBorderSize) + "px";
2874  bmpOverlay.style.width = (cellW + 1 - (c?bmpGridThickness+bmpBorderSize*2:0)) + "px";
2875  bmpOverlay.style.height = (butttonSz) + "px";
2876  bmpOverlay.style.display = "block";
2877  }
2878 
2879  infoStr = "Set all pixels in column " + transRC[1] + ".";
2880  }
2881  else if(r == -2 && c == -2) //handle all button
2882  {
2883  //make mouse over bitmap
2884  {
2885  bmpOverlay.src = ConfigurationAPI.getOnePixelPngData([216,188,188]);//canvas.toDataURL();
2886 
2887  bmpOverlay.style.left = (bmpX - axisPaddingMargin - bmpBorderSize - butttonSz) + "px";
2888  bmpOverlay.style.top = (bmpY + bmpH + axisPaddingMargin - bmpBorderSize) + "px";
2889  bmpOverlay.style.width = (butttonSz) + "px";
2890  bmpOverlay.style.height = (butttonSz) + "px";
2891  bmpOverlay.style.display = "block";
2892  }
2893 
2894  infoStr = "Set all pixels.";
2895  }
2896  else //pixel case
2897  {
2898  transRC = localConvertGridToRowCol(r,c);
2899 
2900  //have mouse over bitmap
2901  {
2902  //make a partial alpha overlay that lightens or darkens
2903  // depending on pixel color
2904  var overClr = (bmpDataImage.data[(r*cols+c)*4+0] +
2905  bmpDataImage.data[(r*cols+c)*4+1] +
2906  bmpDataImage.data[(r*cols+c)*4+2]) < (256+128)?255:0;
2907 
2908  bmpOverlay.src = ConfigurationAPI.getOnePixelPngData(
2909  [overClr,overClr,overClr,100]);
2910 
2911  bmpOverlay.style.left = (bmpX + c*cellW) + "px";
2912  bmpOverlay.style.top = (bmpY + r*cellH) + "px";
2913  bmpOverlay.style.width = (cellW) + "px";
2914  bmpOverlay.style.height = (cellH) + "px";
2915  bmpOverlay.style.display = "block";
2916  }
2917 
2918  //position cursor info
2919  infoStr = "Value = " + bmpData[r][c] + " @ (Row,Col) = (" +
2920  transRC[0] + "," + transRC[1] + ")";
2921  }
2922  cursorInfo.innerHTML = infoStr;
2923  hdrCursorInfo.innerHTML = infoStr;
2924 
2925  //Debug.log("r,c " + r + "," + c);
2926  if(r == -2 && c == -2)
2927  return; //prevent mouse over execution of all button (assume it's accidental)
2928 
2929  if(buttonDown >= 0)
2930  {
2931  stopProp = true;
2932  localSetBitMap(r,c); //set bitmap data
2933  }
2934 
2935  } //end mouse move
2936 
2937  //::::::::
2938  //el.onmousedown ~~
2939  el.onmousedown = function(event) {
2940 
2941  var cell = localGetRowCol(event.pageX,event.pageY);
2942  var r = cell.r, c = cell.c;
2943 
2944  //Debug.log("click which " + event.which);
2945  //Debug.log("click button " + event.button);
2946  buttonDown = event.button;
2947 
2948  if(r == -1 || c == -1) //handle no select case
2949  {
2950  rLast = -1; cLast = -1; //reset for mouse move
2951  stopProp = false;
2952  return;
2953  }
2954 
2955  rLast = r; cLast = c;
2956  localSetBitMap(r,c); //set bitmap data
2957 
2958  stopProp = true;
2959  event.stopPropagation();
2960 
2961  } //end mouse down
2962 
2963  //::::::::
2964  //el.onmouseup ~~
2965  el.onmouseup = function(event) {
2966  //Debug.log("click up ");
2967  buttonDown = -1;
2968  } //end mouse up
2969 
2970  //::::::::
2971  //el.oncontextmenu ~~
2972  el.oncontextmenu = function(event) {
2973  //Debug.log("click stopProp " + stopProp);
2974 
2975  if(stopProp)
2976  {
2977  stopProp = false;
2978  event.stopPropagation();
2979  return false;
2980  }
2981  } //end oncontextmenu
2982 
2983  //::::::::
2984  //localSetBitMap ~~
2985  function localSetBitMap(r,c) {
2986 
2987  Debug.log("set r,c " + buttonDown + " @ " + r + "," + c );
2988  buttonDown = buttonDown?1:0; // 0=left-click, 1=right-click
2989 
2990  var maxr = r==-2?rows-1:r;
2991  var minr = r==-2?0:r;
2992  var maxc = c==-2?cols-1:c;
2993  var minc = c==-2?0:c;
2994 
2995  for(r=minr;r<=maxr;++r)
2996  for(c=minc;c<=maxc;++c)
2997  {
2998  bmpData[r][c] = clickValues[buttonDown];
2999  bmpDataImage.data[(r*cols + c)*4 + 0] =
3000  clickColors[buttonDown][0];
3001  bmpDataImage.data[(r*cols + c)*4 + 1] =
3002  clickColors[buttonDown][1];
3003  bmpDataImage.data[(r*cols + c)*4 + 2] =
3004  clickColors[buttonDown][2];
3005  bmpDataImage.data[(r*cols + c)*4 + 3] =
3006  clickColors[buttonDown][3];
3007  }
3008 
3009  bmpContext.putImageData(bmpDataImage,0,0);
3010  bmp.src = bmpCanvas.toDataURL();
3011  }// end localSetBitMap
3012 
3013  } localCreateMouseHandler();
3014 
3015  //:::::::::::::::::::::::::::::::::::::::::
3016  //localValidateInputs ~~
3017  // returns false if inputs are invalid
3018  // else true.
3019  function localValidateInputs() {
3020 
3021  //veryify bitmap params is expected size
3022  if(bitMapParams.length != 16)
3023  {
3024  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)." +
3025  "\nHere is a printout of the input parameters: " + bitMapParams,Debug.HIGH_PRIORITY);
3026  return false;
3027  }
3028  var DEFAULT = "DEFAULT";
3029 
3030  rows = bitMapParams[0]|0;
3031  cols = bitMapParams[1]|0;
3032  bitFieldSize = bitMapParams[2]|0;
3033 
3034  //js can only handle 31 bits unsigned!! hopefully no one needs it?
3035 
3036  if(rows < 1 || rows >= 1<<30)
3037  {
3038  Debug.log("Illegal input parameters, rows of " + rows + " is illegal. " +
3039  "(rows possible values are from 1 to " + ((1<<30)-1) + ".)",Debug.HIGH_PRIORITY);
3040  return false;
3041  }
3042  if(cols < 1 || cols >= 1<<30)
3043  {
3044  Debug.log("Illegal input parameters, cols of " + cols + " is illegal. " +
3045  "(cols possible values are from 1 to " + ((1<<30)-1) + ".)",Debug.HIGH_PRIORITY);
3046  return false;
3047  }
3048  if(bitFieldSize < 1 || bitFieldSize > 31)
3049  {
3050  Debug.log("Illegal input parameters, bitFieldSize of " + bitFieldSize + " is illegal. " +
3051  "(bitFieldSize possible values are from 1 to " + (31) + ".)",Debug.HIGH_PRIORITY);
3052  return false;
3053  }
3054 
3055 
3056  if(bitFieldSize > 30)
3057  {
3058  bitMask = 0;
3059  for(var i=0;i<bitFieldSize;++i)
3060  bitMask |= 1 << i;
3061  }
3062  else
3063  bitMask = (1<<bitFieldSize) - 1; //wont work for 31 bits (JS is always signed)
3064 
3065  minValue = bitMapParams[3] == "DEFAULT" || bitMapParams[3] == ""?0:(bitMapParams[3]|0);
3066  maxValue = bitMapParams[4] == "DEFAULT" || bitMapParams[4] == ""?bitMask:(bitMapParams[4]|0);
3067  if(maxValue < minValue)
3068  maxValue = bitMask;
3069  midValue = (maxValue + minValue)/2; //used for color calcs
3070  stepValue = bitMapParams[5] == "DEFAULT" || bitMapParams[5] == ""?1:(bitMapParams[5]|0);
3071 
3072  if(minValue < 0 || minValue > bitMask)
3073  {
3074  Debug.log("Illegal input parameters, minValue of " + minValue + " is illegal. " +
3075  "(minValue possible values are from 0 to " + bitMask + ".)",Debug.HIGH_PRIORITY);
3076  return false;
3077  }
3078  if(maxValue < 0 || maxValue > bitMask)
3079  {
3080  Debug.log("Illegal input parameters, maxValue of " + maxValue + " is illegal. " +
3081  "(maxValue possible values are from 0 to " + bitMask + ".)",Debug.HIGH_PRIORITY);
3082  return false;
3083  }
3084  if(minValue > maxValue)
3085  {
3086  Debug.log("Illegal input parameters, minValue > maxValue is illegal.",Debug.HIGH_PRIORITY);
3087  return false;
3088  }
3089  if(stepValue < 1 || stepValue > maxValue - minValue)
3090  {
3091  Debug.log("Illegal input parameters, stepValue of " + stepValue + " is illegal. " +
3092  "(stepValue possible values are from 1 to " + (maxValue - minValue) + ".)",Debug.HIGH_PRIORITY);
3093  return false;
3094  }
3095  if((((maxValue-minValue)/stepValue)|0) != (maxValue-minValue)/stepValue)
3096  {
3097  Debug.log("Illegal input parameters, maxValue of " + maxValue +
3098  " must be an integer number of stepValue (stepValue=" + stepValue +
3099  ") steps away from minValue (minValue=" + minValue + ").",Debug.HIGH_PRIORITY);
3100  return false;
3101  }
3102 
3103  if(bitMapParams[6] != "" &&
3104  bitMapParams[6] != DEFAULT)
3105  {
3106  forcedAspectH = bitMapParams[6].split(':');
3107  if(forcedAspectH.length != 2)
3108  {
3109  Debug.log("Illegal input parameter, expecting ':' in string defining cell display aspect ratio " +
3110  "Height:Width (e.g. 100:150)." +
3111  "\nInput aspect ratio string '" + bitMapParams[6] + "' is invalid.",Debug.HIGH_PRIORITY);
3112  return false;
3113  }
3114  forcedAspectW = forcedAspectH[1].trim()|0;
3115  forcedAspectH = forcedAspectH[0].trim()|0;
3116  }
3117  else //default to 1:1
3118  forcedAspectW = forcedAspectH = 1;
3119 
3120 
3121  //colors
3122  minValueColor = bitMapParams[7] == DEFAULT || bitMapParams[7] == ""?"red":bitMapParams[7];
3123  midValueColor = bitMapParams[8] == DEFAULT || bitMapParams[8] == ""?"yellow":bitMapParams[8];
3124  maxValueColor = bitMapParams[9] == DEFAULT || bitMapParams[9] == ""?"green":bitMapParams[9];
3125  floorValueColor = bitMapParams[10] == DEFAULT || bitMapParams[10] == ""?minValueColor:bitMapParams[10];
3126  ceilValueColor = bitMapParams[11] == DEFAULT || bitMapParams[11] == ""?maxValueColor:bitMapParams[11];
3127 
3128  //convert to arrays
3129  minValueColor = DesktopContent.getColorAsRGBA(minValueColor).split("(")[1].split(")")[0].split(",");
3130  midValueColor = DesktopContent.getColorAsRGBA(midValueColor).split("(")[1].split(")")[0].split(",");
3131  maxValueColor = DesktopContent.getColorAsRGBA(maxValueColor).split("(")[1].split(")")[0].split(",");
3132  ceilValueColor = DesktopContent.getColorAsRGBA(ceilValueColor).split("(")[1].split(")")[0].split(",");
3133  floorValueColor = DesktopContent.getColorAsRGBA(floorValueColor).split("(")[1].split(")")[0].split(",");
3134 
3135  //load bools
3136  doDisplayRowsAscending = bitMapParams[12] == "Yes"?1:0;
3137  doDisplayColsAscending = bitMapParams[13] == "Yes"?1:0;
3138  doSnakeColumns = bitMapParams[14] == "Yes"?1:0;
3139  doSnakeRows = bitMapParams[15] == "Yes"?1:0;
3140 
3141  if(doSnakeColumns && doSnakeRows)
3142  {
3143  Debug.log("Can not have a bitmap that snakes both rows and columns, please choose one or the other (or neither).",Debug.HIGH_PRIORITY);
3144  return false;
3145  }
3146 
3147 
3148  return true;
3149  }
3150 
3151  //:::::::::::::::::::::::::::::::::::::::::
3152  //localInitBitmapData ~~
3153  // load bitmap data from input string <initBitMapValue>
3154  // and initialize bmpDataImage
3155  // treat <initBitMapValue> as JSON 2D array string
3156  function localInitBitmapData()
3157  {
3158  //create empty array for bmpData
3159  bmpData = [];
3160 
3161  try
3162  {
3163  var jsonMatrix = JSON.parse(initBitMapValue);
3164 
3165  //create place holder 2D array for fill
3166  for(var r=0;r<rows;++r)
3167  {
3168  bmpData.push([]); //create empty row array
3169 
3170  for(var c=0;c<cols;++c)
3171  bmpData[r][c] = 0;
3172  }
3173  localConvertFullRowColToGrid(jsonMatrix); //also sets bmpDataImage
3174  }
3175  catch(err)
3176  {
3177  Debug.log("The input initial value of the bitmap is illegal JSON format. " +
3178  "See error below: \n\n" + err,Debug.HIGH_PRIORITY);
3179  Debug.log("Defaulting to initial bitmap with min-value fill.",Debug.HIGH_PRIORITY);
3180 
3181  //min-value fill
3182  var color;
3183  for(var r=0;r<rows;++r)
3184  {
3185  bmpData.push([]); //create empty row array
3186 
3187  for(var c=0;c<cols;++c)
3188  {
3189  bmpData[r][c] = minValue; //min-value entry in column
3190 
3191  color = localConvertValueToRGBA(bmpData[r][c]);
3192  bmpDataImage.data[(r*cols + c)*4+0]=color[0];
3193  bmpDataImage.data[(r*cols + c)*4+1]=color[1];
3194  bmpDataImage.data[(r*cols + c)*4+2]=color[2];
3195  bmpDataImage.data[(r*cols + c)*4+3]=color[3];
3196  }
3197  }
3198 
3199  bmpContext.putImageData(bmpDataImage,0,0);
3200  bmp.src = bmpCanvas.toDataURL();
3201  }
3202  }
3203 
3204  //:::::::::::::::::::::::::::::::::::::::::
3205  //localConvertGridToRowCol ~~
3206  // grid row col is always 0,0 in top left
3207  // but there might be translation for user (imagine snaked columns)
3208  // inputs: doDisplayRowsAscending, doDisplayColsAscending, doSnakeColumns, doSnakeRows,
3209  // ...rows, cols
3210  // return translated row,col
3211  function localConvertGridToRowCol(r,c)
3212  {
3213  var retVal = [r,c];
3214  if(!doDisplayRowsAscending) //reverse row order so flip row
3215  retVal[0] = rows - 1 - retVal[0];
3216  if(!doDisplayColsAscending) //reverse col order so flip col
3217  retVal[1] = cols - 1 - retVal[1];
3218  if(doSnakeRows && retVal[0]%2 == 1) //snake row so flip col
3219  retVal[1] = cols + (cols - 1 - retVal[1]);
3220  if(doSnakeColumns && retVal[1]%2 == 1) //snake col so flip row
3221  retVal[0] = rows + (rows - 1 - retVal[0]);
3222 
3223  return retVal;
3224  }
3225 
3226  //:::::::::::::::::::::::::::::::::::::::::
3227  //localConvertValueToRGBA ~~
3228  // conver bitfield value to RGBA based on input parameters
3229  function localConvertValueToRGBA(val)
3230  {
3231  if(val >= maxValue)
3232  return [ceilValueColor[0],
3233  ceilValueColor[1],
3234  ceilValueColor[2],
3235  255]; //always max alpha
3236 
3237  if(val <= minValue)
3238  return [floorValueColor[0],
3239  floorValueColor[1],
3240  floorValueColor[2],
3241  255]; //always max alpha
3242 
3243  if(val == midValue) //avoid dividing by 0 in blend
3244  return [midValueColor[0],
3245  midValueColor[1],
3246  midValueColor[2],
3247  255]; //always max alpha
3248 
3249  //blend lower half
3250  var t;
3251  if(val <= midValue)
3252  {
3253  t = (val - minValue)/(midValue - minValue);
3254  return [minValueColor[0]*(1-t) + t*midValueColor[0],
3255  minValueColor[1]*(1-t) + t*midValueColor[1],
3256  minValueColor[2]*(1-t) + t*midValueColor[2],
3257  255]; //always max alpha
3258  }
3259  //blend upper half
3260  //if(val >= midValue)
3261  {
3262  t = (val - midValue)/(maxValue - midValue);
3263  return [midValueColor[0]*(1-t) + t*maxValueColor[0],
3264  midValueColor[1]*(1-t) + t*maxValueColor[1],
3265  midValueColor[2]*(1-t) + t*maxValueColor[2],
3266  255]; //always max alpha
3267  }
3268  }
3269 
3270 
3271  //:::::::::::::::::::::::::::::::::::::::::
3272  //localConvertFullGridToRowCol ~~
3273  // convert bmpData matrix to a matrix with translated Row,Col pairs
3274  function localConvertFullGridToRowCol()
3275  {
3276  var retArr = [];
3277  var convertedRC;
3278  for(var r=0;r<rows;++r)
3279  for(var c=0;c<cols;++c)
3280  {
3281  convertedRC = localConvertGridToRowCol(r,c);
3282  //if doSnakeColumns, odd columns are considered to be in even column
3283  if(doSnakeColumns)
3284  convertedRC[1] = (convertedRC[1]/2)|0;
3285  //if doSnakeRows, odd rows are considered to be in even row
3286  if(doSnakeRows)
3287  convertedRC[0] = (convertedRC[0]/2)|0;
3288 
3289  if(retArr[convertedRC[0]] === undefined)
3290  retArr[convertedRC[0]] = []; //create row for first time
3291  retArr[convertedRC[0]][convertedRC[1]] = bmpData[r][c];
3292  }
3293  return retArr;
3294  }
3295 
3296  //:::::::::::::::::::::::::::::::::::::::::
3297  //localConvertFullRowColToGrid ~~
3298  // convert a matrix with translated Row,Col pairs to bmpData matrix
3299  // updates bmpDataImage also and bmp display
3300  function localConvertFullRowColToGrid(srcMatrix)
3301  {
3302  var convertedRC;
3303  var color;
3304  var noErrors = true;
3305  for(var r=0;r<rows;++r)
3306  for(var c=0;c<cols;++c)
3307  {
3308  convertedRC = localConvertGridToRowCol(r,c);
3309 
3310  //if doSnakeColumns, odd columns are considered to be in even column
3311  if(doSnakeColumns)
3312  convertedRC[1] = (convertedRC[1]/2)|0;
3313  //if doSnakeRows, odd rows are considered to be in even row
3314  if(doSnakeRows)
3315  convertedRC[0] = (convertedRC[0]/2)|0;
3316  try
3317  {
3318  bmpData[r][c] = srcMatrix[convertedRC[0]][convertedRC[1]]|0;
3319  if(bmpData[r][c] < minValue)
3320  throw("There was an illegal value less than minValue: " +
3321  bmpData[r][c] + " < " + minValue + " @ (row,col) = (" +
3322  convertedRC[0] + "," + convertedRC[0] + ")");
3323  if(bmpData[r][c] > maxValue)
3324  throw("There was an illegal value greater than maxValue: " +
3325  bmpData[r][c] + " > " + maxValue + " @ (row,col) = (" +
3326  convertedRC[0] + "," + convertedRC[0] + ")");
3327  if((((bmpData[r][c]-minValue)/stepValue)|0) != (bmpData[r][c]-minValue)/stepValue)
3328  throw("There was an illegal value not following stepValue from minValue: " +
3329  bmpData[r][c] + " != " +
3330  (stepValue*(((bmpData[r][c]-minValue)/stepValue)|0)) +
3331  " @ (row,col) = (" +
3332  convertedRC[0] + "," + convertedRC[0] + ")");
3333  color = localConvertValueToRGBA(bmpData[r][c]);
3334  bmpDataImage.data[(r*cols + c)*4+0]=color[0];
3335  bmpDataImage.data[(r*cols + c)*4+1]=color[1];
3336  bmpDataImage.data[(r*cols + c)*4+2]=color[2];
3337  bmpDataImage.data[(r*cols + c)*4+3]=color[3];
3338  }
3339  catch(err)
3340  {noErrors = false;} //ignore errors
3341  }
3342  bmpContext.putImageData(bmpDataImage,0,0);
3343  bmp.src = bmpCanvas.toDataURL();
3344 
3345  if(!noErrors)
3346  throw("There was a mismatch in row/col dimensions. Input matrix was " +
3347  "dimension [row,col] = [" + srcMatrix.length + "," +
3348  (srcMatrix.length?srcMatrix[0].length:0) + "]");
3349  }
3350 
3351  //:::::::::::::::::::::::::::::::::::::::::
3352  //localCreateBitmap ~~
3353  // create bitmap
3354  function localCreateBitmap()
3355  {
3356  bmp = document.createElement("img");
3357  bmp.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap");
3358 
3359  bmpGrid = document.createElement("div"); //div of row and col grid divs
3360  bmpGrid.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid");
3361 
3362  bmpOverlay = document.createElement("img");
3363  bmpOverlay.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-overlay");
3364 
3365  cursorInfo = document.createElement("div"); //div of row and col grid divs
3366  cursorInfo.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-cursor-info");
3367  hdrCursorInfo = document.createElement("div"); //div of row and col grid divs
3368  hdrCursorInfo.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-cursor-hdrInfo");
3369 
3370  //create divs for r,c text display
3371  rowLeftNums = document.createElement("div");
3372  rowRightNums = document.createElement("div");
3373  colTopNums = document.createElement("div");
3374  colBottomNums = document.createElement("div");
3375  rowLeftNums.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-numbers-rowLeft");
3376  rowRightNums.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-numbers-rowRight");
3377  colTopNums.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-numbers-colTop");
3378  colBottomNums.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-numbers-colBottom");
3379 
3380  var tmpEl;
3381 
3382  //group creation of row/col elements
3383  {
3384  bmpCanvas=document.createElement("canvas");
3385  bmpCanvas.width = cols;
3386  bmpCanvas.height = rows;
3387  bmpContext = bmpCanvas.getContext("2d");
3388 
3389  if(bmpDataImage) delete bmpDataImage;
3390  bmpDataImage = bmpContext.createImageData(cols,rows);
3391 
3392  //add outside box div as child 0
3393  tmpEl = document.createElement("div");
3394  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-box");
3395  bmpGrid.appendChild(tmpEl);
3396 
3397  for(var i=0;i<rows;++i)
3398  {
3399  if(i < rows - 1) //add internal row divs to start
3400  {
3401  tmpEl = document.createElement("div");
3402  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-row-dark");
3403  bmpGrid.appendChild(tmpEl);
3404  tmpEl = document.createElement("div");
3405  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-row");
3406  bmpGrid.appendChild(tmpEl);
3407  }
3408 
3409  for(var j=0;j<cols;++j)
3410  {
3411  if(i == rows-1 & j < cols-1) //add internal col divs at end
3412  {
3413  tmpEl = document.createElement("div");
3414  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-col-dark");
3415  bmpGrid.appendChild(tmpEl);
3416  tmpEl = document.createElement("div");
3417  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-grid-col");
3418  bmpGrid.appendChild(tmpEl);
3419  }
3420  }
3421  }
3422 
3423  bmpContext.putImageData(bmpDataImage,0,0);
3424  bmp.src = bmpCanvas.toDataURL();
3425  }
3426 
3427  bmp.style.position = "absolute";
3428  bmp.draggable = false; //prevent dragging
3429 
3430  bmpGrid.style.position = "absolute";
3431 
3432  bmpOverlay.style.display = "none";
3433  bmpOverlay.style.position = "absolute";
3434  bmpOverlay.draggable = false; //prevent dragging
3435 
3436  cursorInfo.style.position = "absolute";
3437  cursorInfo.style.display = "none";
3438  hdrCursorInfo.style.position = "absolute";
3439  hdrCursorInfo.style.display = "none";
3440  hdrCursorInfo.style.width = "320px";
3441 
3442  rowLeftNums.style.position = "absolute";
3443  rowRightNums.style.position = "absolute";
3444  colTopNums.style.position = "absolute";
3445  colBottomNums.style.position = "absolute";
3446 
3447  el.appendChild(bmp);
3448  el.appendChild(bmpGrid);
3449  el.appendChild(bmpOverlay);
3450 
3451  el.appendChild(hdrCursorInfo); //insert hdrInfo first so cursorInfo goes over top of it
3452  el.appendChild(cursorInfo);
3453 
3454  el.appendChild(rowLeftNums);
3455  el.appendChild(rowRightNums);
3456  el.appendChild(colTopNums);
3457  el.appendChild(colBottomNums);
3458  }
3459 
3460  //:::::::::::::::::::::::::::::::::::::::::
3461  //localCreateGridButtons ~~
3462  // create all (row,col) buttons
3463  function localCreateGridButtons()
3464  {
3465  allRowBtns = document.createElement("div"); //div of all row button divs
3466 
3467  allColBtns = document.createElement("div"); //div of all col button divs
3468 
3469  allBtn = document.createElement("div"); //div of all button
3470  allBtn.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-btn-all");
3471 
3472  var tmpEl;
3473  for(var i=0;i<rows;++i)
3474  {
3475  tmpEl = document.createElement("div");
3476  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-btn-all");
3477  tmpEl.style.position = "absolute";
3478  allRowBtns.appendChild(tmpEl);
3479  }
3480  for(var i=0;i<cols;++i)
3481  {
3482  tmpEl = document.createElement("div");
3483  tmpEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-btn-all");
3484  tmpEl.style.position = "absolute";
3485  allColBtns.appendChild(tmpEl);
3486  }
3487 
3488  allRowBtns.style.position = "absolute";
3489  el.appendChild(allRowBtns);
3490  allColBtns.style.position = "absolute";
3491  el.appendChild(allColBtns);
3492  allBtn.style.position = "absolute";
3493  el.appendChild(allBtn);
3494  }
3495 
3496  //:::::::::::::::::::::::::::::::::::::::::
3497  //localCreateHeader ~~
3498  // create header
3499  function localCreateHeader()
3500  {
3501  hdr = document.createElement("div");
3502  hdr.setAttribute("id", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-header");
3503 
3504  var str = "";
3505 
3506  str += "<div style='float:left; margin: 0 0 20px 0;'>"; //field name and info container
3507  str += "<div style='float:left; '>";
3508  str += fieldName;
3509 // fieldName = fieldName.split('\n');
3510 // str += "Target UID/Field: &quot;" +
3511 // fieldName[0].split(" : ")[1] + "/" +
3512 // fieldName[1].split(" : ")[0] + "&quot;";
3513  str += "</div>";
3514 
3515  str += "<div style='float:left; margin-left: 50px;'>";
3516  str += "Number of [Rows,Cols]: " + "[" + rows + "," + cols + "]";
3517  str += "</div>";
3518  str += "</div>";//end field name and info container
3519 
3520  str += "<div style='float:right; '>";
3521  str += "<a id='" +
3522  ConfigurationAPI._POP_UP_DIALOG_ID +
3523  "-cancel' href='#'>Cancel</a>";
3524  str += "</div>";
3525 
3526  str += "<div id='clearDiv'></div>";
3527 
3528  str += "<div style='float:right; margin: 40px 20px -50px 0;'>";
3529  str += "<a id='" +
3530  ConfigurationAPI._POP_UP_DIALOG_ID +
3531  "-ok' href='#'>OK</a>";
3532  str += "</div>";
3533 
3534  str += "<div style='float:left; margin: 0 0 0 0;'>";
3535  for(var clickIndex=0;clickIndex<2;++clickIndex)
3536  {
3537  str += "<div style='float:left; margin: 5px 0 0 0;'>";
3538  str += "<div style='float:left; width:180px; text-align:right; margin-top: 3px;'>";
3539  str += (clickIndex?"Right":"Left") + "-Click Value:";
3540  str += "</div>";
3541  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3542  "-bitmap-scrollbar' style='float:left;' " +
3543  "type='range' min='" + minValue +
3544  "' max='" + maxValue + "' value='" + (clickIndex?maxValue:minValue) +
3545  "' step='" + stepValue +
3546  "' oninput='ConfigurationAPI.bitMapDialog.localUpdateScroll(" + clickIndex + ")' />";
3547  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3548  "-bitmap-btnInput' style='float:left; margin: 0 1px 0 5px;' " +
3549  "type='button' value='<' " +
3550  "onmousedown='ConfigurationAPI.bitMapDialog.localUpdateButtonInput(" + clickIndex + ",0,0)' " +
3551  "onmouseup='ConfigurationAPI.bitMapDialog.localUpdateButtonInput(" + clickIndex + ",0,1)' " +
3552  "/> ";
3553  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3554  "-bitmap-btnInput' style='float:left;' " +
3555  "type='button' value='>' " +
3556  "onmousedown='ConfigurationAPI.bitMapDialog.localUpdateButtonInput(" + clickIndex + ",1,0)' " +
3557  "onmouseup='ConfigurationAPI.bitMapDialog.localUpdateButtonInput(" + clickIndex + ",1,1)' " +
3558  "/> ";
3559  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3560  "-bitmap-textInput' style='float:left; margin: 0 5px 0 5px; width: 50px;' " +
3561  "type='text' " + //value come from scroll update at start
3562  "onchange='ConfigurationAPI.bitMapDialog.localUpdateTextInput(" + clickIndex + ",1)' " +
3563  "onkeydown='ConfigurationAPI.bitMapDialog.localUpdateTextInput(" + clickIndex + ",0)' " +
3564  "onkeyup='ConfigurationAPI.bitMapDialog.localUpdateTextInput(" + clickIndex + ",0)' " +
3565  "/>";
3566  str += "<img class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3567  "-bitmap-colorSample' style='float:left;width:25px; height:25px; margin: -2px 0 2px 0;' " +
3568  "ondragstart='return false;' " + //ondragstart for firefox
3569  "draggable='false'" + //draggable for chrome
3570  "'/>";
3571 
3572 
3573  str += "</div>";
3574 
3575  str += "<div id='clearDiv'></div>";
3576  }
3577  str += "</div>";
3578 
3579  //add download upload buttons
3580  str += "<div style='float:left; margin: 5px 0 0 40px;'>";
3581  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3582  "-bitmap-btnCsv' style='float:left;' " +
3583  "type='button' value='Download as CSV' " +
3584  "onclick='ConfigurationAPI.bitMapDialog.localDownloadCSV()' " +
3585  "/> ";
3586  str += "<input class='" + ConfigurationAPI._POP_UP_DIALOG_ID +
3587  "-bitmap-btnCsv' style='float:left; margin: 0 0 0 10px;' " +
3588  "type='button' value='Upload CSV' " +
3589  "onclick='ConfigurationAPI.bitMapDialog.locaPopupUploadCSV()' " +
3590  "/> ";
3591  str += "</div>";
3592 
3593  hdr.innerHTML = str;
3594  hdr.style.overflowY = "auto";
3595  hdr.style.position = "absolute";
3596 
3597  var scrollEls = hdr.getElementsByClassName(ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-scrollbar");
3598  var textInputEls = hdr.getElementsByClassName(ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-textInput");
3599  var colorSampleEls = hdr.getElementsByClassName(ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-colorSample");
3600 
3601 
3602  //::::::::::
3603  //localUpdateScroll ~~
3604  ConfigurationAPI.bitMapDialog.localUpdateScroll = function(i)
3605  {
3606  Debug.log("localUpdateScroll " + i);
3607 
3608  clickValues[i] = scrollEls[i].value|0;
3609  clickColors[i] = localConvertValueToRGBA(clickValues[i]);
3610 
3611  textInputEls[i].value = clickValues[i];
3612  colorSampleEls[i].src = ConfigurationAPI.getOnePixelPngData(clickColors[i]);
3613  }; //end localUpdateScroll
3614 
3615  //::::::::::
3616  //localUpdateTextInput ~~
3617  ConfigurationAPI.bitMapDialog.localUpdateTextInput = function(i,finalChange)
3618  {
3619  Debug.log("localUpdateTextInput " + textInputEls[i].value + " " + finalChange);
3620 
3621  clickValues[i] = textInputEls[i].value|0;
3622 
3623  if(finalChange)
3624  {
3625  if(clickValues[i] < minValue) clickValues[i] = minValue;
3626  if(clickValues[i] > maxValue) clickValues[i] = maxValue;
3627  clickValues[i] = (((clickValues[i]-minValue)/stepValue)|0)*stepValue + minValue; //lock to step
3628  textInputEls[i].value = clickValues[i]; //fix value
3629  }
3630  else //try to continue with change, but if invalid just return
3631  {
3632  if(clickValues[i] < minValue) return;
3633  if(clickValues[i] > maxValue) return;
3634  if((((clickValues[i]-minValue)/stepValue)|0) != (clickValues[i]-minValue)/stepValue)
3635  return; //no locked to step value
3636  Debug.log("displaying change");
3637  }
3638  clickColors[i] = localConvertValueToRGBA(clickValues[i]);
3639 
3640  scrollEls[i].value = clickValues[i];
3641  colorSampleEls[i].src = ConfigurationAPI.getOnePixelPngData(clickColors[i]);
3642  }; //end localUpdateTextInput
3643 
3644  //::::::::::
3645  //localUpdateButtonInput ~~
3646  var mouseDownTimer = 0;
3647  ConfigurationAPI.bitMapDialog.localUpdateButtonInput = function(i,dir,mouseUp,delay)
3648  {
3649  window.clearInterval(mouseDownTimer);
3650  if(mouseUp) //mouse up
3651  {
3652  Debug.log("cancel mouse down");
3653  return;
3654  }
3655  //else mouse is down so set repeat interval
3656  mouseDownTimer = window.setInterval(function()
3657  {
3658  //faster and faster
3659  if(delay > 50) delay -= 50;
3660  ConfigurationAPI.bitMapDialog.localUpdateButtonInput(i,dir,0,50); //same as this call
3661  },delay!==undefined?delay:300);
3662 
3663  Debug.log("localUpdateButtonInput " + textInputEls[i].value + " " + dir);
3664 
3665  clickValues[i] = clickValues[i] + (dir?stepValue:-stepValue);
3666  if(clickValues[i] < minValue) clickValues[i] = minValue;
3667  if(clickValues[i] > maxValue) clickValues[i] = maxValue;
3668 
3669  clickColors[i] = localConvertValueToRGBA(clickValues[i]);
3670 
3671  textInputEls[i].value = clickValues[i];
3672  scrollEls[i].value = clickValues[i];
3673  colorSampleEls[i].src = ConfigurationAPI.getOnePixelPngData(clickColors[i]);
3674 
3675  }; //end localUpdateButtonInput
3676 
3677  //::::::::::
3678  //localDownloadCSV ~~
3679  // online people complain that this doesn't work for big files.
3680  // Note: if files are too big, could have server create csv file
3681  // then link to <a href='/WebPath/download.csv' download></a>
3682  // Note: href must be encodeURI(dataStr) if data not already encoded
3683  ConfigurationAPI.bitMapDialog.localDownloadCSV = function()
3684  {
3685  var transGrid = localConvertFullGridToRowCol();
3686  console.log(transGrid);
3687 
3688  var dataStr = "data:text/csv;charset=utf-8,";
3689 
3690  for(var r=0;r<transGrid.length;++r)
3691  {
3692  if(r) dataStr += encodeURI("\n"); //encoded \n
3693  for(var c=0;c<transGrid[0].length;++c)
3694  {
3695  if(c) dataStr += ",";
3696  dataStr += transGrid[r][c];
3697  }
3698  }
3699 
3700  Debug.log("ConfigurationAPI.bitMapDialog.localDownloadCSV dataStr=" + dataStr);
3701 
3702  var link = document.createElement("a");
3703  link.setAttribute("href", dataStr); //double encode, so encoding remains in CSV
3704  link.setAttribute("style", "display:none");
3705  link.setAttribute("download", _currentConfigName + "_" +
3706  fieldName + "_download.csv");
3707  document.body.appendChild(link); // Required for FF
3708 
3709  link.click(); // This will download the data file named "my_data.csv"
3710 
3711  link.parentNode.removeChild(link);
3712  }; //end localDownloadCSV
3713 
3714 
3715 
3716  //::::::::::
3717  //locaUploadCSV ~~
3718  ConfigurationAPI.bitMapDialog._csvUploadDataStr; //uploaded csv table ends up here
3719  ConfigurationAPI.bitMapDialog.locaUploadCSV = function()
3720  {
3721  Debug.log("locaUploadCSV ConfigurationAPI.bitMapDialog._csvUploadDataStr = " + ConfigurationAPI.bitMapDialog._csvUploadDataStr);
3722  var srcDataStr = ConfigurationAPI.bitMapDialog._csvUploadDataStr.split('\n');
3723  var src = []; //src = [r][c]
3724  for(var i=0;i<srcDataStr.length;++i)
3725  src.push(srcDataStr[i].split(','));
3726  console.log(src);
3727 
3728  try
3729  {
3730  localConvertFullRowColToGrid(src);
3731 
3732  Debug.log("Successfully uploaded CSV file to bitmap!", Debug.INFO_PRIORITY);
3733 
3734  //on succes remove popup
3735  el = document.getElementById("popUpDialog");
3736  if(el) el.parentNode.removeChild(el);
3737  }
3738  catch(err)
3739  {
3740  Debug.log("Errors occured during upload. Bitmap may not reflect contents of CSV file." +
3741  "\nHere is the error description: \n" + err, Debug.HIGH_PRIORITY);
3742 
3743  //enable button so upload can be tried again
3744  document.getElementById('popUpDialog-submitButton').disabled = false;
3745  }
3746  }
3747 
3748  //::::::::::
3749  //locaPopupUploadCSV ~~
3750  ConfigurationAPI.bitMapDialog.locaPopupUploadCSV = function()
3751  {
3752  Debug.log("ConfigurationAPI.bitMapDialog.locaPopupUploadCSV");
3753  ConfigurationAPI.bitMapDialog._csvUploadDataStr = ""; //clear previous upload
3754 
3755  var str = "";
3756 
3757  var pel = document.getElementById("popUpDialog");
3758  if(!pel)
3759  {
3760  pel = document.createElement("div");
3761  pel.setAttribute("id", "popUpDialog");
3762  }
3763  pel.style.display = "none";
3764 
3765  //set position and size
3766  var w = 380;
3767  var h = 195;
3768  ConfigurationAPI.setPopUpPosition(pel,w /*w*/,h /*h*/);
3769 
3770  var str = "<a id='" +
3771  "popUpDialog" + //clear upload string on cancel!
3772  "-header' href='#' onclick='javascript:ConfigurationAPI.bitMapDialog._csvUploadDataStr = \"\"; var pel = document.getElementById(" +
3773  "\"popUpDialog\"); if(pel) pel.parentNode.removeChild(pel); return false;'>Cancel</a><br><br>";
3774 
3775  str += "<div id='popUpDialog-div'>";
3776 
3777  str += "Please choose a CSV formatted data file (i.e. commas for columns, and new lines for rows) " +
3778  "to upload:<br><br>";
3779 
3780  str += "<center>";
3781 
3782  str += "<input type='file' id='popUpDialog-fileUpload' " +
3783  "accept='.csv' enctype='multipart/form-data' />";
3784 
3785  // done with special handling
3786  // continue with pop-up prompt
3787  str += "</center></div><br><br>"; //close main popup div
3788 
3789  var onmouseupJS = "";
3790  onmouseupJS += "document.getElementById(\"popUpDialog-submitButton\").disabled = true;";
3791  onmouseupJS += "ConfigurationAPI.bitMapDialog.locaUploadCSV();";
3792 
3793  str += "<input id='popUpDialog-submitButton' disabled type='button' onmouseup='" +
3794  onmouseupJS + "' " +
3795  "value='Upload File' title='" +
3796  "Upload the chosen file to replace the row,col data in the current bitmap." +
3797  "'/>";
3798 
3799  pel.innerHTML = str;
3800  el.appendChild(pel); //add element to bitmap div
3801  pel.style.display = "block";
3802 
3803  document.getElementById('popUpDialog-fileUpload').addEventListener(
3804  'change', function(evt) {
3805  var files = evt.target.files;
3806  var file = files[0];
3807  var reader = new FileReader();
3808  reader.onload = function() {
3809  //store uploaded file and enable button
3810  ConfigurationAPI.bitMapDialog._csvUploadDataStr = this.result;
3811  Debug.log("ConfigurationAPI.bitMapDialog._csvUploadDataStr = " + ConfigurationAPI.bitMapDialog._csvUploadDataStr);
3812  document.getElementById('popUpDialog-submitButton').disabled = false;
3813  }
3814  reader.readAsText(file);
3815  }, false);
3816 
3817  }; //end locaUploadCSV
3818 
3819 
3820  el.appendChild(hdr);
3821 
3822  ConfigurationAPI.bitMapDialog.localUpdateScroll(0);
3823  ConfigurationAPI.bitMapDialog.localUpdateScroll(1);
3824 
3825  } //end localCreateHeader()
3826 
3827  //:::::::::::::::::::::::::::::::::::::::::
3828  //localPaint ~~
3829  // called every time window is resized
3830  function localPaint()
3831  {
3832  Debug.log("localPaint");
3833 
3834  popSz = ConfigurationAPI.setPopUpPosition(el,undefined,undefined,padding,undefined,
3835  30 /*margin*/, true /*doNotResize*/);
3836 
3837  hdrW = popSz.w;
3838  //axisPadding = 40 + axisPaddingExtra;
3839  hdrX = padding;
3840  hdrY = padding;
3841  hdrW = popSz.w;
3842  hdrH = 150;
3843  bmpX = padding;
3844  bmpY = hdrY+hdrH+padding;
3845  bmpW = popSz.w - 2*axisPadding;
3846  bmpH = popSz.h - hdrH - padding - 2*axisPadding;
3847 
3848  cellW = bmpW/cols;
3849  cellH = bmpH/rows;
3850 
3851  localOptimizeAspectRatio(); //sets up bmpX,Y based on aspect ratio (inputs are cellW/cellH, bmpW/bmpH, rows/cols)
3852 
3853  //place header
3854  hdr.style.left = hdrX + "px";
3855  hdr.style.top = hdrY + "px";
3856  hdr.style.width = hdrW + "px";
3857  hdr.style.height = hdrH + "px";
3858 
3859  //place bitmap
3860  bmp.style.left = bmpX + "px";
3861  bmp.style.top = bmpY + "px";
3862  bmp.style.width = bmpW + "px";
3863  bmp.style.height = bmpH + "px";
3864 
3865 
3866  //place bitmap grid and buttons
3867  {
3868 
3869  bmpGrid.style.left = (bmpX-bmpBorderSize) + "px";
3870  bmpGrid.style.top = (bmpY-bmpBorderSize) + "px";
3871  bmpGrid.style.width = (bmpW) + "px";
3872  bmpGrid.style.height = (bmpH) + "px";
3873 
3874  var bmpGridChildren = bmpGrid.childNodes;
3875 
3876  //place all rows div
3877  allRowBtns.style.left = (bmpX - bmpBorderSize - axisPaddingMargin - bmpBorderSize - butttonSz) + "px";
3878  allRowBtns.style.top = (bmpY - bmpBorderSize) + "px";
3879  //place all cols div
3880  allColBtns.style.left = (bmpX - bmpBorderSize) + "px";
3881  allColBtns.style.top = (bmpY + bmpH + axisPaddingMargin - bmpBorderSize*2) + "px";
3882  //place all div
3883  allBtn.style.left = (bmpX - bmpBorderSize - axisPaddingMargin - bmpBorderSize - butttonSz) + "px";
3884  allBtn.style.top = (bmpY + bmpH + axisPaddingMargin - bmpBorderSize*2) + "px";
3885  allBtn.style.width = butttonSz + "px";
3886  allBtn.style.height = butttonSz + "px";
3887 
3888  var allRowsChildren = allRowBtns.childNodes;
3889  var allColsChildren = allColBtns.childNodes;
3890 
3891 
3892  //place number divs
3893  rowLeftNums.style.left = (bmpX - bmpBorderSize - axisPaddingMargin - bmpBorderSize - butttonSz + (- bmpBorderSize - axisPaddingMargin - axisPaddingExtra)) + "px";
3894  rowLeftNums.style.top = (bmpY - bmpBorderSize) + "px";
3895  rowRightNums.style.left = (bmpX + bmpW + axisPaddingMargin + bmpBorderSize) + "px";
3896  rowRightNums.style.top = (bmpY - bmpBorderSize) + "px";
3897  colTopNums.style.left = (bmpX - bmpBorderSize) + "px";
3898  colTopNums.style.top = (bmpY - bmpBorderSize*2 - numberDigitH) + "px";
3899  colBottomNums.style.left = (bmpX - bmpBorderSize) + "px";
3900  colBottomNums.style.top = (bmpY + bmpH + bmpBorderSize + axisPaddingMargin + bmpBorderSize + butttonSz + bmpBorderSize) + "px";
3901  rowLeftNums.innerHTML = ""; //clear all children
3902  rowRightNums.innerHTML = ""; //clear all children
3903  colTopNums.innerHTML = ""; //clear all children
3904  colBottomNums.innerHTML = ""; //clear all children
3905 
3906  var thresholdNumberSpacing = 100; //once threshold is reached another number is shown
3907  var numberLoc = []; //used to keep track of spacing
3908  var oldNumberLoc = [-thresholdNumberSpacing,-thresholdNumberSpacing]; //used to keep track of spacing
3909  var numberEl;
3910  var translatedRC;
3911 
3912  //outer box
3913  bmpGridChildren[0].style.left = 0 + "px";
3914  bmpGridChildren[0].style.top = 0 + "px";
3915  bmpGridChildren[0].style.width = (bmpW) + "px";
3916  bmpGridChildren[0].style.height = (bmpH) + "px";
3917 
3918  //place rows
3919  for(var i=0;i<rows;++i)
3920  {
3921  if(i<rows-1)
3922  {
3923  //dark
3924  bmpGridChildren[1+i*2].style.left = bmpBorderSize + "px";
3925  bmpGridChildren[1+i*2].style.top = ((i+1)*cellH) + "px";//((i+1)*cellH + bmpBorderSize) + "px";
3926  bmpGridChildren[1+i*2].style.width = (bmpW) + "px";
3927  bmpGridChildren[1+i*2].style.height = (bmpGridThickness+bmpBorderSize*2) + "px";
3928 
3929  //light
3930  bmpGridChildren[1+i*2+1].style.left = 0 + "px";
3931  bmpGridChildren[1+i*2+1].style.top = ((i+1)*cellH + bmpBorderSize) + "px";//((i+1)*cellH + bmpBorderSize*2) + "px";
3932  bmpGridChildren[1+i*2+1].style.width = (bmpW + bmpBorderSize*2) + "px";
3933  bmpGridChildren[1+i*2+1].style.height = bmpGridThickness + "px";//((doSnakeRows && i%2 == 1)?0:bmpGridThickness) + "px";
3934 
3935  bmpGridChildren[1+i*2+1].style.backgroundColor = //change color if snaking
3936  (doSnakeRows && i%2 == 1)?"rgb(100,100,100)":"#efeaea";
3937  }
3938 
3939  //row button
3940  allRowsChildren[i].style.left = 0 + "px";
3941  allRowsChildren[i].style.top = (i*cellH + (i?bmpGridThickness+bmpBorderSize*2-1:0)) + "px";
3942  allRowsChildren[i].style.width = (butttonSz) + "px";
3943  allRowsChildren[i].style.height = (cellH - 1 + (i?-bmpBorderSize*2:0)) + "px";
3944 
3945  //numbers
3946  {
3947  numberLoc[0] = (i*cellH - 1 + cellH/2 - numberDigitH/2 + (i?bmpGridThickness+bmpBorderSize*2:0));
3948 
3949  //rowLeft numbers
3950  translatedRC = localConvertGridToRowCol(i,0);
3951  if(numberLoc[0] - oldNumberLoc[0] >= thresholdNumberSpacing &&
3952  translatedRC[0]%5 == 0)
3953  {
3954  //add a number
3955  numberEl = document.createElement("div");
3956  numberEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-number");
3957  numberEl.innerHTML = translatedRC[0];
3958  numberEl.style.top = numberLoc[0] + "px";
3959  numberEl.style.width = axisPaddingExtra + "px";
3960  rowLeftNums.appendChild(numberEl);
3961  oldNumberLoc[0] = numberLoc[0];
3962  }
3963 
3964  //rowRight numbers
3965  translatedRC = localConvertGridToRowCol(i,cols>1?1:0);
3966  if(numberLoc[0] - oldNumberLoc[1] >= thresholdNumberSpacing &&
3967  translatedRC[0]%5 == 0)
3968  {
3969  //add a number
3970  numberEl = document.createElement("div");
3971  numberEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-number");
3972  numberEl.innerHTML = translatedRC[0];
3973  numberEl.style.top = numberLoc[0] + "px";
3974  numberEl.style.width = axisPaddingExtra + "px";
3975  rowRightNums.appendChild(numberEl);
3976  oldNumberLoc[1] = numberLoc[0];
3977  }
3978  }
3979  }
3980 
3981  oldNumberLoc = [-thresholdNumberSpacing,-thresholdNumberSpacing]; //reset, used to keep track of spacing
3982  //place cols
3983  for(var i=0;i<cols;++i)
3984  {
3985  if(i<cols-1)
3986  {
3987  //if snaking cols, then darken every other
3988  // by making light width = 0
3989 
3990  //dark
3991  bmpGridChildren[1+(rows-1)*2+i*2].style.top = bmpBorderSize + "px";
3992  bmpGridChildren[1+(rows-1)*2+i*2].style.left = ((i+1)*cellW + bmpBorderSize) + "px";
3993  bmpGridChildren[1+(rows-1)*2+i*2].style.height = (bmpH) + "px";
3994  bmpGridChildren[1+(rows-1)*2+i*2].style.width = (bmpGridThickness+bmpBorderSize*2) + "px";
3995 
3996  //light
3997  bmpGridChildren[1+(rows-1)*2+i*2+1].style.top = 0 + "px";
3998  bmpGridChildren[1+(rows-1)*2+i*2+1].style.left = ((i+1)*cellW + bmpBorderSize*2) + "px";
3999  bmpGridChildren[1+(rows-1)*2+i*2+1].style.height = (bmpH + bmpBorderSize*2) + "px";
4000  bmpGridChildren[1+(rows-1)*2+i*2+1].style.width = bmpGridThickness + "px"; //((doSnakeColumns && i%2 == 1)?0:bmpGridThickness) + "px";
4001 
4002  bmpGridChildren[1+(rows-1)*2+i*2+1].style.backgroundColor = //change color if snaking
4003  (doSnakeColumns && i%2 == 1)?"rgb(100,100,100)":"#efeaea";
4004  }
4005 
4006  //row button
4007  allColsChildren[i].style.left = (i*cellW - 1 + (i?bmpGridThickness+bmpBorderSize*2:0)) + "px";
4008  allColsChildren[i].style.top = 0 + "px";
4009  allColsChildren[i].style.width = (cellW + 1 - (i?bmpGridThickness+bmpBorderSize*2:0)) + "px";
4010  allColsChildren[i].style.height = (butttonSz) + "px";
4011 
4012  //numbers
4013  {
4014  numberLoc[0] = (i*cellW + cellW/2 - axisPaddingExtra/2 + (i?bmpGridThickness+bmpBorderSize*2:0));
4015 
4016  //colTop numbers
4017  translatedRC = localConvertGridToRowCol(0,i);
4018  if(numberLoc[0] - oldNumberLoc[0] >= thresholdNumberSpacing &&
4019  translatedRC[1]%5 == 0)
4020  {
4021  //add a number
4022  numberEl = document.createElement("div");
4023  numberEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-number");
4024  numberEl.innerHTML = translatedRC[1];
4025  numberEl.style.left = numberLoc[0] + "px";
4026  numberEl.style.width = axisPaddingExtra + "px";
4027  colTopNums.appendChild(numberEl);
4028  oldNumberLoc[0] = numberLoc[0];
4029  }
4030 
4031  //colBottom numbers
4032  translatedRC = localConvertGridToRowCol(rows>1?1:0,i);
4033  if(numberLoc[0] - oldNumberLoc[1] >= thresholdNumberSpacing &&
4034  translatedRC[1]%5 == 0)
4035  {
4036  //add a number
4037  numberEl = document.createElement("div");
4038  numberEl.setAttribute("class", ConfigurationAPI._POP_UP_DIALOG_ID + "-bitmap-number");
4039  numberEl.innerHTML = translatedRC[1];
4040  numberEl.style.left = numberLoc[0] + "px";
4041  numberEl.style.width = axisPaddingExtra + "px";
4042  colBottomNums.appendChild(numberEl);
4043  oldNumberLoc[1] = numberLoc[0];
4044  }
4045  }
4046  }
4047  }
4048 
4049 
4050 
4051 
4052 
4053 
4054  } //end localPaint()
4055 
4056  //:::::::::::::::::::::::::::::::::::::::::
4057  //localOptimizeAspectRatio ~~
4058  // inputs are cellW/cellH, bmpW/bmpH, rows/cols
4059  //
4060  // optimize aspect ratio for viewing window
4061  // hdr and bmp positions are known after this
4062  function localOptimizeAspectRatio()
4063  {
4064  var cellSkew = (cellW>cellH)?cellW/cellH:cellH/cellW;
4065  var MAX_SKEW = 3;
4066 
4067 
4068  if(forcedAspectH !== undefined)
4069  {
4070  var offAspectH = forcedAspectH/cellH;
4071  var offAspectW = forcedAspectW/cellW;
4072 
4073  Debug.log("Adjusting skew factor = " + forcedAspectH + "-" + forcedAspectW);
4074 
4075  if(offAspectH < offAspectW) //height is too big
4076  bmpH = bmpW/cols*forcedAspectH/forcedAspectW*rows;
4077  else //width is too big
4078  bmpW = bmpH/rows*forcedAspectW/forcedAspectH*cols;
4079  }
4080  else if(cellSkew > MAX_SKEW) //re-adjust bitmap
4081  {
4082  var adj = cellSkew/MAX_SKEW;
4083  //to much skew in cell shape.. so let's adjust
4084  Debug.log("Adjusting skew factor = " + adj);
4085  if(cellW > cellH)
4086  {
4087  bmpW /= adj;
4088  }
4089  else
4090  bmpH /= adj;
4091  }
4092  //recalculate new cells
4093  cellW = bmpW/cols;
4094  cellH = bmpH/rows;
4095 
4096  //center bitmap
4097  bmpX = padding + (popSz.w-bmpW)/2;
4098  bmpY = bmpY + (popSz.h-bmpY-bmpH)/2;
4099  hdrY = bmpY - padding - hdrH;
4100  } //end localOptimizeAspectRatio()
4101 }
4102 
4103 
4104 //=====================================================================================
4105 //getDateString ~~
4106 // Example call from linux timestamp:
4107 // groupCreationTime = ConfigurationAPI.getDateString(new Date((groupCreationTime|0)*1000));
4108 ConfigurationAPI.getDateString;
4109 {
4110 ConfigurationAPI.getDateStringDayArr_ = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
4111 ConfigurationAPI.getDateStringMonthArr_ = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
4112 ConfigurationAPI.getDateString = function(date)
4113 {
4114  var dateStr = "";
4115 
4116  dateStr += ConfigurationAPI.getDateStringDayArr_[date.getDay()];
4117  dateStr += " ";
4118  dateStr += ConfigurationAPI.getDateStringMonthArr_[date.getMonth()];
4119  dateStr += " ";
4120  dateStr += date.getDate();
4121  dateStr += " ";
4122  dateStr += date.getHours();
4123  dateStr += ":";
4124  dateStr += ((date.getMinutes()<10)?"0":"") + date.getMinutes();
4125  dateStr += ":";
4126  dateStr += ((date.getSeconds()<10)?"0":"") + date.getSeconds();
4127  dateStr += " ";
4128  dateStr += date.getFullYear();
4129  dateStr += " ";
4130  dateStr += date.toLocaleTimeString([],{timeZoneName: "short"}).split(" ")[2];
4131  return dateStr;
4132 }
4133 }
4134 
4135 //=====================================================================================
4136 //setCaretPosition ~~
4137 ConfigurationAPI.setCaretPosition = function(elem, caretPos, endPos)
4138 {
4139  elem.focus();
4140  elem.setSelectionRange(caretPos, endPos);
4141 }
4142 
4143 
4144 //=====================================================================================
4145 //setPopUpPosition ~~
4146 // centers element based on width and height constraint
4147 //
4148 // Note: assumes a padding and border size if not specified
4149 // Note: if w,h not specified then fills screen (minus margin)
4150 // 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)
4151 ConfigurationAPI.setPopUpPosition = function(el,w,h,padding,border,margin,doNotResize,offsetUp)
4152 {
4153  Debug.log("ConfigurationAPI.setPopUpPosition");
4154 
4155  if(padding === undefined) padding = 10;
4156  if(border === undefined) border = 1;
4157  if(margin === undefined) margin = 0;
4158 
4159  var x,y;
4160 
4161  //:::::::::::::::::::::::::::::::::::::::::
4162  //popupResize ~~
4163  // set position and size
4164  ConfigurationAPI.setPopUpPosition.stopPropagation = function(event) {
4165  //Debug.log("stop propagation");
4166  event.stopPropagation();
4167  }
4168 
4169  //:::::::::::::::::::::::::::::::::::::::::
4170  //popupResize ~~
4171  // set position and size
4172  ConfigurationAPI.setPopUpPosition.popupResize = function() {
4173 
4174  try //check if element still exists
4175  {
4176  if(!el) //if element no longer exists.. then remove listener and exit
4177  {
4178  window.removeEventListener("resize",ConfigurationAPI.setPopUpPosition.popupResize);
4179  window.removeEventListener("scroll",ConfigurationAPI.setPopUpPosition.popupResize);
4180  return;
4181  }
4182  }
4183  catch(err) {return;} //do nothing on errors
4184 
4185  //else resize el
4186  //Debug.log("ConfigurationAPI.setPopUpPosition.popupResize");
4187 
4188 
4189  var ww = DesktopContent.getWindowWidth()-(padding+border)*2;
4190  var wh = DesktopContent.getWindowHeight()-(padding+border)*2;
4191 
4192  //ww & wh are max window size at this point
4193 
4194  var ah = el.offsetHeight;//actual height, in case of adjustments
4195 
4196  if(w === undefined || h === undefined)
4197  {
4198  w = ww-(margin)*2;
4199  h = wh-(margin)*2;
4200  }
4201  //else w,h are inputs and margin is ignored
4202 
4203  x = (DesktopContent.getWindowScrollLeft() + ((ww-w)/2));
4204  y = (DesktopContent.getWindowScrollTop() + ((wh-h)/2)) - (offsetUp|0) - 100; //bias up (looks nicer)
4205 
4206  if(y<DesktopContent.getWindowScrollTop()+margin+padding)
4207  y = DesktopContent.getWindowScrollTop()+margin+padding; //don't let it bottom out though
4208 
4209  //if dialog is smaller than window, allow scrolling to see the whole thing
4210  if(w > ww-margin-padding)
4211  x = -DesktopContent.getWindowScrollLeft();
4212  if(ah > wh-margin-padding)
4213  y = -DesktopContent.getWindowScrollTop();
4214 
4215  el.style.left = x + "px";
4216  el.style.top = y + "px";
4217  };
4218  ConfigurationAPI.setPopUpPosition.popupResize();
4219 
4220  //window width and height are not manipulated on resize, only setup once
4221  el.style.width = w + "px";
4222  el.style.height = h + "px";
4223 
4224  if(!doNotResize)
4225  {
4226  window.addEventListener("resize",ConfigurationAPI.setPopUpPosition.popupResize);
4227  window.addEventListener("scroll",ConfigurationAPI.setPopUpPosition.popupResize);
4228  }
4229  el.addEventListener("keydown",ConfigurationAPI.setPopUpPosition.stopPropagation);
4230  el.addEventListener("mousemove",ConfigurationAPI.setPopUpPosition.stopPropagation);
4231  el.addEventListener("mousemove",DesktopContent.mouseMove);
4232 
4233  el.style.overflow = "auto";
4234 
4235  return {"w" : w, "h" : h, "x" : x, "y" : y};
4236 }
4237 
4238 
4239 //=====================================================================================
4240 //getOnePixelPngData ~~
4241 // alpha is optional, will assume full 255 alpha
4242 ConfigurationAPI.getOnePixelPngData = function(rgba)
4243 {
4244  if(ConfigurationAPI.getOnePixelPngData.canvas === undefined)
4245  {
4246  //create only first time this functino is called
4247  ConfigurationAPI.getOnePixelPngData.canvas = document.createElement("canvas");
4248  ConfigurationAPI.getOnePixelPngData.canvas.width = 1;
4249  ConfigurationAPI.getOnePixelPngData.canvas.height = 1;
4250  ConfigurationAPI.getOnePixelPngData.ctx = ConfigurationAPI.getOnePixelPngData.canvas.getContext("2d");
4251  ConfigurationAPI.getOnePixelPngData.bmpOverlayData = ConfigurationAPI.getOnePixelPngData.ctx.createImageData(1,1);
4252  }
4253 
4254  ConfigurationAPI.getOnePixelPngData.bmpOverlayData.data[0]=rgba[0];
4255  ConfigurationAPI.getOnePixelPngData.bmpOverlayData.data[1]=rgba[1];
4256  ConfigurationAPI.getOnePixelPngData.bmpOverlayData.data[2]=rgba[2];
4257  ConfigurationAPI.getOnePixelPngData.bmpOverlayData.data[3]=rgba[3]!==undefined?rgba[3]:255;
4258 
4259  ConfigurationAPI.getOnePixelPngData.ctx.putImageData(
4260  ConfigurationAPI.getOnePixelPngData.bmpOverlayData,0,0);
4261  return ConfigurationAPI.getOnePixelPngData.canvas.toDataURL();
4262 }
4263 
4264 
4265 //=====================================================================================
4266 //createEditableFieldElement ~~
4267 //
4268 // Creates div element with editable and highlight features.
4269 // Note: set ConfigurationAPI.editableField_SELECTED_COLOR_ = "transparent" to disable highlight features
4270 //
4271 // Input field must be a value node.
4272 // Input object is single field as returned from ConfigurationAPI.getFieldsOfRecords
4273 //
4274 // fieldIndex is unique integer for the field
4275 // depthIndex (optional) is indicator of depth, e.g. for tree display
4276 //
4277 // Field := {}
4278 // obj.fieldTableName
4279 // obj.fieldUID
4280 // obj.fieldColumnName
4281 // obj.fieldRelativePath
4282 // obj.fieldColumnType
4283 // obj.fieldColumnDataType
4284 // obj.fieldColumnDataChoicesArr[]
4285 // obj.fieldColumnDefaultValue
4286 //
4287 ConfigurationAPI.editableFieldEditingCell_ = 0;
4288 ConfigurationAPI.editableFieldEditingIdString_;
4289 ConfigurationAPI.editableFieldEditingNodeType_;
4290 ConfigurationAPI.editableFieldEditingOldValue_;
4291 ConfigurationAPI.editableFieldEditingInitValue_;
4292 ConfigurationAPI.editableFieldHoveringCell_ = 0;
4293 ConfigurationAPI.editableFieldHoveringIdString_;
4294 ConfigurationAPI.editableFieldSelectedIdString_ = 0;
4295 ConfigurationAPI.editableFieldHandlersSubscribed_ = false;
4296 ConfigurationAPI.editableFieldMouseIsSelecting_ = false;
4297 ConfigurationAPI.editableField_SELECTED_COLOR_ = "rgb(251, 245, 53)";
4298 ConfigurationAPI.createEditableFieldElement = function(fieldObj,fieldIndex,
4299  depthIndex /*optional*/)
4300 {
4301  var str = "";
4302  var depth = depthIndex|0;
4303  var uid = fieldIndex|0;
4304 
4305  if(!ConfigurationAPI.editableFieldHandlersSubscribed_)
4306  {
4307  ConfigurationAPI.editableFieldHandlersSubscribed_ = true;
4308 
4309  //be careful to not override the window.onmousemove DesktopContent action
4310  DesktopContent.mouseMoveSubscriber(ConfigurationAPI.handleEditableFieldBodyMouseMove);
4311  }
4312 
4313  var fieldEl = document.createElement("div");
4314  fieldEl.setAttribute("class", "ConfigurationAPI-EditableField");
4315  fieldEl.setAttribute("id", "ConfigurationAPI-EditableField-" +
4316  ( depth + "-" + uid ));
4317 
4318  Debug.log("Field type " + fieldObj.fieldColumnType);
4319  //console.log(fieldObj);
4320 
4321  var valueType = fieldObj.fieldColumnType;
4322  var choices = fieldObj.fieldColumnDataChoicesArr;
4323  var value = fieldObj.fieldColumnDefaultValue;
4324  var path = fieldObj.fieldRelativePath;
4325  var nodeName = fieldObj.fieldColumnName;
4326 
4327  return ConfigurationAPI.fillEditableFieldElement(fieldEl,uid,
4328  depth,nodeName,value,valueType,choices,path);
4329 }
4330 
4331 //=====================================================================================
4332 //getEditableFieldValue ~~
4333 // return value is the string value
4334 // loosely based ConfigurationGUI editTreeNodeOK()
4335 ConfigurationAPI.getEditableFieldValue = function(fieldObj,fieldIndex,depthIndex /*optional*/)
4336 {
4337  //Debug.log("getEditableFieldValue " + fieldObj.fieldColumnName + " of type " +
4338  // fieldObj.fieldColumnType);
4339 
4340  ConfigurationAPI.handleEditableFieldEditOK(); //make sure OK|Cancel closed
4341 
4342  var depth = depthIndex|0;
4343  var uid = fieldIndex|0;
4344  var fieldEl = document.getElementById("editableFieldNode-Value-leafNode-" +
4345  ( depth + "-" + uid ));
4346  if(!fieldEl)
4347  {
4348  Debug.log("getEditableFieldValue Error! Invalid target field element '" +
4349  ( depth + "-" + uid ), Debug.HIGH_PRIORITY);
4350  return;
4351  }
4352 
4353  var valueType = fieldObj.fieldColumnType;
4354  var value = fieldEl.textContent;
4355 
4356  //Debug.log("get Value " + value);
4357  return value;
4358 }
4359 
4360 //=====================================================================================
4361 //setEditableFieldValue ~~
4362 // set value of a single field element as specified by:
4363 // fieldObj (as returned from ConfigurationAPI.getFieldsOfRecords)
4364 // fieldIndex is unique integer for the field
4365 // depthIndex (optional) is indicator of depth, e.g. for tree display
4366 //
4367 // input value is expected to be a string value
4368 ConfigurationAPI.setEditableFieldValue = function(fieldObj,value,fieldIndex,depthIndex /*optional*/)
4369 {
4370  //Debug.log("setEditableFieldValue " + fieldObj.fieldColumnName + " = " + value);
4371 
4372  var depth = depthIndex|0;
4373  var uid = fieldIndex|0;
4374  var fieldEl = document.getElementById("ConfigurationAPI-EditableField-" +
4375  ( depth + "-" + uid ));
4376  if(!fieldEl)
4377  {
4378  Debug.log("setEditableFieldValue Error! Invalid target field element '" +
4379  ( depth + "-" + uid ), Debug.HIGH_PRIORITY);
4380  return;
4381  }
4382  var valueType = fieldObj.fieldColumnType;
4383  var choices = fieldObj.fieldColumnDataChoicesArr;
4384  var path = fieldObj.fieldRelativePath;
4385  var nodeName = fieldObj.fieldColumnName;
4386 
4387  return ConfigurationAPI.fillEditableFieldElement(fieldEl,uid,
4388  depth,nodeName,value,valueType,choices,path);
4389 }
4390 
4391 //=====================================================================================
4392 //fillEditableFieldElement ~~
4393 // helper to fill element used by setEditableFieldValue and createEditableFieldElement
4394 ConfigurationAPI.fillEditableFieldElement = function(fieldEl,uid,
4395  depth,nodeName,value,valueType,choices,path)
4396 {
4397  var str = "";
4398 
4399  var pathHTML = path;
4400  //make path html safe
4401  pathHTML = pathHTML.replace(/</g, "&lt");
4402  pathHTML = pathHTML.replace(/>/g, "&gt");
4403 
4404  str += "<div class='editableFieldNode-Path' style='display:none' id='editableFieldNode-path-" +
4405  ( depth + "-" + uid ) + "'>" + // end path id
4406  pathHTML + //save path for future use.. and a central place to edit when changes occur
4407  "</div>";
4408 
4409  //track if this is a child link with fixed choice
4410  var childLinkFixedChoice = false; //init, but if it is, then change type handling to fixed choice style
4411  var isChildLink = valueType.indexOf("ChildLink") == 0;
4412 
4413  if(valueType == "FixedChoiceData" ||
4414  (isChildLink && choices.length > 1))
4415  {
4416  //track if this is a child link with fixed choice
4417  childLinkFixedChoice = valueType.indexOf("ChildLink") == 0;
4418 
4419  //add CSV choices div
4420  str +=
4421  "<div class='editableFieldNode-FixedChoice-CSV' style='display:none' " +
4422  "id='editableFieldNode-FixedChoice-CSV-" +
4423  ( depth + "-" + uid ) + "'>";
4424 
4425  for(var j=0;j<choices.length;++j)
4426  {
4427  if(j) str += ",";
4428  str += choices[j];
4429  }
4430  str += "</div>";
4431 
4432 
4433  }
4434  else if(valueType == "BitMap")
4435  {
4436  //add bitmap params div
4437  str +=
4438  "<div class='editableFieldNode-BitMap-Params' style='display:none' " +
4439  "id='editableFieldNode-BitMap-Params-" +
4440  ( depth + "-" + uid ) + "'>";
4441 
4442  for(var j=1;j<choices.length;++j) //skip the first DEFAULT param
4443  {
4444  if(j-1) str += ";"; //assume no ';' in fields, so likely no issue to replace ; with ,
4445  str += choices[j].replace(/;/g,","); //change all ; to , for split safety
4446  }
4447  str += "</div>";
4448  }
4449 
4450  //normal value node and edit icon
4451  {
4452  //start value node
4453  str +=
4454  "<div class='editableFieldNode-Value editableFieldNode-ValueType-" +
4455  //if it is a fixed choice child link, then change type handling to fixed choice style
4456  (childLinkFixedChoice?"ChildLinkFixedChoice":valueType) +
4457  "' " +
4458  "id='editableFieldNode-Value-" +
4459  (depth + "-" + uid) + "' " +
4460 
4461  "onclick='ConfigurationAPI.handleEditableFieldClick(" +
4462  depth + "," + uid + "," +
4463  "0,\"value\")' " +
4464 
4465  "onmousemove='ConfigurationAPI.handleEditableFieldHover(" +
4466  depth + "," + uid + "," +
4467  "event)' " +
4468 
4469  ">";
4470 
4471  titleStr = "~ Leaf Value Node ~\n";
4472  titleStr += "Path: \t" + path + nodeName + "\n";
4473 
4474  //left side of value
4475  str +=
4476  "<div style='float:left' title='" + titleStr + "'>" +
4477  "<b class='editableFieldNode-Value-leafNode-fieldName bold-header'>" +
4478  nodeName + "</b>" +
4479  "</div><div style='float:left'>&nbsp;:</div>";
4480 
4481  //normal edit icon
4482  str +=
4483  "<div class='editableFieldNode-Value-editIcon' id='editableFieldNode-Value-editIcon-" +
4484  (depth + "-" + uid) + "' " +
4485  "onclick='ConfigurationAPI.handleEditableFieldClick(" +
4486  depth + "," + uid + "," +
4487  "1,\"value\"); event.stopPropagation();' " +
4488  "title='Edit the value of this node.' " +
4489  "></div>";
4490  }
4491 
4492  str += "<div style='float:left; margin-left:9px;' id='editableFieldNode-Value-leafNode-" +
4493  (depth + "-" + uid) +
4494  "' class='" +
4495  "editableFieldNode-Value-leafNode-ColumnName-" + nodeName +
4496  "' " +
4497  ">";
4498 
4499 
4500  if(valueType == "OnOff" ||
4501  valueType == "YesNo" ||
4502  valueType == "TrueFalse")
4503  {
4504  //colorize true false
4505  str += "<div style='float:left'>";
4506  str += value;
4507  str += "</div>";
4508 
4509  var color = (value == "On" || value == "Yes" || value == "True")?
4510  "rgb(16, 204, 16)":"rgb(255, 0, 0);";
4511  str += "<div style='width:10px;height:10px;" +
4512  "background-color:" + color + ";" +
4513  "float: left;" +
4514  "border-radius: 7px;" +
4515  "border: 2px solid white;" +
4516  "margin: 2px 0 0 6px;" +
4517  "'></div>";
4518  }
4519  else if(valueType == "Timestamp")
4520  str += ConfigurationAPI.getDateString(new Date((value|0)*1000));
4521  else
4522  str += value;
4523 
4524  str += "</div>";
4525 
4526  //make links to child subset configuration editor
4527  if(isChildLink &&
4528  value.indexOf("Table") == value.length - ("Table").length)
4529  {
4530  //make record alias with spaces instead of all one word
4531  // and remove table
4532  var recordAlias = "";
4533  for(var c=0;c<value.length - ("Table").length;++c)
4534  {
4535  if(c && c+1 < value.length &&
4536  (value[c] >= 'A' &&
4537  value[c] <= 'Z') &&
4538  (value[c+1] >= 'a' &&
4539  value[c+1] <= 'z'))
4540  recordAlias += ' ';
4541  recordAlias += value[c];
4542  }
4543 
4544  var newWindowStr = "/WebPath/html/ConfigurationGUI_subset.html?urn=" +
4545  DesktopContent._localUrnLid +
4546  "&subsetBasePath=" + value +
4547  "&groupingFieldList=AUTO" +
4548  "&recordAlias=" + recordAlias +
4549  "&editableFieldList=" + "!*CommentDescription";
4550 
4551  str += "<div style='float:left; margin-left:9px;' " +
4552  " id='editableFieldNode-ChildLink-SubConfigLinkWindow-" +
4553  (depth + "-" + uid) + "' " +
4554  " class='" +
4555  "editableFieldNode-ChildLink-SubConfigLink" +
4556  "' " +
4557  "onclick='" +
4558  "DesktopContent.openNewWindow(" +
4559  "\"" + value +
4560  " Subset-Configuration\",\"\",\"" +
4561  //windowPath
4562  newWindowStr +
4563  "\",false /*unique*/);" +
4564  "'" +
4565  " title='Open " + value + " subset configuration in a new desktop window.' " +
4566  ">Open Window</div>";
4567 
4568  str += "<div style='float:left; margin-left:9px;' " +
4569  " id='editableFieldNode-ChildLink-SubConfigLinkTab-" +
4570  (depth + "-" + uid) + "' " +
4571  " class='" +
4572  "editableFieldNode-ChildLink-SubConfigLink" +
4573  "' " +
4574  "onclick='" +
4575  "DesktopContent.openNewBrowserTab(" +
4576  "\"" + value +
4577  " Subset-Configuration\",\"\",\"" +
4578  //windowPath
4579  newWindowStr +
4580  "\",false /*unique*/);" +
4581  "'" +
4582  " title='Open " + value + " subset configuration in a new browser tab.' " +
4583  ">Open Tab</div>";
4584  }
4585 
4586  //Debug.log(str);
4587 
4588  fieldEl.innerHTML = str;
4589 
4590  //check if this field is currently the selected field
4591  // if so, setup select color
4592  if(ConfigurationAPI.editableFieldSelectedIdString_ == (depth + "-" + uid))
4593  fieldEl.getElementsByClassName("editableFieldNode-Value")[0].style.backgroundColor =
4594  ConfigurationAPI.editableField_SELECTED_COLOR_;
4595 
4596  return fieldEl;
4597 }
4598 
4599 //=====================================================================================
4600 //handleEditableFieldClick ~~
4601 // handler for click event for editable field elements
4602 //
4603 // copied from ConfigurationGUI.html handleTreeNodeClick() ..but only
4604 // defines functionality for value nodes.
4605 ConfigurationAPI.handleEditableFieldClick = function(depth,uid,editClick,type)
4606 {
4607  var idString = depth + "-" + uid;
4608  ConfigurationAPI.editableFieldEditingIdString_ = idString;
4609 
4610  Debug.log("handleEditableFieldClick editClick " + editClick);
4611  Debug.log("handleEditableFieldClick idString " + idString);
4612 
4613  var el = document.getElementById("editableFieldNode-Value-" + idString);
4614 
4615  if(!el)
4616  {
4617  Debug.log("Invalid element pointed to by idString. Ignoring and exiting.");
4618  return;
4619  }
4620 
4621  if(ConfigurationAPI.editableFieldHoveringCell_)
4622  {
4623  //Debug.log("handleTreeNodeClick editClick clearing ");
4624  ConfigurationAPI.handleEditableFieldBodyMouseMove();
4625  }
4626 
4627  if(ConfigurationAPI.editableFieldEditingCell_) //already have the edit box open, cancel it
4628  {
4629  if(ConfigurationAPI.editableFieldEditingCell_ == el) //if same cell do nothing
4630  return true;
4631  ConfigurationAPI.handleEditableFieldEditOK(); //if new cell, click ok on old cell before continuing
4632  }
4633 
4634  var path = document.getElementById("editableFieldNode-path-" + idString).textContent;
4635 
4636  // Debug.log("handleEditableFieldClick el " + el.innerHTML);
4637  //Debug.log("handleEditableFieldClick idString " + idString);
4638  //Debug.log("handleEditableFieldClick uid " + uid);
4639  // Debug.log("handleEditableFieldClick nodeName " + nodeName);
4640  Debug.log("handleEditableFieldClick path " + path);
4641  //Debug.log("handleEditableFieldClick editClick " + editClick);
4642  Debug.log("handleEditableFieldClick type " + type);
4643 
4644  //determine type clicked:
4645  // - value
4646  //
4647  //allow different behavior for each depending on single or edit(2x) click
4648  // - value
4649  // 1x = select node
4650  // 2x = edit record Value mode (up/down tab/shtab enter esc active)
4651  //
4652  //on tree node edit OK
4653  // - value
4654  // save value back to field element
4655  //
4656  //on tree node edit cancel
4657  // return previous value back to field element
4658 
4659 
4660  //==================
4661  //take action based on editClick and type string:
4662  // - value
4663  //
4664  //params:
4665  // (el,depth,uid,path,editClick,type,delayed)
4666 
4667  if(editClick) //2x click
4668  {
4669 
4670  if(type == "value")
4671  {
4672  //edit ID (no keys active)
4673  Debug.log("edit value mode");
4674 
4675  selectThisTreeNode(idString,type);
4676  //==================
4677  function selectThisTreeNode(idString,type)
4678  {
4679  //edit column entry in record
4680  // data type matters here, also don't edit author, timestamp
4681  var el = document.getElementById("editableFieldNode-Value-leafNode-" + idString);
4682  var vel = document.getElementById("editableFieldNode-Value-" + idString);
4683 
4684  //if value node, dataType is in element class name
4685  var colType = vel.className.split(' ')[1].split('-');
4686  if(colType[1] == "ValueType")
4687  colType = colType[2];
4688 
4689  var fieldName = el.className.substr(("editableFieldNode-Value-leafNode-ColumnName-").length);
4690 
4691  Debug.log("fieldName=" + fieldName);
4692  Debug.log("colType=" + colType);
4693 
4694  if(colType == "Author" ||
4695  colType == "Timestamp")
4696  {
4697  Debug.log("Can not edit Author or Timestamp fields.",
4698  Debug.WARN_PRIORITY);
4699  return false;
4700  }
4701 
4702 
4703  var str = "";
4704  var optionIndex = -1;
4705 
4706 
4707  if(colType == "YesNo" ||
4708  colType == "TrueFalse" ||
4709  colType == "OnOff") //if column type is boolean, use dropdown
4710  {
4711  type += "-bool";
4712  ConfigurationAPI.editableFieldEditingOldValue_ = el.innerHTML;
4713 
4714  var initVal = el.childNodes[0].textContent;
4715  ConfigurationAPI.editableFieldEditingInitValue_ = initVal;
4716 
4717  var boolVals = [];
4718  if(colType == "YesNo")
4719  boolVals = ["No","Yes"];
4720  else if(colType == "TrueFalse")
4721  boolVals = ["False","True"];
4722  else if(colType == "OnOff")
4723  boolVals = ["Off","On"];
4724 
4725 
4726  str += "<select onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
4727  "onmousedown='ConfigurationAPI.editableFieldMouseIsSelecting_ = true; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);' " +
4728  "onmouseup='ConfigurationAPI.editableFieldMouseIsSelecting_ = false; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_); event.stopPropagation();' " +
4729  "onclick='event.stopPropagation();'" +
4730  "style='margin:-8px -2px -2px -1px; height:" + (el.offsetHeight+6) + "px'>";
4731  for(var i=0;i<boolVals.length;++i)
4732  {
4733  str += "<option value='" + boolVals[i] + "'>";
4734  str += boolVals[i]; //can display however
4735  str += "</option>";
4736  if(boolVals[i] == initVal)
4737  optionIndex = i; //get starting sel index
4738  }
4739  str += "</select>";
4740  if(optionIndex == -1) optionIndex = 0; //use False option by default
4741  }
4742  else if(colType == "FixedChoiceData" ||
4743  colType == "ChildLinkFixedChoice")
4744  {
4745  ConfigurationAPI.editableFieldEditingOldValue_ = el.textContent;
4746  ConfigurationAPI.editableFieldEditingInitValue_ = ConfigurationAPI.editableFieldEditingOldValue_;
4747 
4748  var allowFixedChoiceArbitraryEdit = false;
4749  var optionCount = -1;
4750  optionIndex = 0; //default to default
4751 
4752  str += "<div onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
4753  "onmouseup='event.stopPropagation();' " +
4754  "onclick='event.stopPropagation();' " +
4755  "style='" +
4756  "white-space:nowrap;" +
4757  "margin:-3px -2px -2px -1px;" +
4758  "height:" + (el.offsetHeight+6) + "px'>";
4759 
4760  str += "<select onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' " +
4761  "id='fixedChoice-editSelectBox' " +
4762  "onmouseup='event.stopPropagation();' " +
4763  "onclick='event.stopPropagation();' " +
4764  "style='" +
4765  "float:left;" +
4766  "margin:-2px -2px -2px -1px; height:" +
4767  (el.offsetHeight+6) + "px'>";
4768 
4769  //default value is assumed in list
4770 
4771  var vel = document.getElementById("editableFieldNode-FixedChoice-CSV-" +
4772  idString);
4773  var choices = vel.textContent.split(',');
4774 
4775  var isChildLinkFixedChoice = colType == "ChildLinkFixedChoice";
4776 
4777  if(isChildLinkFixedChoice)
4778  {
4779  try
4780  {
4781  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkTab-" +
4782  (depth + "-" + uid) ).style.display = "none";
4783  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkWindow-" +
4784  (depth + "-" + uid) ).style.display = "none";
4785  }
4786  catch(e) {} //ignore errors
4787  }
4788 
4789  if(choices.length > 1 &&
4790  choices[1].indexOf("arbitraryBool=") == 0)
4791  {
4792  //found arbitraryBool flag
4793  allowFixedChoiceArbitraryEdit =
4794  choices[1][("arbitraryBool=").length] == "1"?
4795  true:false;
4796  Debug.log("allowFixedChoiceArbitraryEdit " + allowFixedChoiceArbitraryEdit);
4797  }
4798 
4799  for(var i=0;i<choices.length;++i)
4800  {
4801  if(i == 0 && isChildLinkFixedChoice && !allowFixedChoiceArbitraryEdit)
4802  {
4803  //skip default if child link fixed choice does not allow arbitrary edit
4804  continue;
4805  }
4806  else if(i==1)//check for arbitraryBool flag
4807  {
4808  if(choices[i].indexOf("arbitraryBool=") == 0)
4809  {
4810  //found arbitraryBool flag, skip it
4811  continue;
4812  }
4813  else
4814  {
4815  //else no flag found, so treat as fixed choice option
4816  ++optionCount; //count as an option in dropdown
4817  }
4818  }
4819  else
4820  ++optionCount; //count as an option in dropdown
4821 
4822 
4823  str += "<option>";
4824  str += decodeURIComponent(choices[i]); //can display however
4825  str += "</option>";
4826  if(decodeURIComponent(choices[i])
4827  == ConfigurationAPI.editableFieldEditingOldValue_)
4828  optionIndex = optionCount; //save selected index
4829  }
4830  str += "</select>";
4831 
4832  if(allowFixedChoiceArbitraryEdit)
4833  {
4834  var ww = (el.offsetWidth-6);
4835  if(ww < 150) ww = 150;
4836  str += "<input type='text' " +
4837  "id='fixedChoice-editTextBox' " +
4838  "style='display:none;" +
4839  "float:left;" +
4840  "margin:-2px 0 -" + (el.offsetHeight+6) + "px 0;" +
4841  "width:" +
4842  ww + "px; height:" + (el.offsetHeight+6) + "px" +
4843  "' " + //end style
4844  "></input>";
4845 
4846  str += "<div style='display:block;" +
4847  "margin: -2px 0 -7px 14px;" +
4848  "' " +
4849  "class='editableFieldNode-Value-editIcon' id='fixedChoice-editIcon" +
4850  "' " +
4851  "onclick='ConfigurationAPI.handleEditableFieldFixedChoiceEditToggle();' " +
4852  "title='Toggle free-form editing' " +
4853  "></div>";
4854  }
4855  str += "</div>";
4856  }
4857  else if(colType == "BitMap")
4858  {
4859  Debug.log("Handling bitmap select");
4860 
4861  ConfigurationAPI.editableFieldEditingOldValue_ = el.textContent;
4862 
4863  //let API bitmap dialog handle it
4864  ConfigurationAPI.bitMapDialog(
4865  //_editingCellElOldTitle, //field name
4866  "Target Field: &quot;" +
4867  fieldName_ + "&quot;",
4868  document.getElementById("editableFieldNode-BitMap-Params-" +
4869  idString).textContent.split(';'),
4870  ConfigurationAPI.editableFieldEditingOldValue_,
4871  function(val)
4872  {
4873  Debug.log("yes " + val);
4874  el.innerHTML = "";
4875  el.appendChild(document.createTextNode(val));
4876  ConfigurationAPI.editableFieldEditingCell_ = el;
4877 
4878  type += "-bitmap";
4879  editTreeNodeOK();
4880 
4881  },
4882  function() //cancel handler
4883  {
4884  //remove the editing cell selection
4885  Debug.log("cancel bitmap");
4886  ConfigurationAPI.editableFieldEditingCell_ = 0;
4887  });
4888  return true;
4889  }
4890  else if(colType == "MultilineData")
4891  {
4892  ConfigurationAPI.editableFieldEditingOldValue_ = el.textContent;
4893  ConfigurationAPI.editableFieldEditingInitValue_ = ConfigurationAPI.editableFieldEditingOldValue_;
4894 
4895  str += "<textarea rows='4' onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' cols='50' style='font-size: 14px; " +
4896  "margin:-8px -2px -2px -1px;width:" +
4897  (el.offsetWidth-6) + "px; height:" + (el.offsetHeight-8) + "px' ";
4898  str += " onmousedown='ConfigurationAPI.editableFieldMouseIsSelecting_ = true; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);' " +
4899  "onmouseup='ConfigurationAPI.editableFieldMouseIsSelecting_ = false; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);event.stopPropagation();' " +
4900  "onclick='event.stopPropagation();'" +
4901  ">";
4902  str += ConfigurationAPI.editableFieldEditingOldValue_;
4903  str += "</textarea>";
4904  }
4905  else // normal cells, with text input
4906  {
4907  if(colType == "GroupID") //track type if it is groupid field
4908  type += "-groupid";
4909 
4910  ConfigurationAPI.editableFieldEditingOldValue_ = el.textContent;
4911  ConfigurationAPI.editableFieldEditingInitValue_ = ConfigurationAPI.editableFieldEditingOldValue_;
4912 
4913  var ow = el.offsetWidth+6;
4914  if(ow < 150) //force a minimum input width
4915  ow = 150;
4916  str += "<input type='text' onkeydown='ConfigurationAPI.handleEditableFieldKeyDown(event)' style='margin:-8px -2px -2px -1px;width:" +
4917  (ow) + "px; height:" + (el.offsetHeight>20?el.offsetHeight:20) + "px' value='";
4918  str += ConfigurationAPI.editableFieldEditingOldValue_;
4919  str += "' onmousedown='ConfigurationAPI.editableFieldMouseIsSelecting_ = true; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);' " +
4920  "onmouseup='ConfigurationAPI.editableFieldMouseIsSelecting_ = false; Debug.log(ConfigurationAPI.editableFieldMouseIsSelecting_);event.stopPropagation();' " +
4921  "onclick='event.stopPropagation();'" +
4922  ">";
4923  }
4924 
4925 
4926  str += ConfigurationAPI._OK_CANCEL_DIALOG_STR;
4927 
4928  el.innerHTML = str;
4929 
4930  //handle default selection
4931  if(colType == "YesNo" ||
4932  colType == "TrueFalse" ||
4933  colType == "OnOff") //if column type is boolean, use dropdown
4934  { //select initial value
4935  el.getElementsByTagName("select")[0].selectedIndex = optionIndex;
4936  el.getElementsByTagName("select")[0].focus();
4937  }
4938  else if(colType == "FixedChoiceData" ||
4939  colType == "ChildLinkFixedChoice")
4940  {
4941  el.getElementsByTagName("select")[0].selectedIndex = optionIndex;
4942  el.getElementsByTagName("select")[0].focus();
4943 
4944  }
4945  else if(colType == "MultilineData")
4946  ConfigurationAPI.setCaretPosition(el.getElementsByTagName("textarea")[0],0,ConfigurationAPI.editableFieldEditingOldValue_.length);
4947  else //select text in new input
4948  ConfigurationAPI.setCaretPosition(el.getElementsByTagName("input")[0],0,ConfigurationAPI.editableFieldEditingOldValue_.length);
4949 
4950 
4951  //wrapping up
4952  ConfigurationAPI.editableFieldEditingCell_ = el;
4953  ConfigurationAPI.editableFieldEditingNodeType_ = type;
4954  }
4955  }
4956  else
4957  {
4958  Debug.log("This should be impossible - tell a developer how you got here!", Debug.HIGH_PRIORITY);
4959  return;
4960  }
4961  }
4962  else //1x click
4963  {
4964  if(type == "value")
4965  {
4966  //Mark selected
4967  Debug.log("Toggling selection of target field " + idString);
4968 
4969  //remove previously selected
4970  var vel;
4971  if(ConfigurationAPI.editableFieldSelectedIdString_ &&
4972  (vel = document.getElementById("editableFieldNode-Value-" +
4973  ConfigurationAPI.editableFieldSelectedIdString_)))
4974  vel.style.backgroundColor = "transparent";
4975 
4976  //add newly selected
4977  vel = document.getElementById("editableFieldNode-Value-" +
4978  idString);
4979  if(ConfigurationAPI.editableFieldSelectedIdString_ == idString)
4980  {
4981  //same field was clicked
4982  // so toggle (deselect) the previously selected field
4983  ConfigurationAPI.editableFieldSelectedIdString_ = undefined;
4984  return;
4985  }
4986  vel.style.backgroundColor = ConfigurationAPI.editableField_SELECTED_COLOR_;
4987  ConfigurationAPI.editableFieldSelectedIdString_ = idString;
4988  }
4989  else
4990  {
4991  Debug.log("This should be impossible - tell a developer how you got here!", Debug.HIGH_PRIORITY);
4992  return;
4993  }
4994  }
4995 }
4996 
4997 //=====================================================================================
4998 //getSelectedEditableFieldIndex ~~
4999 // returns the unique field index value for the selected field
5000 // if no selected field
5001 // returns -1
5002 ConfigurationAPI.getSelectedEditableFieldIndex = function()
5003 {
5004  if(!ConfigurationAPI.editableFieldSelectedIdString_)
5005  return -1;
5006 
5007  var idStr = ConfigurationAPI.editableFieldSelectedIdString_;
5008  return idStr.split('-')[1]; // depth + "-" + fieldId
5009 }
5010 
5011 //=====================================================================================
5012 //handleEditableFieldHover ~~
5013 // handler for mousemove event for editable field elements
5014 ConfigurationAPI.handleEditableFieldHover = function(depth,uid,event)
5015 {
5016  var idString = depth + "-" + uid;
5017 
5018  //Debug.log("handleEditableFieldHover idString " + idString);
5019 
5020  event.stopPropagation();
5021  DesktopContent.mouseMove(event); //keep desktop content happy
5022 
5023 
5024  if(ConfigurationAPI.editableFieldEditingCell_) return; //no setting while editing
5025 
5026  var el = document.getElementById("editableFieldNode-Value-editIcon-" + idString);
5027  if(ConfigurationAPI.editableFieldHoveringCell_ == el) return;
5028 
5029  if(ConfigurationAPI.editableFieldHoveringCell_)
5030  {
5031  //Debug.log("bodyMouseMoveHandler clearing ");
5032  bodyMouseMoveHandler();
5033  }
5034 
5035  //Debug.log("handleTreeNodeMouseMove setting ");
5036  ConfigurationAPI.editableFieldHoveringIdString_ = idString;
5037  ConfigurationAPI.editableFieldHoveringCell_ = el;
5038  ConfigurationAPI.editableFieldHoveringCell_.style.display = "block";
5039  var vel = document.getElementById("editableFieldNode-Value-" +
5040  ConfigurationAPI.editableFieldHoveringIdString_);
5041  vel.style.backgroundColor = "rgb(218, 194, 194)";
5042 }
5043 
5044 //=====================================================================================
5045 //handleEditableFieldFixedChoiceEditToggle ~~
5046 ConfigurationAPI.handleEditableFieldFixedChoiceEditToggle = function()
5047 {
5048  Debug.log("handleEditableFieldFixedChoiceEditToggle");
5049 
5050  var sel = document.getElementById("fixedChoice-editSelectBox");
5051  var tel = document.getElementById("fixedChoice-editTextBox");
5052 
5053  Debug.log("sel.style.display " + sel.style.display);
5054  if(sel.style.display == "none")
5055  {
5056  sel.style.display = "block";
5057  tel.style.display = "none";
5058  }
5059  else
5060  {
5061  tel.style.width = ((sel.offsetWidth>150?sel.offsetWidth:150)-2) + "px";
5062  tel.parentNode.style.width = ((sel.offsetWidth>150?sel.offsetWidth:150)+50) + "px"; //div may need to expand to accommodate pencil in display
5063  sel.style.display = "none";
5064 
5065  if(tel.value == "") //fill with oldvalue for user convenience, if blank
5066  tel.value = ConfigurationAPI.editableFieldEditingOldValue_;
5067 
5068  tel.style.display = "block";
5069  ConfigurationAPI.setCaretPosition(tel,0,tel.value.length);
5070  }
5071 }
5072 
5073 //=====================================================================================
5074 //handleEditableFieldBodyMouseMove ~~
5075 ConfigurationAPI.handleEditableFieldBodyMouseMove = function(e)
5076 {
5077  if(ConfigurationAPI.editableFieldHoveringCell_)
5078  {
5079  //Debug.log("bodyMouseMoveHandler clearing ");
5080  ConfigurationAPI.editableFieldHoveringCell_.style.display = "none";
5081  ConfigurationAPI.editableFieldHoveringCell_ = 0;
5082 
5083  var vel = document.getElementById("editableFieldNode-Value-" +
5084  ConfigurationAPI.editableFieldHoveringIdString_);
5085  if(vel)
5086  {
5087  if(ConfigurationAPI.editableFieldHoveringIdString_ ==
5088  ConfigurationAPI.editableFieldSelectedIdString_)
5089  vel.style.backgroundColor = ConfigurationAPI.editableField_SELECTED_COLOR_;
5090  else
5091  vel.style.backgroundColor = "transparent";
5092  }
5093  }
5094 }
5095 
5096 //=====================================================================================
5097 //handleEditableFieldKeyDown ~~
5098 // copied from ConfigurationGUI keyHandler but modified for only value cells
5099 ConfigurationAPI.handleEditableFieldKeyDown = function(e,keyEl)
5100 {
5101  var TABKEY = 9;
5102  var ENTERKEY = 13;
5103  var UPKEY = 38;
5104  var DNKEY = 40;
5105  var ESCKEY = 27;
5106  //Debug.log("key " + e.keyCode);
5107 
5108  var shiftIsDown;
5109  if (window.event)
5110  {
5111  key = window.event.keyCode;
5112  shiftIsDown = !!window.event.shiftKey; // typecast to boolean
5113  }
5114  else
5115  {
5116  key = e.which;
5117  shiftIsDown = !!e.shiftKey; // typecast to boolean
5118  }
5119  //Debug.log("shift=" + shiftIsDown);
5120 
5121 
5122  //handle text area specially
5123  if(!shiftIsDown)
5124  {
5125  var tel;
5126  if(ConfigurationAPI.editableFieldEditingCell_ &&
5127  (tel = ConfigurationAPI.editableFieldEditingCell_.getElementsByTagName("textarea")).length)
5128  {
5129  tel = tel[0];
5130  //handle special keys for text area
5131  if(e.keyCode == TABKEY)
5132  {
5133  Debug.log("tab.");
5134  if(e.preventDefault)
5135  e.preventDefault();
5136 
5137  var i = tel.selectionStart;
5138  var j = tel.selectionEnd;
5139  tel.value = tel.value.substr(0,i) +
5140  '\t' + tel.value.substr(j);
5141  tel.selectionStart = tel.selectionEnd = j+1;
5142  }
5143  return false; //done if text area was identified
5144  }
5145  }
5146 
5147  //tab key jumps to next cell with a CANCEL
5148  // (enter key does same except saves/OKs value)
5149  // shift is reverse jump
5150  if(e.keyCode == TABKEY || e.keyCode == ENTERKEY ||
5151  e.keyCode == UPKEY || e.keyCode == DNKEY)
5152  {
5153  //this.value += " ";
5154  if(e.preventDefault)
5155  e.preventDefault();
5156 
5157  //save idString
5158  var idString = ConfigurationAPI.editableFieldEditingIdString_;
5159 
5160  ConfigurationAPI.handleEditableFieldEditOK();
5161 
5162 
5163  //enter key := done
5164  //tab/shift+tab := move to next field at depth
5165  //down/up := move to next field at depth
5166 
5167 
5168  if(e.keyCode == ENTERKEY) //dont move to new cell
5169  return false;
5170 
5171  var depth = idString.split('-')[0];
5172  var uid = idString.split('-')[1];
5173 
5174  if((!shiftIsDown && e.keyCode == TABKEY) || e.keyCode == DNKEY) //move to next field
5175  ++uid;
5176  else if((shiftIsDown && e.keyCode == TABKEY) || e.keyCode == UPKEY) //move to prev field
5177  --uid;
5178  if(uid < 0) return false; //no more fields, do nothing
5179 
5180  //assume handleEditableFieldClick handles invalid uids on high side gracefully
5181  ConfigurationAPI.handleEditableFieldClick(depth,uid,1,"value");
5182  Debug.log("new uid=" + uid);
5183 
5184  return false;
5185  }
5186  else if(e.keyCode == ESCKEY)
5187  {
5188  if(e.preventDefault)
5189  e.preventDefault();
5190  ConfigurationAPI.handleEditableFieldEditCancel();
5191  return false;
5192  }
5193  else if((e.keyCode >= 48 && e.keyCode <= 57) ||
5194  (e.keyCode >= 96 && e.keyCode <= 105))// number 0-9
5195  {
5196  //if child link cell or boolean cell
5197  var sel;
5198  if((sel = ConfigurationAPI.editableFieldEditingCell_.getElementsByTagName("select")).length)
5199  {
5200  if(keyEl) //if input element, use it
5201  sel = keyEl;
5202  else
5203  sel = sel[sel.length-1]; //assume the last select in the cell is the select
5204 
5205  //select based on number
5206  var selNum;
5207  if(e.keyCode >= 96)
5208  selNum = e.keyCode - 96;
5209  else
5210  selNum = e.keyCode - 48;
5211 
5212  sel.selectedIndex = selNum % (sel.options.length);
5213  sel.focus();
5214 
5215  Debug.log("number select =" + sel.selectedIndex);
5216  if(sel.onchange) //if onchange implemented call it
5217  sel.onchange();
5218  }
5219  }
5220 }
5221 
5222 //=====================================================================================
5223 //handleEditableFieldEditCancel ~~
5224 // copied from ConfigurationGUI editCellCancel but modified for only value cells
5225 ConfigurationAPI.handleEditableFieldEditCancel = function()
5226 {
5227  if(!ConfigurationAPI.editableFieldEditingCell_) return;
5228  Debug.log("handleEditableFieldEditCancel type " + ConfigurationAPI.editableFieldEditingNodeType_);
5229 
5230  try //try to return link visibility
5231  {
5232  var idSplit = ConfigurationAPI.editableFieldEditingCell_.id.split('-');
5233  var depth = idSplit[idSplit.length-2];
5234  var uid = idSplit[idSplit.length-1];
5235  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkTab-" +
5236  (depth + "-" + uid) ).style.display = "block";
5237  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkWindow-" +
5238  (depth + "-" + uid) ).style.display = "block";
5239  }
5240  catch(e) {} //ignore error
5241 
5242 
5243  if(ConfigurationAPI.editableFieldEditingNodeType_ == "value-bool")
5244  {
5245  //take old value as HTML for bool values
5246  ConfigurationAPI.editableFieldEditingCell_.innerHTML = ConfigurationAPI.editableFieldEditingOldValue_;
5247  }
5248  else
5249  {
5250  ConfigurationAPI.editableFieldEditingCell_.innerHTML = "";
5251  ConfigurationAPI.editableFieldEditingCell_.appendChild(
5252  document.createTextNode(ConfigurationAPI.editableFieldEditingOldValue_));
5253  }
5254 
5255  ConfigurationAPI.editableFieldEditingCell_ = 0;
5256 }
5257 
5258 //=====================================================================================
5259 //handleEditableFieldEditOK ~~
5260 // copied from ConfigurationGUI editCellOK but modified for only value cells
5261 ConfigurationAPI.handleEditableFieldEditOK = function()
5262 {
5263  if(!ConfigurationAPI.editableFieldEditingCell_) return;
5264  Debug.log("handleEditableFieldEditOK type " + ConfigurationAPI.editableFieldEditingNodeType_);
5265 
5266  try //try to return link visibility
5267  {
5268  var idSplit = ConfigurationAPI.editableFieldEditingCell_.id.split('-');
5269  var depth = idSplit[idSplit.length-2];
5270  var uid = idSplit[idSplit.length-1];
5271  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkTab-" +
5272  (depth + "-" + uid) ).style.display = "block";
5273  document.getElementById("editableFieldNode-ChildLink-SubConfigLinkWindow-" +
5274  (depth + "-" + uid) ).style.display = "block";
5275  }
5276  catch(e) {} //ignore error
5277 
5278  var el = ConfigurationAPI.editableFieldEditingCell_;
5279  var type = ConfigurationAPI.editableFieldEditingNodeType_;
5280 
5282  //localEditTreeNodeOKRequestsComplete
5283  function localEditTreeNodeOKRequestsComplete(newValue)
5284  {
5285  // all value types, clear the cell first
5286  el.innerHTML = "";
5287 
5288 
5289  if(type == "value" ||
5290  type == "value-bitmap")
5291  {
5292  //if(type == "MultilineData")
5293  //MultilineData and normal
5294  //normal data
5295  //bitmap data (do nothing would be ok, value already set)
5296  el.appendChild(document.createTextNode(decodeURIComponent(newValue)));
5297 
5298  }
5299  else if(type == "value-bool")
5300  {
5301  var str = "";
5302 
5303  //colorize true false
5304  str += "<div style='float:left'>";
5305  str += newValue;
5306  str += "</div>";
5307 
5308  var color = (newValue == "On" || newValue == "Yes" || newValue == "True")?
5309  "rgb(16, 204, 16)":"rgb(255, 0, 0);";
5310  str += "<div style='width:10px;height:10px;" +
5311  "background-color:" + color + ";" +
5312  "float: left;" +
5313  "border-radius: 7px;" +
5314  "border: 2px solid white;" +
5315  "margin: 2px 0 0 6px;" +
5316  "'></div>";
5317  el.innerHTML = str;
5318  }
5319  else if(type == "value-groupid")
5320  {
5321  el.appendChild(document.createTextNode(newValue));
5322  }
5323  else //unrecognized type!?
5324  {
5325  Debug.log("Unrecognizd tree edit type! Should be impossible!",Debug.HIGH_PRIORITY);
5326  ConfigurationAPI.handleEditableFieldEditCancel(); return;
5327  }
5328 
5329  //requests are done, so end editing
5330  ConfigurationAPI.editableFieldEditingCell_ = 0;
5331  } //end localEditTreeNodeOKRequestsComplete
5333 
5334 
5335  if(
5336  type == "value" ||
5337  type == "value-bool" ||
5338  type == "value-bitmap" ||
5339  type == "value-groupid")
5340 
5341  {
5342  var newValue;
5343 
5344  if(type == "value-bool")
5345  {
5346  var sel = el.getElementsByTagName("select")[0];
5347  newValue = sel.options[sel.selectedIndex].value;
5348  }
5349  else if(type == "value-bitmap")
5350  {
5351  newValue = encodeURIComponent(el.textContent);
5352  }
5353  else //value (normal or multiline data)
5354  {
5355  var sel;
5356  if((sel = el.getElementsByTagName("textarea")).length) //for MultilineData
5357  newValue = sel[0].value; //assume the first textarea in the cell is the textarea
5358  else if((sel = el.getElementsByTagName("select")).length) //for FixedChoiceData
5359  {
5360  //check if displayed
5361  if(sel[0].style.display == "none")
5362  {
5363  //if not displayed assume first input field is ours!
5364  //take "normal cell" approach from below
5365  newValue = el.getElementsByTagName("input")[0].value;
5366  }
5367  else
5368  newValue = sel[0].options[sel[0].selectedIndex].value; //assume the first select dropbox in the cell is the one
5369  }
5370  else
5371  newValue = el.getElementsByTagName("input")[0].value;
5372 
5373  newValue = encodeURIComponent(newValue.trim());
5374  }
5375 
5376  Debug.log("CfgGUI editTreeNodeOK editing " + type + " node = " +
5377  newValue);
5378 
5379  if(ConfigurationAPI.editableFieldEditingInitValue_ == newValue)
5380  {
5381  Debug.log("No change. Do nothing.");
5382  ConfigurationAPI.handleEditableFieldEditCancel();
5383  return;
5384  }
5385 
5386 
5387  // if saved successfully
5388  // update value in field
5389 
5390  localEditTreeNodeOKRequestsComplete(newValue);
5391 
5392  }
5393  else //unrecognized type!?
5394  {
5395  Debug.log("Unrecognizd tree edit type! Should be impossible!",Debug.HIGH_PRIORITY);
5396  editCellCancel(); return;
5397  }
5398 }
5399 
5400 
5401 //=====================================================================================
5402 //hasClass ~~
5403 ConfigurationAPI.hasClass = function(ele,cls)
5404 {
5405  return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
5406 }
5407 
5408 //=====================================================================================
5409 //addClass ~~
5410 ConfigurationAPI.addClass = function(ele,cls)
5411 {
5412  if (!ConfigurationAPI.hasClass(ele,cls)) ele.className += " "+cls;
5413 }
5414 
5415 //=====================================================================================
5416 //removeClass ~~
5417 ConfigurationAPI.removeClass = function(ele,cls)
5418 {
5419  if (ConfigurationAPI.hasClass(ele,cls))
5420  {
5421  var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
5422  ele.className=ele.className.replace(reg,'');
5423  }
5424 }
5425 
5426 
5427 //=====================================================================================
5428 //addSubsetRecords ~~
5429 // takes as input a base path where the desired records should be created.
5430 //
5431 // <modifiedTables> is an array of Table objects (as returned from
5432 // ConfigurationAPI.setFieldValuesForRecords)
5433 //
5434 // when complete, the responseHandler is called with an array parameter.
5435 // on failure, the array will be empty.
5436 // on success, the array will be an array of Table objects
5437 // Table := {}
5438 // obj.tableName
5439 // obj.tableVersion
5440 // obj.tableComment
5441 //
5442 ConfigurationAPI.addSubsetRecords = function(subsetBasePath,
5443  recordArr,responseHandler,modifiedTablesIn,silenceErrors)
5444 {
5445  var modifiedTablesListStr = "";
5446  for(var i=0;modifiedTablesIn && i<modifiedTablesIn.length;++i)
5447  {
5448  if(i) modifiedTablesListStr += ",";
5449  modifiedTablesListStr += modifiedTablesIn[i].tableName + "," +
5450  modifiedTablesIn[i].tableVersion;
5451  }
5452 
5453  var recordListStr = "";
5454  if(Array.isArray(recordArr))
5455  for(var i=0;i<recordArr.length;++i)
5456  {
5457  if(i) recordListStr += ",";
5458  recordListStr += encodeURIComponent(recordArr[i]);
5459  }
5460  else //handle single record case
5461  recordListStr = encodeURIComponent(recordArr);
5462 
5463  DesktopContent.XMLHttpRequest("Request?RequestType=addTreeNodeRecords" +
5464  "&tableGroup=" +
5465  "&tableGroupKey=-1", //end get data
5466  "startPath=/" + subsetBasePath +
5467  "&recordList=" + recordListStr +
5468  "&modifiedTables=" + modifiedTablesListStr, //end post data
5469  function(req)
5470  {
5471  var modifiedTables = [];
5472 
5473  var err = DesktopContent.getXMLValue(req,"Error");
5474  if(err)
5475  {
5476  if(!silenceErrors)
5477  Debug.log(err,Debug.HIGH_PRIORITY);
5478  responseHandler(modifiedTables,err);
5479  return;
5480  }
5481 
5482  //console.log(req);
5483 
5484  //modifiedTables
5485  var tableNames = req.responseXML.getElementsByTagName("NewActiveTableName");
5486  var tableVersions = req.responseXML.getElementsByTagName("NewActiveTableVersion");
5487  var tableComments = req.responseXML.getElementsByTagName("NewActiveTableComment");
5488  var tableVersion;
5489 
5490  //add only temporary version
5491  for(var i=0;i<tableNames.length;++i)
5492  {
5493  tableVersion = DesktopContent.getXMLValue(tableVersions[i])|0; //force integer
5494  if(tableVersion >= -1) continue; //skip unless temporary
5495  var obj = {};
5496  obj.tableName = DesktopContent.getXMLValue(tableNames[i]);
5497  obj.tableVersion = DesktopContent.getXMLValue(tableVersions[i]);
5498  obj.tableComment = DesktopContent.getXMLValue(tableComments[i]);
5499  modifiedTables.push(obj);
5500  }
5501  responseHandler(modifiedTables);
5502 
5503  }, //handler
5504  0, //handler param
5505  0,true); //progressHandler, callHandlerOnErr
5506 
5507 } // end ConfigurationAPI.addSubsetRecords()
5508 
5509 
5510 //=====================================================================================
5511 //deleteSubsetRecords ~~
5512 // takes as input a base path where the desired records should be deleted.
5513 //
5514 // <modifiedTables> is an array of Table objects (as returned from
5515 // ConfigurationAPI.setFieldValuesForRecords)
5516 //
5517 // when complete, the responseHandler is called with an array parameter.
5518 // on failure, the array will be empty.
5519 // on success, the array will be an array of Table objects
5520 // Table := {}
5521 // obj.tableName
5522 // obj.tableVersion
5523 // obj.tableComment
5524 //
5525 ConfigurationAPI.deleteSubsetRecords = function(subsetBasePath,
5526  recordArr,responseHandler,modifiedTablesIn,silenceErrors)
5527 {
5528  var modifiedTablesListStr = "";
5529  for(var i=0;modifiedTablesIn && i<modifiedTablesIn.length;++i)
5530  {
5531  if(i) modifiedTablesListStr += ",";
5532  modifiedTablesListStr += modifiedTablesIn[i].tableName + "," +
5533  modifiedTablesIn[i].tableVersion;
5534  }
5535 
5536  var recordListStr = "";
5537  var recordCount = 1;
5538  if(Array.isArray(recordArr))
5539  {
5540  for(var i=0;i<recordArr.length;++i)
5541  {
5542  if(i) recordListStr += ",";
5543  recordListStr += encodeURIComponent(recordArr[i]);
5544  }
5545  recordCount = recordArr.length;
5546  }
5547  else //handle single record case
5548  recordListStr = encodeURIComponent(recordArr);
5549 
5550  DesktopContent.XMLHttpRequest("Request?RequestType=deleteTreeNodeRecords" +
5551  "&tableGroup=" +
5552  "&tableGroupKey=-1", //end get data
5553  "startPath=/" + subsetBasePath +
5554  "&recordList=" + recordListStr +
5555  "&modifiedTables=" + modifiedTablesListStr, //end post data
5556  function(req)
5557  {
5558 
5559  var err = DesktopContent.getXMLValue(req,"Error");
5560  var modifiedTables = [];
5561  if(err)
5562  {
5563  if(!silenceErrors)
5564  Debug.log(err,Debug.HIGH_PRIORITY);
5565  responseHandler(modifiedTables,err);
5566  return;
5567  }
5568 
5569  //console.log(req);
5570 
5571  //modifiedTables
5572  var tableNames = req.responseXML.getElementsByTagName("NewActiveTableName");
5573  var tableVersions = req.responseXML.getElementsByTagName("NewActiveTableVersion");
5574  var tableComments = req.responseXML.getElementsByTagName("NewActiveTableComment");
5575  var tableVersion;
5576 
5577  //add only temporary version
5578  for(var i=0;i<tableNames.length;++i)
5579  {
5580  tableVersion = DesktopContent.getXMLValue(tableVersions[i])|0; //force integer
5581  if(tableVersion >= -1) continue; //skip unless temporary
5582  var obj = {};
5583  obj.tableName = DesktopContent.getXMLValue(tableNames[i]);
5584  obj.tableVersion = DesktopContent.getXMLValue(tableVersions[i]);
5585  obj.tableComment = DesktopContent.getXMLValue(tableComments[i]);
5586  modifiedTables.push(obj);
5587  }
5588  responseHandler(modifiedTables,undefined,subsetBasePath,recordCount);
5589 
5590  }, //handler
5591  0, //handler param
5592  0,true); //progressHandler, callHandlerOnErr
5593 
5594 } // end ConfigurationAPI.deleteSubsetRecords()
5595 
5596 //=====================================================================================
5597 //incrementName ~~
5598 ConfigurationAPI.incrementName = function(name)
5599 {
5600  //find last non-numeric
5601  for(var i=name.length-1;i>=0;--i)
5602  if(!(name[i] >= '0' && name[i] <= '9'))
5603  break;
5604  //note: if all numbers, then i is -1, which still works
5605  var num = (name.substr(i+1)|0) + 1;
5606  name = name.substr(0,i+1);
5607  return name + num;
5608 } //end incrementName()
5609 
5610 //=====================================================================================
5611 //createNewRecordName ~~
5612 ConfigurationAPI.createNewRecordName = function(startingName,existingArr)
5613 {
5614  var retVal = startingName;
5615  var found,i;
5616  try
5617  {
5618  var apps = existingArr;
5619  do
5620  {
5621  retVal = ConfigurationAPI.incrementName(retVal);
5622  found = false;
5623  for(i=0;i<apps.length;++i)
5624  if(apps[i] == retVal)
5625  {found = true; break;}
5626  } while(found);
5627  Debug.log("createNewRecordName " + retVal);
5628  }
5629  catch(e)
5630  {
5631  //ignore errors.. assume no all apps
5632  return ConfigurationAPI.incrementName(retVal);
5633  }
5634 
5635  return retVal;
5636 } //end createNewRecordName()
5637 
5638 
5639 
5640 
5641 
5642 
5643 
5644 
5645 
5646 
5647 
5648 
5649 
5650 
5651 
5652 
5653 
5654 
5655 
5656 
5657