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