artdaq_node_server  v1_01_01b
client.js
1 var DefaultColor = "";
2 var DefaultShadow = "";
3 var TableHtml;
4 var InfoHtml;
5 var TreeGridHtml;
6 var DialogContentHtml;
7 var CurrentNamedConfig = "";
8 var CurrentEntity;
9 var CurrentCollection;
10 var LastTabId = 1;
11 var EditedValues = {};
12 var ExportTarFileName = "export";
13 var UploadFiles = {};
14 var ConfigVersionData = {};
15 
16 //var userId = generateUUID();
17 // For Testing
18 var UserId = "1";
19 
20 // ReSharper disable once UnusedLocals
21 function GenerateUuid() {
22  var d = Date.now();
23  if (window.performance && typeof window.performance.now === "function") {
24  d += performance.now(); //use high-precision timer if available
25  }
26  var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
27  var r = (d + Math.random() * 16) % 16 | 0;
28  d = Math.floor(d / 16);
29  return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
30  });
31  return uuid;
32 };
33 
34 function UpdateHeader(error, change, text) {
35  if (error) {
36  $("#header").css("background-color", "#D59595").css("text-shadow", "#D99 0 1px 0");
37  $("#info").text(text);
38  text = "ERROR: " + text;
39  } else if (change) {
40  $("#header").css("background-color", "#E7F29B").css("text-shadow", "#EF9 0 1px 0");
41  $("#info").text(text);
42  text = "CHANGE: " + text;
43  } else {
44  $("#header").css("background-color", DefaultColor).css("text-shadow", DefaultShadow);
45  $("#info").text(text);
46  }
47  if (text.length > 0) {
48  console.log(text);
49  }
50 };
51 
52 function ResizeTextAreas() {
53  $("textarea").each(function (i, el) {
54  $(el).height(el.scrollHeight);
55  });
56 };
57 
58 function GetRadix(value) {
59  var output = {
60  radix: 10,
61  value: value
62  };
63  value = "" + value;
64  if (value.search("0x") === 0) {
65  output.value = value.slice(2);
66  output.radix = 16;
67  if (("" + output.value).search(/[^0-9a-fA-F]/) >= 0) { output.radix = -1; }
68  } else if (value.search("0") === 0) {
69  output.value = value.slice(1);
70  output.radix = 8;
71  if (isNaN(output.value)) { output.radix = -1; }
72  } else if (value.search("b") === value.length - 1) {
73  output.value = value.slice(0, -1);
74  output.radix = 2;
75  if (isNaN(output.value)) { output.radix = -1; }
76  } else if (isNaN(value)) {
77  output.radix = -1;
78  }
79  if (value === "0") {
80  output.radix = 10;
81  output.value = "0";
82  }
83  if (output.value === " " || ("" + output.value).length === 0 || ("" + output.value).indexOf(".") >= 0) {
84  output.radix = -1;
85  }
86 
87  return output;
88 }
89 
90 function CreateRowEditor(row, cellvalue, editor, cellText, width, height) {
91  editor.after("<div></div><div></div>");
92  var radixObj = GetRadix(cellText);
93  if (radixObj.radix > 0) {
94  editor.parent().jqxFormattedInput({ radix: radixObj.radix, value: radixObj.value, width: width, height: height, upperCase: true, dropDown: true, spinButtons: true });
95  }
96 };
97 
98 function InitRowEditor(row, cellvalue, editor) {
99  editor.parent().val(cellvalue);
100 };
101 
102 function GetRowEditorValue(row, cellvalue, editor) {
103  if (editor.parent()) {
104  if (editor.val().length === 0) {
105  editor.parent().parent().empty();
106  return "";
107  }
108  var radix = editor.parent().jqxFormattedInput("radix");
109  if (radix === 2 || radix === "binary") {
110  return editor.val() + "b";
111  }
112  if (radix === 16 || radix === "hexadecimal") {
113  return "0x" + editor.val().toUpperCase();
114  }
115  if (radix === 8 || radix === "octal") {
116  if (editor.val()[0] === "0") {
117  return editor.val();
118  }
119  return "0" + editor.val();
120  }
121  }
122  return editor.val();
123 };
124 
125 function RowEditDialogSave(dataFields, newRowId, grid) {
126  var row = {};
127 
128  for (var i in dataFields) {
129  if (dataFields.hasOwnProperty(i)) {
130  var name = dataFields[i].name + "_Editor";
131  var value = "" + $("#" + name).val();
132  if (value === "undefined") {
133  value = "";
134  }
135  row[dataFields[i].name] = value;
136  }
137  }
138 
139  var path = row.name;
140  var currentName = path;
141  var rowData = grid.jqxTreeGrid("getRow", newRowId);
142  var parent = rowData.parent;
143  do {
144  if ((parent === null || parent === undefined) && currentName.indexOf("___") >= 0) {
145  var parentSelector = grid.selector.split(" ").slice(0, -1).join(" ");
146  var parentRow = $(parentSelector + " #treeGrid").jqxTreeGrid("getRow", currentName.slice(0, -4));
147  parent = parentRow[0];
148  }
149  if (parent !== null && parent !== undefined) {
150  currentName = parent.name;
151  path = parent.name + "/" + path;
152  parent = parent.parent;
153  }
154  } while (parent !== null && parent !== undefined);
155 
156  if (CurrentCollection && CurrentEntity) {
157  path = CurrentCollection + "/" + CurrentEntity + "/" + path;
158  } else {
159  var pathTmp = path.split('/');
160  var collection = pathTmp.shift();
161  var entity = pathTmp.shift();
162  var collectionTab = $(".collection-tab[collection-name=\"" + collection + "\"] a");
163  collectionTab.trigger("click");
164  var fileNameTab = $(collectionTab[0].hash).find(".file-tab[file-name=\"" + entity + "\"] a");
165  fileNameTab.trigger("click");
166  $("li.active :visible").parent().addClass("editedValue");
167  $(".configInfo-tab a").trigger("click");
168  CurrentCollection = collection;
169  CurrentEntity = entity;
170  }
171  //console.log("Path is " + path);
172  grid.jqxTreeGrid("updateRow", newRowId, row);
173  EditedValues[CurrentCollection + "/" + CurrentEntity + "/" + row.name] = true;
174  $("li.active :visible").parent().addClass("editedValue");
175  $(".configInfo-tab").removeClass("editedValue");
176 
177  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
178  AjaxPost("/db/AddOrUpdate", {
179  configName: CurrentNamedConfig,
180  table: path,
181  id: row.id,
182  row: row,
183  user: UserId
184  }, function (retval) {
185  if (retval.Success) {
186  var now = new Date;
187  $("#changes", $(".file-tab.active a").attr("href")).val(now.toISOString() + ": Edit - Path: " + path + ", Name: " + row.name + " Dialog Edit - multiple columns may have been changed!");
188  $("#masterChanges").val(now.toISOString() + ": Edit - Path: " + path + ", Name: " + row.name + " Dialog Edit - multiple columns may have been changed!");
189  ResizeTextAreas();
190  UpdateHeader(false, true, "There are pending unsaved changes. Please save or discard before closing the editor!");
191  } else {
192  UpdateHeader(true, false, "Sending Update to server failed");
193  }
194  });
195 
196 }
197 
198 function MakeTreeGrid(tag, dataFields, data, columnGroups) {
199  tag.html(TreeGridHtml);
200  var grid = tag.find("#treeGrid");
201  var contextMenu = tag.find("#Menu");
202  var dialog = tag.find("#dialog");
203  var newRowId = null;
204  var displayColumns = TranslateColumns(dataFields);
205  // prepare the data
206  //console.log("Data is " + JSON.stringify(data));
207  console.log("DisplayColumns is " + JSON.stringify(displayColumns));
208  console.log("DataFields is " + JSON.stringify(dataFields));
209  //console.log("ColumnGroups is " + JSON.stringify(columnGroups));
210  var source = {
211  dataType: "json",
212  dataFields: dataFields,
213  hierarchy: {
214  root: "children"
215  },
216  localData: data,
217  comment: "comment",
218  addRow: function (rowID, rowData, position, parentID, commit) {
219  newRowId = rowID;
220  commit(true);
221  }
222  };
223  // ReSharper disable once InconsistentNaming
224  var dataAdapter = new $.jqx.dataAdapter(source, {
225  beforeLoadComplete: function (records) {
226  var numberFields = [];
227  for (var c in source.dataFields) {
228  if (source.dataFields.hasOwnProperty(c)) {
229  if (source.dataFields[c].dataType === "number") {
230  numberFields.push({ name: source.dataFields[c].name, radix: source.dataFields[c].radix });
231  }
232  }
233  }
234  for (var i in records) {
235  if (records.hasOwnProperty(i)) {
236  for (var f in numberFields) {
237  if (numberFields.hasOwnProperty(f)) {
238  var radix = numberFields[f].radix;
239  if (!radix) {
240  radix = 10;
241  }
242  var value = records[i][numberFields[f].name];
243  if (value) {
244  if (typeof value === "number" || !(value.search("0x") === 0 || value.search("0") === 0 || value.search("b") === value.length - 1)) {
245 
246  value = parseInt(value).toString(radix).toUpperCase();
247  records[i][numberFields[f].name] = value;
248  if (radix === 2) {
249  records[i][numberFields[f].name] += "b";
250  }
251  if (radix === 16) {
252  records[i][numberFields[f].name] = "0x" + value;
253  }
254  if (radix === 8) {
255  records[i][numberFields[f].name] = "0" + value;
256  }
257  }
258  }
259  }
260  }
261  }
262  }
263  return records;
264  }
265  });
266 
267  var sequenceTables = [];
268  // create Tree Grid
269  grid.addClass("jqxTreeGrid").jqxTreeGrid(
270  {
271  width: "100%",
272  source: dataAdapter,
273  editable: true,
274  editSettings: { saveOnPageChange: true, saveOnBlur: true, saveOnSelectionChange: true, cancelOnEsc: true, saveOnEnter: true, editSingleCell: true, editOnDoubleClick: true, editOnF2: true },
275  sortable: true,
276  columnsResize: true,
277  columns: displayColumns,
278  columnGroups: columnGroups,
279  rowDetails: true,
280  rowDetailsRenderer: function (rowKey, row) {
281  if (row.isSequence) {
282  var indent = (1 + row.level) * 20;
283  var details = "<div id=\"" + rowKey + "___Table\" style=\"margin-left: " + indent + "px;padding-right: 5px\"></div>";
284  sequenceTables.push({ key: rowKey, data: row.table });
285  return details;
286  }
287  return "";
288  }
289  ,
290  rendered: function () {
291  $(".jqx-tooltip").remove();
292  var setupRow = function (rowObject) {
293  if (rowObject[source.comment] && rowObject[source.comment].length > 0 && rowObject[source.comment] !== " ") {
294  var selector = $("tr[data-key=\'" + rowObject.uid + "\']");
295  if (selector.length) {
296  selector.jqxTooltip({ content: rowObject[source.comment], position: "mouse" });
297  }
298  }
299  for (var trow in rowObject.records) {
300  if (rowObject.records.hasOwnProperty(trow)) {
301  setupRow(rowObject.records[trow]);
302  }
303  }
304  };
305  var rows = grid.jqxTreeGrid("getRows");
306  for (var ttrow = 0; ttrow < rows.length; ttrow++) {
307  setupRow(rows[ttrow]);
308  }
309  for (var seq in sequenceTables) {
310  if (sequenceTables.hasOwnProperty(seq)) {
311  var elem = tag.find("#" + sequenceTables[seq].key + "___Table");
312  var data = sequenceTables[seq].data;
313  MakeTreeGrid(elem, data.columns, data.rows, data.columnGroups);
314  }
315  }
316  },
317  ready: function () {
318  dialog.on("close", function () {
319  // enable jqxTreeGrid.
320  grid.jqxTreeGrid({ disabled: false });
321  });
322  dialog.jqxWindow({
323  resizable: true,
324  width: 270,
325  position: { left: grid.offset().left + 75, top: grid.offset().top + 35 },
326  autoOpen: false,
327  showCloseButton: false
328  });
329  dialog.css("visibility", "visible");
330  }
331  });
332 
333 
334  grid.jqxTreeGrid("expandAll");
335  // create context menu
336  contextMenu.jqxMenu({ width: 200, height: 90, autoOpenPopup: false, mode: "popup" });
337  grid.on("contextmenu", function () {
338  return false;
339  });
340  grid.on("rowClick", function (event) {
341  var args = event.args;
342  if (args.originalEvent.button === 2) {
343  var scrollTop = $(window).scrollTop();
344  var scrollLeft = $(window).scrollLeft();
345 
346  if (args.row.type === "table" || args.row.type === "sequence") {
347  contextMenu.jqxMenu("disable", "edit", true);
348  contextMenu.jqxMenu("disable", "bitedit", true);
349  contextMenu.jqxMenu("disable", "add", false);
350  } else {
351  contextMenu.jqxMenu("disable", "edit", false);
352  contextMenu.jqxMenu("disable", "bitedit", false);
353  contextMenu.jqxMenu("disable", "add", true);
354  }
355 
356  contextMenu.jqxMenu("open", parseInt(event.args.originalEvent.clientX) + 5 + scrollLeft, parseInt(event.args.originalEvent.clientY) + 5 + scrollTop);
357  return false;
358  }
359  return true;
360  });
361  var edit = function (row, key, newRow, x, y, deleteOnCancel) {
362  newRowId = key;
363  // update the widgets inside jqxWindow.
364  dialog.jqxWindow("setTitle", "Edit Row: " + row.name);
365  dialog.jqxWindow("setContent", DialogContentHtml);
366  dialog.jqxWindow("move", x, y);
367  var jqEditors = [];
368  for (var i in dataFields) {
369  if (dataFields.hasOwnProperty(i)) {
370  var name = dataFields[i].name;
371  var value = "" + row[name];
372  if (value === "undefined") {
373  value = "";
374  }
375  if (dataFields[i].editable || newRow) {
376  var start = "<tr><td align=\"right\">" + name + ":</td><td align=\"left\">";
377  var editor = "<input id=\"" + name + "_Editor\" type=\"text\" value=\"" + value + "\" />";
378 
379  var radixObj = GetRadix(value);
380  if (name === "name") radixObj.radix = -1;
381 
382  if (radixObj.radix > 0) {
383  editor = "<div id=\"" + name + "_Editor\"><input type=\"text\" /><div></div><div></div></div>";
384  jqEditors.push({
385  name: name + "_Editor",
386  value: radixObj.value,
387  radix: radixObj.radix
388  });
389  }
390  var end = "</td></tr>";
391  dialog.find("tr:last").before(start + editor + end);
392  } else {
393  dialog.find("tr:last").before("<tr><td align=\"right\">" + name + ":</td><td align=\"left\"><input id=\"" + name + "_Editor\" type=\"text\" value=\"" + value + "\" readonly=\"true\" disabled=\"disabled\" /></td></tr>");
394  }
395  }
396  }
397  var content = dialog.find("table").parent().html();
398  dialog.jqxWindow("setContent", content);
399  for (var ed in jqEditors) {
400  if (jqEditors.hasOwnProperty(ed)) {
401  dialog.find("#" + jqEditors[ed].name).jqxFormattedInput({ radix: jqEditors[ed].radix, value: jqEditors[ed].value, width: "100%", height: 25, upperCase: true, dropDown: true, spinButtons: true });
402  }
403  }
404  dialog.find("#save").jqxButton({ height: 30, width: 80 });
405  dialog.find("#cancel").jqxButton({ height: 30, width: 80 });
406  dialog.find("#save").mousedown(function () {
407  dialog.jqxWindow("close");
408  RowEditDialogSave(dataFields, newRowId, grid);
409  });
410  dialog.find("#cancel").mousedown(function () {
411  dialog.jqxWindow("close");
412  if(deleteOnCancel) grid.jqxTreeGrid("deleteRow", newRowId);
413  });
414  dialog.jqxWindow("open");
415  var newHeight = $("#bottomOfDialog").position().top + 100;
416  dialog.jqxWindow({ height: newHeight });
417  dialog.attr("data-row", key);
418  // disable jqxTreeGrid.
419 
420  grid.jqxTreeGrid({ disabled: true });
421  };
422  var bitedit = function (row, key, x, y) {
423  newRowId = key;
424  // update the widgets inside jqxWindow.
425  dialog.jqxWindow("setTitle", "Bit Editor for Row: " + row.name);
426  dialog.jqxWindow("setContent", DialogContentHtml);
427  dialog.jqxWindow("move", x, y);
428  var fields = [];
429  for (var i in dataFields) {
430  if (dataFields.hasOwnProperty(i)) {
431  var name = dataFields[i].name;
432  var value = "" + row[name];
433  if (value === "undefined") {
434  value = "";
435  }
436  dialog.find("tr:last").before("<input id=\"" + name + "_Editor\" type=\"hidden\" value=\"" + value + "\" />");
437  if (dataFields[i].editable) {
438  var radixObj = GetRadix(value);
439  if (radixObj.radix === 2 || radixObj.radix === 8 || radixObj.radix === 16)
440  fields.push(name);
441  }
442  }
443  }
444 
445  var selectHtml = "<select id=bitFieldSelect>";
446  for (var f in fields) {
447  if (fields.hasOwnProperty(f)) {
448  selectHtml += "<option id=" + fields[f] + " value=\"" + fields[f] + "\">" + fields[f] + "</option>";
449  }
450  }
451  selectHtml += "</select>";
452  dialog.find("tr:last").before("<tr><td colspan=\"2\" align=\"right\">" + selectHtml + "</td></tr>");
453  dialog.find("tr:last").before('<tr><td colspan="2" align="right"><div id=bitCheckboxes></div></td></tr>');
454 
455  var content = dialog.find("table").parent().html();
456  dialog.jqxWindow("setContent", content);
457  dialog.find("#bitFieldSelect").change(function () {
458  var currentValue = dialog.find("#" + this.value + "_Editor").val();
459  var radixObj = GetRadix(currentValue);
460  var length = String(radixObj.value).length;
461  var bits = 0;
462  switch (radixObj.radix) {
463  case 2:
464  bits = length;
465  break;
466  case 8:
467  bits = 3 * length;
468  break;
469  case 16:
470  bits = 4 * length;
471  break;
472  }
473 
474  var bitFieldCheckboxHtml = "<table><tr>";
475  for (var i = 0; i < bits; ++i) {
476  if (i > 0 && i % 4 === 0) {
477  bitFieldCheckboxHtml += "</tr><tr>";
478  }
479  var checkedStr = (currentValue & (1 << i)) !== 0 ? "checked" : "";
480  bitFieldCheckboxHtml += "<td><input type=\"checkbox\" name=\"" + i + "\" class=\"bitFieldCheckBox\"" + checkedStr + ">" + i + "</input></td>";
481  }
482  bitFieldCheckboxHtml += "</tr></table>";
483  dialog.find("#bitCheckboxes").html(bitFieldCheckboxHtml);
484  var newHeight = $("#bottomOfDialog").position().top;
485  dialog.jqxWindow({ height: newHeight });
486  $(".bitFieldCheckBox").change(function () {
487  var currentValue = String(dialog.find("#" + $("#bitFieldSelect").val() + "_Editor").val());
488  var radixObj = GetRadix(currentValue);
489  var bitId = this.name;
490  var digit, rem, curDigit, mask;
491  switch (radixObj.radix) {
492  case 2:
493  currentValue = currentValue.substr(0, currentValue.length - bitId - 1) + (this.checked ? "1" : "0") + currentValue.substr(currentValue.length - bitId);
494  break;
495  case 8:
496  digit = Math.floor(bitId / 3);
497  rem = bitId - (digit * 3);
498  curDigit = parseInt(currentValue.charAt(currentValue.length - digit - 1), 8);
499  if (this.checked) {
500  curDigit = curDigit | (1 << rem);
501  } else {
502  mask = 7 - (1 << rem);
503  curDigit = curDigit & mask;
504  }
505  currentValue = currentValue.substr(0, currentValue.length - digit - 1) + curDigit.toString(8) + currentValue.substr(currentValue.length - digit);
506  break;
507  case 16:
508  digit = Math.floor(bitId / 4);
509  rem = bitId - (digit * 4);
510  curDigit = parseInt(currentValue.charAt(currentValue.length - digit - 1), 16);
511  if (this.checked) {
512  curDigit = curDigit | (1 << rem);
513  } else {
514  mask = 15 - (1 << rem);
515  curDigit = curDigit & mask;
516  }
517  currentValue = currentValue.substr(0, currentValue.length - digit - 1) + curDigit.toString(16) + currentValue.substr(currentValue.length - digit);
518  break;
519  }
520  dialog.find("#" + $("#bitFieldSelect").val() + "_Editor").val(currentValue);
521  });
522  }).trigger("change");
523  dialog.find("#save").jqxButton({ height: 30, width: 80 });
524  dialog.find("#cancel").jqxButton({ height: 30, width: 80 });
525  dialog.find("#save").mousedown(function () {
526  dialog.jqxWindow("close");
527  RowEditDialogSave(dataFields, newRowId, grid);
528  });
529  dialog.find("#cancel").mousedown(function () {
530  dialog.jqxWindow("close");
531  });
532  dialog.jqxWindow("open");
533  var newHeight = $("#bottomOfDialog").position().top;
534  dialog.jqxWindow({ height: newHeight });
535  dialog.attr("data-row", key);
536  // disable jqxTreeGrid.
537 
538  grid.jqxTreeGrid({ disabled: true });
539  };
540  contextMenu.on("itemclick", function (event) {
541  var args = event.args;
542  var selection = grid.jqxTreeGrid("getSelection");
543  var rowid = selection[0].uid;
544  var text = $.trim($(args).text());
545  if (text === "Edit Selected Row") {
546  edit(selection[0], rowid, false, event.pageX, event.pageY, false);
547  } else if (text === "Bit Editor") {
548  bitedit(selection[0], rowid, event.pageX, event.pageY);
549  } else {
550  var arrayName = false;
551  if (("" + rowid).search(/___/) > 0) {
552  arrayName = true;
553  }
554  grid.jqxTreeGrid("addRow", null, {}, "last", rowid);
555  if (arrayName) {
556  var idx = newRowId.search(/___/);
557  var num = newRowId.slice(idx + 3, -1);
558  num = parseInt(num) + 1;
559  newRowId = newRowId.slice(0, idx + 3) + num;
560  }
561  var obj = {};
562  for (var i in dataFields) {
563  if (dataFields.hasOwnProperty(i)) {
564  obj[dataFields[i].name] = "";
565  }
566  }
567  obj.name = newRowId;
568  edit(obj, newRowId, true, event.pageX, event.pageY, true);
569  }
570  });
571 
572  // Cell End Edit
573  grid.on("cellEndEdit", function (event) {
574  var args = event.args;
575  // row key
576  var rowKey = args.key;
577  // row's data.
578  var rowData = args.row;
579  var path = rowData.name;
580  var currentName = rowKey;
581  var parent = rowData.parent;
582  var first = true;
583  do {
584  if ((parent === null || parent === undefined) && currentName.indexOf("___") >= 0) {
585  var parentSelector = tag.selector.split(" ").slice(0, -1).join(" ");
586  var parentRow = $(parentSelector + " #treeGrid").jqxTreeGrid("getRow", currentName.slice(0, -4));
587  parent = parentRow[0];
588  while ((parent !== null && parent !== undefined) && parent.constructor === Array) {
589  parent = parent[0];
590  }
591  }
592  else if ((parent === null || parent === undefined) && rowData.name.indexOf("___") >= 0 && first) {
593  first = false;
594  var parentSelector = tag.selector.split(" ").slice(0, -1).join(" ");
595  var parentName = tag.selector.split(" ").slice(-1).join(" ").slice(1);
596  parentName = parentName.slice(0, parentName.indexOf("___"));
597  var parentRow = $(parentSelector + " #treeGrid").jqxTreeGrid("getRow", parentName);
598  parent = parentRow[0];
599  while ((parent !== null && parent !== undefined) && parent.constructor === Array) {
600  parent = parent[0];
601  }
602  }
603 
604  if (parent !== null && parent !== undefined) {
605  currentName = parent.name;
606  path = parent.name + "/" + path;
607  parent = parent.parent;
608  }
609  } while (parent !== null && parent !== undefined);
610 
611  if (CurrentCollection && CurrentEntity) {
612  path = CurrentCollection + "/" + CurrentEntity + "/" + path;
613  } else {
614  var pathTmp = path.split('/');
615  var collection = pathTmp.shift();
616  var entity = pathTmp.shift();
617  var collectionTab = $(".collection-tab[collection-name=\"" + collection + "\"] a");
618  collectionTab.trigger("click");
619  var fileNameTab = $(collectionTab[0].hash).find(".file-tab[file-name=\"" + entity + "\"] a");
620  fileNameTab.trigger("click");
621  $("li.active :visible").parent().addClass("editedValue");
622  $(".configInfo-tab a").trigger("click");
623  CurrentCollection = collection;
624  CurrentEntity = entity;
625  }
626  console.log("Path is " + path);
627 
628  var columnDataField = args.dataField;
629  // column name
630  var columnName = args.dataField;
631  for (var i in args.owner._columns) {
632  if (args.owner._columns.hasOwnProperty(i)) {
633  if (args.owner._columns[i].dataField === columnName) {
634  columnName = args.owner._columns[i].text;
635  break;
636  }
637  }
638  }
639  EditedValues[CurrentCollection + "/" + CurrentEntity + "/" + rowKey] = true;
640  $("li.active :visible").parent().addClass("editedValue");
641  $(".configInfo-tab").removeClass("editedValue");
642  // cell's value.
643  var value = args.value;
644  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
645  AjaxPost("/db/Update", {
646  configName: CurrentNamedConfig,
647  table: path,
648  column: columnDataField,
649  id: rowData.id,
650  name: rowData.name,
651  value: value,
652  user: UserId
653  }, function (retval) {
654  if (retval.Success) {
655  var now = new Date;
656  var selector = $("#changes", $(".file-tab.active a").attr("href"));
657  selector.val(now.toISOString() + ": Edit - File: " + CurrentCollection + "/" + CurrentEntity + ", Name: " + rowData.name + ", Column: " + columnName + ", Value: " + value + "\n" + selector.val());
658  $("#masterChanges").val(now.toISOString() + ": Edit - File: " + CurrentCollection + "/" + CurrentEntity + ", Name: " + rowData.name + ", Column: " + columnName + ", Value: " + value + "\n" + $("#masterChanges").val());
659  UpdateHeader(false, true, "There are pending unsaved changes. Please save or discard before closing the editor!");
660  } else {
661  UpdateHeader(true, false, "Sending Update to server failed");
662  }
663  });
664  });
665 };
666 
667 function CellClass(row, dataField, cellText, rowData) {
668  var edited = false;
669  for (var val in EditedValues) {
670  if (EditedValues.hasOwnProperty(val)) {
671  if (val === CurrentCollection + "/" + CurrentEntity + "/" + rowData.uid) {
672  edited = EditedValues[val];
673  break;
674  }
675  }
676  }
677  if (edited) {
678  return "editedValue";
679  }
680  return "value";
681 };
682 
683 function TranslateColumns(columns) {
684  var displayColumns = [];
685 
686  for (var c in columns) {
687  if (columns.hasOwnProperty(c)) {
688  var title = columns[c].title;
689  if (title === undefined || title === null || title.length === 0) {
690  title = columns[c].name.charAt(0).toUpperCase() + columns[c].name.slice(1);
691  }
692  if (columns[c].type === "string" && columns[c].display) {
693  displayColumns.push({
694  text: title,
695  dataField: columns[c].name,
696  editable: columns[c].editable,
697  columnGroup: columns[c].columnGroup,
698  createEditor: CreateRowEditor,
699  initEditor: InitRowEditor,
700  getEditorValue: GetRowEditorValue,
701  cellClassName: CellClass
702  });
703  } else if (columns[c].type === "number" && columns[c].display) {
704  columns[c].type = "string";
705  columns[c].dataType = "number";
706  displayColumns.push({
707  text: title,
708  dataField: columns[c].name,
709  editable: columns[c].editable,
710  columnGroup: columns[c].columnGroup,
711  createEditor: CreateRowEditor,
712  initEditor: InitRowEditor,
713  getEditorValue: GetRowEditorValue,
714  cellClassName: CellClass
715  });
716  }
717  }
718  }
719  return displayColumns;
720 }
721 
722 function LoadTable(tag) {
723  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
724  AjaxPost("/db/GetData", { configName: CurrentNamedConfig, entity: CurrentEntity, collection: CurrentCollection, user: UserId }, function (data) {
725  if (!data.Success) {
726  UpdateHeader(true, false, "Fetch of data from database failed");
727  return;
728  }
729  var columns = data.data.columns;
730 
731  MakeTreeGrid(tag, columns, data.data.children); //.trigger('create');
732 
733  });
734 };
735 
736 function SetupConfigVersionPicker(element, target) {
737 
738  element.on("change", function () {
739  var thisConfig = element.find(":selected").text();
740  var configVersionHtml = "";
741  for (var version in ConfigVersionData[thisConfig]) {
742  if (ConfigVersionData[thisConfig].hasOwnProperty(version)) {
743  configVersionHtml += "<option name=\"" + ConfigVersionData[thisConfig][version].name
744  + "\" value=" + ConfigVersionData[thisConfig][version].data + ">" + ConfigVersionData[thisConfig][version].version + "</option>";
745  }
746  }
747  $("#" + target).html(configVersionHtml).trigger("create").selectmenu("refresh");
748  $("#" + target).html($("#" + target + " option").sort(function (a, b) {
749  return parseInt(a.text) === parseInt(b.text) ? 0 : parseInt(a.text) > parseInt(b.text) ? -1 : 1;
750  }));
751  var option = $("#" + target).find('option:eq(0)');
752  option.prop('selected', true);
753 $("#"+target).trigger("create").selectmenu("refresh");
754  });
755 }
756 
757 function GetConfigList() {
758  UpdateHeader(false, false, "");
759  $("#masterChanges").val("");
760  ResizeTextAreas();
761  for (var i = 2; i <= LastTabId; i++) {
762  $("#tab" + i).remove();
763  $("#tablink" + i).remove();
764  }
765 
766  $("#reloadConfigsButton").text("Reload Configurations");
767 
768  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
769  AjaxPost("/db/NamedConfigs", { configFilter: $("#configurationFilter").val(), user: UserId }, function (data) {
770  if (!data.Success) {
771  UpdateHeader(true, false, "Error retrieving Configuration list. Please contact an expert!");
772  return;
773  }
774  var configsHtml = "";
775  for (var name in data.data) {
776  if (data.data.hasOwnProperty(name)) {
777  configsHtml += "<option>" + name + "</option>";
778  ConfigVersionData[name] = data.data[name];
779  }
780  }
781  $("#configs").html(configsHtml).trigger("create").selectmenu("refresh");
782  $("#oldConfigName").html(configsHtml).trigger("create").selectmenu("refresh");
783  $("#exportConfigName").html(configsHtml).trigger("create").selectmenu("refresh");
784 
785  SetupConfigVersionPicker($("#configs"), "configversions");
786  $("#configs").trigger("change");
787  SetupConfigVersionPicker($("#oldConfigName"), "oldConfigVersion");
788  $("#oldConfigName").trigger("change");
789  SetupConfigVersionPicker($("#exportConfigName"), "exportConfigVersion");
790  $("#exportConfigName").trigger("change");
791 
792  var config = GetUrlParameter("configs");
793  if (config !== undefined) {
794  $("#configs").val(config);
795  }
796  });
797  $("#configLoad").collapsible("option", "disabled", false).collapsible("option", "collapsed", false);
798  $("#configSave").collapsible("option", "disabled", true).collapsible("option", "collapsed", true);
799  $("#configMetadata").collapsible("option", "disabled", true).collapsible("option", "collapsed", true);
800  $("#searchConfig").collapsible("option", "disabled", false);
801  $("#exportFile").collapsible("option", "collapsed", true);
802  $("#newConfig").collapsible("option", "collapsed", true);
803 };
804 
805 function RegisterTabFunctions() {
806  $(".file-tab a").off();
807  $(".collection-tab a").off();
808  $(".tabs .tab-links a").off().on("click", function (e) {
809  var currentAttrValue = $(this).attr("href");
810  var tab = $(this).parent();
811  var div = tab.parent();
812  div.scrollLeft(0);
813  var left = Math.floor(tab.position().left - 20);
814  div.scrollLeft(left);
815  // Show/Hide Tabs
816  $(".tabs " + currentAttrValue).show().siblings().hide();
817 
818  // Change/remove current tab to active
819  $(this).parent("li").addClass("active").siblings().removeClass("active");
820 
821  e.preventDefault();
822  });
823  $(".configInfo-tab a").on("click", function () {
824  CurrentCollection = null;
825  CurrentEntity = null;
826  });
827  $(".collection-tab a").on("click", function () {
828  CurrentCollection = $(this).text();
829  });
830  $(".file-tab a").on("click", function () {
831  var fileName = $(this).text();
832  CurrentEntity = fileName;
833  LoadFile($(this).attr("href"));
834  ResizeTextAreas();
835  });
836 };
837 
838 function GetUrlParameter(sParam) {
839  var sUrlVariables = window.location.search.substring(1).split("&");
840  //console.log("sUrlVariables: " + sUrlVariables);
841  for (var i = 0; i < sUrlVariables.length; i++) {
842  var sParameterName = sUrlVariables[i].split("=");
843  //console.log("Comparing " + sParam + " to " + sParameterName[0] + " (" + sParameterName[1] + ")");
844  if (sParameterName[0].search(sParam) >= 0) {
845  //console.log("Returning " + sParameterName[1]);
846  return sParameterName[1];
847  }
848  }
849  return "";
850 };
851 
852 function LoadConfigMetadata() {
853  console.log("Loading configuration metadata");
854  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
855  AjaxPost("/db/LoadConfigMetadata", { configName: CurrentNamedConfig, user: UserId }, function (metadata) {
856  if (!metadata.Success) {
857  UpdateHeader(true, false, "Error loading configuration metadata from database");
858  return;
859  }
860  var metadataObj = metadata.data;
861  //console.log(metadata);
862 
863  var displayColumns = [
864  {
865  text: "Entity Name",
866  dataField: "name",
867  editable: false
868  },
869  {
870  text: "Collection Name",
871  dataField: "collection",
872  editable: false
873  },
874  {
875  text: "Version",
876  dataField: "version",
877  editable: false
878  },
879  {
880  text: "Edit", cellsAlign: "center", align: "center", columnType: "none", editable: false, sortable: false, dataField: null, cellsRenderer: function (row) {
881  // render custom column.
882  return "<button data-row='" + row + "' class='editButtons' onclick=''>Edit</button>";
883  }
884  }
885  ];
886  var dataFields = [
887  { name: "name", type: "string", editable: false, display: true },
888  { name: "collection", type: "string", editable: false, display: true },
889  { name: "file", type: "string", editable: false, display: false },
890  { name: "version", type: "string", editable: false, display: true }
891  ];
892  var source = {
893  dataType: "json",
894  dataFields: dataFields,
895  id: "file",
896  hierarchy: {
897  root: "values"
898  },
899  localData: metadataObj.entities
900  };
901  // ReSharper disable once InconsistentNaming
902  var dataAdapter = new $.jqx.dataAdapter(source);
903  // create Tree Grid
904  var grid = $("#configurationEntities");
905  grid.addClass("jqxTreeGrid").jqxTreeGrid(
906  {
907  width: "100%",
908  source: dataAdapter,
909  editable: false,
910  sortable: true,
911  columnsResize: true,
912  columns: displayColumns,
913  rendering: function () {
914  // destroys all buttons.
915  if ($(".editButtons").length > 0) {
916  $(".editButtons").jqxButton("destroy");
917  }
918  },
919  rendered: function () {
920  if ($(".editButtons").length > 0) {
921  $(".editButtons").jqxButton();
922 
923  var editClick = function (event) {
924  // get clicked row.
925  var rowKey = event.target.getAttribute("data-row");
926  var row = grid.jqxTreeGrid("getRow", rowKey);
927  var collection = row["collection"];
928  var collectionTab = $(".collection-tab[collection-name=\"" + collection + "\"] a");
929  collectionTab.trigger("click");
930  var file = row["name"];
931  var fileNameTab = $(collectionTab[0].hash).find(".file-tab[file-name=\"" + file + "\"] a");
932  fileNameTab.trigger("click");
933  };
934  $(".editButtons").on("click", function (event) {
935  editClick(event);
936  return false;
937  });
938 
939  }
940  }
941  });
942  });
943 };
944 
945 function AddEntityToFile(id) {
946  var newEntityName = $(id + " #newEntityName").val();
947  $(id + " #newEntityName").val("");
948  if (newEntityName !== "") {
949  AjaxPost("/db/AddEntityToFile", { configName: CurrentNamedConfig, entity: CurrentEntity, collection: CurrentCollection, user: UserId, name: newEntityName }, function (res) {
950  if (res.Success) {
951  LoadFile(id);
952  }
953  else {
954  UpdateHeader(true, false, "Error adding Entity to configuration file");
955  }
956  });
957  }
958 }
959 
960 function LoadFile(id) {
961  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
962  AjaxPost("/db/LoadFileMetadata", { configName: CurrentNamedConfig, entity: CurrentEntity, collection: CurrentCollection, user: UserId }, function (metadata) {
963  if (!metadata.Success) {
964  UpdateHeader(true, false, "Error loading file metadata from database");
965  return;
966  }
967  console.log("Loading file metadata");
968  var metadataObj = metadata.data;
969  console.log(metadata);
970  $(id + " #metadataCollection").val(metadataObj.collection);
971  $(id + " #metadataVersion").val(metadataObj.version);
972  $(id + " #changeLog").val(metadataObj.changelog);
973  //$(id + " #addNewEntity").on("click", function () { AddEntityToFile(id); });
974  var displayColumns = [
975  {
976  text: "Name",
977  dataField: "name",
978  editable: false
979  },
980  {
981  text: "Date Assigned",
982  dataField: "assigned",
983  editable: false
984  }
985  ];
986  var dataFields = [
987  { name: "name", type: "string", editable: false, display: true },
988  { name: "assigned", type: "date", editable: false, display: true }
989  ];
990  var entitiesSource = {
991  dataType: "json",
992  dataFields: dataFields,
993  id: "name",
994  hierarchy: {
995  root: "values"
996  },
997  localData: metadataObj.entities
998  };
999  // ReSharper disable once InconsistentNaming
1000  var entitiesDataAdapter = new $.jqx.dataAdapter(entitiesSource);
1001  // create Tree Grid
1002  $(id + " #metadataEntities").addClass("jqxTreeGrid").jqxTreeGrid(
1003  {
1004  width: "100%",
1005  source: entitiesDataAdapter,
1006  editable: false,
1007  sortable: true,
1008  columnsResize: true,
1009  columns: displayColumns
1010  });
1011  var configsSource = {
1012  dataType: "json",
1013  dataFields: dataFields,
1014  id: "name",
1015  hierarchy: {
1016  root: "values"
1017  },
1018  localData: metadataObj.configurations
1019  };
1020  // ReSharper disable once InconsistentNaming
1021  var configsDataAdapter = new $.jqx.dataAdapter(configsSource);
1022  // create Tree Grid
1023  $(id + " #metadataConfigurations").addClass("jqxTreeGrid").jqxTreeGrid(
1024  {
1025  width: "100%",
1026  source: configsDataAdapter,
1027  editable: false,
1028  sortable: true,
1029  columnsResize: true,
1030  columns: displayColumns
1031  });
1032  LoadTable($(id + " #fileTable"));
1033  });
1034 };
1035 
1036 function SearchCurrentConfig() {
1037  console.log("Searching Configuration");
1038 
1039  var searchKey = $("#searchKey").val();
1040  console.log("SearchKey is " + searchKey);
1041  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1042  AjaxPost("/db/SearchLoadedConfig", { configName: CurrentNamedConfig, user: UserId, searchKey: searchKey }, function (results) {
1043  if (!results.Success) {
1044  UpdateHeader(true, false, "Error searching configuration");
1045  return;
1046  }
1047 
1048  MakeTreeGrid($("#searchResults"), results.columns, results.collections);
1049  });
1050 }
1051 
1052 function LoadConfig() {
1053  console.log("Loading Configuration");
1054  $("#masterChanges").val("");
1055  UpdateHeader(false, false, "");
1056  var selected = $("#configversions").find(":selected");
1057  if (selected.text() === "No Configurations Found" || selected.text() === "Click \"Load Configurations\" To Load Configuration Names") { return; }
1058  CurrentNamedConfig = selected.attr("name");
1059  $("#configName").val(CurrentNamedConfig);
1060  for (var i = 2; i <= LastTabId; i++) {
1061  $("#tab" + i).remove();
1062  $("#tablink" + i).remove();
1063  }
1064  LastTabId = 1;
1065  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1066  AjaxPost("/db/LoadNamedConfig", { configName: CurrentNamedConfig, query: selected.val(), user: UserId }, function (config) {
1067  if (!config.Success) {
1068  UpdateHeader(true, false, "Error loading configuration files from database");
1069  return;
1070  }
1071  ResizeTextAreas();
1072  for (var collection in config.collections) {
1073  if (config.collections.hasOwnProperty(collection)) {
1074  LastTabId++;
1075  var parentTab = LastTabId;
1076  $("#tabLinks").append("<li id=\"tablink" + LastTabId + "\"collection-name=\"" + config.collections[collection].name + "\" tabNum=\"" + LastTabId + "\" class=\"collection-tab\"><a href=\"#tab" + LastTabId + "\">" + config
1077  .collections[collection].name + "</a></li>");
1078  $("#tabContents").append("<div id=tab" + LastTabId + " class=\"tab\"></div>");
1079  $("#tab" + LastTabId).html(TableHtml);
1080 
1081  for (var file in config.collections[collection].files) {
1082  if (config.collections[collection].files.hasOwnProperty(file)) {
1083  var name = config.collections[collection].files[file];
1084 
1085  LastTabId++;
1086  $("#tab" + parentTab + " #tabLinks").append("<li id=\"tablink" + LastTabId + "\" file-name=\"" + name + "\" tabNum=\"" + LastTabId + "\" class=\"file-tab\"><a href=\"#tab" + LastTabId + "\">" + name + "</a></li>");
1087  $("#tab" + parentTab + " #tabContents").append("<div id=tab" + LastTabId + " class=\"tab\"></div>");
1088  $("#tab" + LastTabId).html(InfoHtml).trigger("create");;
1089  }
1090  }
1091  }
1092  }
1093  $("#configLoad").collapsible("option", "disabled", false).collapsible("option", "collapsed", true);
1094  $("#configSave").collapsible("option", "disabled", false).collapsible("option", "collapsed", false);
1095  LoadConfigMetadata();
1096  $("#configMetadata").collapsible("option", "disabled", false).collapsible("option", "collapsed", false);
1097  $("#searchConfig").collapsible("option", "disabled", false);
1098  RegisterTabFunctions();
1099  });
1100 };
1101 
1102 function BaseConfig() {
1103  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1104  AjaxPost("/db/LoadConfigMetadata", { configName: $("#oldConfigName :selected").text(), user: UserId }, function (metadata) {
1105  if (!metadata.Success) {
1106  UpdateHeader(true, false, "Error loading configuration metadata from database");
1107  return;
1108  }
1109  var metadataObj = metadata.data;
1110 
1111  // Unselect Everything!
1112  $("#newConfigName").val($("#oldConfigName :selected").text());
1113  var tag = $("#configurationPicker");
1114  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1115  for (var r in rows) {
1116  if (rows.hasOwnProperty(r)) {
1117  var thisRow = rows[r];
1118  var isChecked = false;
1119  for (var e in metadataObj.entities) {
1120  if (isChecked) break;
1121  if (metadataObj.entities.hasOwnProperty(e)) {
1122  var entity = metadataObj.entities[e];
1123  if (thisRow.name === entity.collection) {
1124  for (var r in thisRow.records) {
1125  if (thisRow.records.hasOwnProperty(r)) {
1126  var record = thisRow.records[r];
1127  if (record.id === entity.collection + entity.name) {
1128  tag.find("#grid").jqxTreeGrid("updateRow", record.uid, { name: entity.name, version: entity.version });
1129  tag.find("#grid").jqxTreeGrid("checkRow", record.uid);
1130  isChecked = true;
1131  break;
1132  }
1133  }
1134  }
1135  }
1136  }
1137  }
1138  if (!isChecked && thisRow.checked) {
1139  tag.find("#grid").jqxTreeGrid("uncheckRow", thisRow.uid);
1140  }
1141  }
1142  }
1143  });
1144 };
1145 
1146 function BaseExportConfig() {
1147  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1148  AjaxPost("/db/LoadConfigMetadata", { configName: $("#exportConfigName :selected").text(), user: UserId }, function (metadata) {
1149  if (!metadata.Success) {
1150  UpdateHeader(true, false, "Error loading configuration metadata from database");
1151  return;
1152  }
1153  ExportTarFileName = $("#exportConfigName :selected").text();
1154  var metadataObj = metadata.data;
1155 
1156  // Unselect Everything!
1157  var tag = $("#filePicker");
1158  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1159  for (var r in rows) {
1160  if (rows.hasOwnProperty(r)) {
1161  var thisRow = rows[r];
1162  var isChecked = false;
1163  for (var e in metadataObj.entities) {
1164  if (isChecked) break;
1165  if (metadataObj.entities.hasOwnProperty(e)) {
1166  var entity = metadataObj.entities[e];
1167  if (thisRow.name === entity.collection) {
1168  for (var r in thisRow.records) {
1169  if (thisRow.records.hasOwnProperty(r)) {
1170  var record = thisRow.records[r];
1171  if (record.id === entity.collection + entity.name) {
1172  tag.find("#grid").jqxTreeGrid("updateRow", record.uid, { name: entity.name, version: entity.version });
1173  tag.find("#grid").jqxTreeGrid("checkRow", record.uid);
1174  isChecked = true;
1175  break;
1176  }
1177  }
1178  }
1179  }
1180  }
1181  }
1182  if (!isChecked && thisRow.checked) {
1183  tag.find("#grid").jqxTreeGrid("uncheckRow", thisRow.uid);
1184  }
1185  }
1186  }
1187 
1188  });
1189 };
1190 
1191 function SaveNewConfig() {
1192  console.log("Saving New Configuration");
1193  var tag = $("#configurationPicker");
1194  var rows = tag.find("#grid").jqxTreeGrid("getCheckedRows");
1195  var configObj = {
1196  entities: []
1197  };
1198 
1199  for (var r in rows) {
1200  if (rows.hasOwnProperty(r) &&
1201  (typeof rows[r].collection !== "undefined" && rows[r].collection.length > 0) &&
1202  (typeof rows[r].version !== "undefined" && rows[r].version.length > 0)) {
1203  configObj.entities.push({ name: rows[r].name, version: rows[r].version, collection: rows[r].collection });
1204  }
1205  }
1206 
1207  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1208  AjaxPost("/db/MakeNewConfig", { user: UserId, config: JSON.stringify(configObj), name: $("#newConfigName").val() }, function (retval) {
1209  if (retval.Success) {
1210  $("#newConfig").collapsible("option", "collapsed", true);
1211  GetConfigList();
1212  } else {
1213  UpdateHeader(true, false, "MakeNewConfig operation failed.");
1214  }
1215  });
1216 };
1217 
1218 function ExportFiles() {
1219  console.log("Exporting Files");
1220  var tag = $("#filePicker");
1221  var rows = tag.find("#grid").jqxTreeGrid("getCheckedRows");
1222  var configObj = {
1223  entities: []
1224  };
1225 
1226  for (var r in rows) {
1227  if (rows.hasOwnProperty(r) &&
1228  (typeof rows[r].collection !== "undefined" && rows[r].collection.length > 0) &&
1229  (typeof rows[r].version !== "undefined" && rows[r].version.length > 0)) {
1230  configObj.entities.push({ name: rows[r].name, version: rows[r].version, collection: rows[r].collection });
1231  }
1232  }
1233 
1234  //http://stackoverflow.com/questions/16086162/handle-file-download-from-ajax-post
1235  var xhr = new XMLHttpRequest();
1236  xhr.open("POST", "/db/DownloadConfigurationFile", true);
1237  xhr.responseType = "arraybuffer";
1238  xhr.onload = function () {
1239  if (this.status === 200) {
1240  var filename = "";
1241  var disposition = xhr.getResponseHeader("Content-Disposition");
1242  if (disposition && disposition.indexOf("attachment") !== -1) {
1243  var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
1244  var matches = filenameRegex.exec(disposition);
1245  if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, "");
1246  }
1247  var type = xhr.getResponseHeader("Content-Type");
1248 
1249  var blob = new Blob([this.response], { type: type });
1250  if (typeof window.navigator.msSaveBlob !== "undefined") {
1251  // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
1252  window.navigator.msSaveBlob(blob, filename);
1253  } else {
1254  var url = window.URL || window.webkitURL;
1255  var downloadUrl = url.createObjectURL(blob);
1256 
1257  if (filename) {
1258  // use HTML5 a[download] attribute to specify filename
1259  var a = document.createElement("a");
1260  // safari doesn't support this yet
1261  if (typeof a.download === "undefined") {
1262  window.location = downloadUrl;
1263  } else {
1264  a.href = downloadUrl;
1265  a.download = filename;
1266  document.body.appendChild(a);
1267  a.click();
1268  }
1269  } else {
1270  window.location = downloadUrl;
1271  }
1272 
1273  setTimeout(function () { url.revokeObjectURL(downloadUrl); }, 100); // cleanup
1274  }
1275  } else if (this.status === 500) {
1276  UpdateHeader(true, false, "An error occurred on the server. Contact an expert if the situation persists");
1277  }
1278  };
1279  xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
1280  xhr.send($.param({ user: UserId, config: JSON.stringify(configObj), tarFileName: ExportTarFileName, type: $("#exportFileFormat :selected").val() }));
1281  ExportTarFileName = "export";
1282 };
1283 
1284 function AddRowToFileUploader() {
1285  $("#fileUploader").find("#grid").jqxTreeGrid("addRow", null, {});
1286 }
1287 
1288 function SetupFileUploadTable(tag) {
1289  var displayColumns = [
1290  {
1291  text: "File",
1292  dataField: "fileName",
1293  editable: true,
1294  cellClassName: CellClass,
1295  columntype: "template",
1296  initEditor: function (row, cellvalue, editor) {
1297  if (cellvalue === "" || cellvalue === "undefined" || !cellvalue) {
1298  editor.html("<input type=\"file\" id=\"fileName\"/>");
1299  } else {
1300  editor.text(cellvalue);
1301  }
1302  },
1303  getEditorValue: function (row, cellvalue, editor) {
1304  // return the editor's value.
1305  if (cellvalue === "" || cellvalue === "undefined" || !cellvalue) {
1306  var fileName = editor.find("#fileName")[0];
1307  UploadFiles[row] = fileName.files[0];
1308  return fileName.files[0].name;
1309  }
1310  return cellvalue;
1311  }
1312  },
1313  {
1314  text: "File Type",
1315  dataField: "fileType",
1316  editable: true,
1317  cellClassName: CellClass,
1318  columntype: "template",
1319  initEditor: function (row, cellvalue, editor) {
1320  // ReSharper disable once InconsistentNaming
1321  var versionsAdapter = new $.jqx.dataAdapter({ datatype: "array", datafields: [{ name: "name", type: "string" }, { name: "value", type: "string" }], localdata: [{ name: "FHiCL", value: "fhicl" }, { name: "JSON", value: "json" }] }, { autoBind: true });
1322  editor.jqxDropDownList({ source: versionsAdapter, displayMember: "name", valueMember: "value", selectedIndex: 0 }).jqxDropDownList("refresh");
1323  // set the editor's current value. The callback is called each time the editor is displayed.
1324  editor.jqxDropDownList("selectItem", cellvalue);
1325  },
1326  getEditorValue: function (row, cellvalue, editor) {
1327  // return the editor's value.
1328  return editor.val();
1329  }
1330  },
1331  {
1332  text: "Collection",
1333  dataField: "collection",
1334  editable: true,
1335  cellClassName: CellClass
1336  },
1337  {
1338  text: "Entity Name",
1339  dataField: "entity",
1340  editable: true,
1341  cellClassName: CellClass
1342  },
1343  {
1344  text: "Version",
1345  dataField: "version",
1346  editable: true,
1347  cellClassName: CellClass
1348  },
1349  {
1350  text: "Remove", cellsAlign: "center", align: "center", columnType: "none", editable: false, sortable: false, dataField: null, cellsRenderer: function (row) {
1351  // render custom column.
1352  return "<button data-row='" + row + "' class='removeButtons' onclick=''>Remove</button>";
1353  }
1354  }
1355  ];
1356 
1357  tag.html("<div id=\"grid\" class=\"jqxTreeGrid\"></div><br>");
1358 
1359  var dataFields = [
1360  { name: "id", type: "string", editable: false, display: false },
1361  { name: "fileName", type: "string", editable: true, display: true },
1362  { name: "fileType", type: "string", editable: true, display: true },
1363  { name: "collection", type: "string", editable: true, display: true },
1364  { name: "entity", type: "string", editable: true, display: true },
1365  { name: "version", type: "string", editable: true, display: true }
1366  ];
1367  var source = {
1368  dataType: "json",
1369  dataFields: dataFields,
1370  id: "id",
1371  hierarchy: {
1372  root: "entities"
1373  },
1374  localData: {}
1375  };
1376  // ReSharper disable once InconsistentNaming
1377  var dataAdapter = new $.jqx.dataAdapter(source);
1378  // create Tree Grid
1379  tag.find("#grid").jqxTreeGrid({
1380  width: "100%",
1381  source: dataAdapter,
1382  sortable: true,
1383  editable: true,
1384  columnsResize: true,
1385  columns: displayColumns,
1386  rendering: function () {
1387  // destroys all buttons.
1388  if ($(".removeButtons").length > 0) {
1389  $(".removeButtons").jqxButton("destroy");
1390  }
1391  },
1392  rendered: function () {
1393  if ($(".removeButtons").length > 0) {
1394  $(".removeButtons").jqxButton();
1395 
1396  var removeClick = function (event) {
1397  // get clicked row.
1398  var rowKey = event.target.getAttribute("data-row");
1399  delete UploadFiles[rowKey];
1400  tag.find("#grid").jqxTreeGrid("deleteRow", rowKey);
1401  };
1402  $(".removeButtons").on("click", function (event) {
1403  removeClick(event);
1404  return false;
1405  });
1406 
1407  }
1408  }
1409  });
1410 }
1411 
1412 function SetupReader(file) {
1413  var f = UploadFiles[file];
1414  var r = new FileReader();
1415  r.fileid = file;
1416  r.onload = function (e) {
1417  var contents = e.target.result;
1418  var thisfile = e.target.fileid;
1419  var row = $("#fileUploader").find("#grid").jqxTreeGrid("getRow", thisfile);
1420  console.log("Calling AjaxPost with parameters: Collection: " +
1421  row.collection +
1422  ", Version: " +
1423  row.version +
1424  ", Entity: " +
1425  row.entity +
1426  ", Type: " +
1427  row.fileType + ", fileName: " + row.fileName + ", file.name: " + f.name);
1428 
1429  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1430  AjaxPost("/db/UploadConfigurationFile",
1431  {
1432  file: contents,
1433  collection: row.collection,
1434  version: row.version,
1435  entity: row.entity,
1436  user: UserId,
1437  type: row.fileType
1438  },
1439  function (res) {
1440  if (!res.Success) {
1441  UpdateHeader(true, false, "File upload failed");
1442  } else {
1443  console.log("Upload: " + row.fileName + " returned: " + JSON.stringify(res));
1444  }
1445  delete UploadFiles[thisfile];
1446  });
1447  };
1448  r.readAsText(f);
1449 }
1450 
1451 function UploadFhiclFile() {
1452  $("#uploadFhiclFileButton").text("Uploading... Please Wait.");
1453  //http://www.htmlgoodies.com/beyond/javascript/read-text-files-using-the-javascript-filereader.html
1454  var didAnything = false;
1455  for (var file in UploadFiles) {
1456  if (UploadFiles.hasOwnProperty(file)) {
1457  didAnything = true;
1458  var f = UploadFiles[file];
1459 
1460  var row = $("#fileUploader").find("#grid").jqxTreeGrid("getRow", file);
1461  if (!f) {
1462  alert("Failed to load file");
1463  continue;
1464  }
1465 
1466  if (row.fileType === "fhicl" && f.name.search(".fcl") === -1) {
1467  alert(f.name + " is not a valid fhicl file.");
1468  continue;
1469  }
1470 
1471  if (row.fileType === "json" && f.name.search(".json") === -1) {
1472  alert(f.name + " is not a valid json file.");
1473  continue;
1474  }
1475 
1476  if ((row.fileType !== "fhicl" &&
1477  row.fileType !== "json") ||
1478  row.collection === "" ||
1479  row.version === "" ||
1480  row.entity === "") {
1481  alert(f.name + " missing needed metadata");
1482  continue;
1483  }
1484 
1485  SetupReader(file);
1486  }
1487  break;
1488  }
1489 
1490  if (!didAnything) {
1491  $("#uploadFile").collapsible("option", "collapsed", true);
1492  $("#uploadFhiclFileButton").text("Store File(s) In Database");
1493  } else {
1494  setTimeout(function () { UploadFhiclFile(); }, 1000);
1495  }
1496 };
1497 
1498 function SetupEntityVersionPicker(tag) {
1499  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1500  AjaxGet("/db/EntitiesAndVersions", function (data) {
1501  console.log(JSON.stringify(data));
1502  if (!data.Success) {
1503  UpdateHeader(true, false, "Error retrieving entities and versions lists. Please contact an expert!");
1504  return;
1505  }
1506  var collectionsObj = data.collections;
1507  var dataObj = [];
1508  var collectionNames = [];
1509  for (var c in collectionsObj) {
1510  if (collectionsObj.hasOwnProperty(c)) {
1511  var collection = [];
1512  var entitiesObj = collectionsObj[c];
1513  for (var e in entitiesObj.entities) {
1514  if (entitiesObj.entities.hasOwnProperty(e)) {
1515  var entity = entitiesObj.entities[e];
1516  var versions = [];
1517  var search = entity.versions.search;
1518  for (var v in search) {
1519  if (search.hasOwnProperty(v)) {
1520  versions.push({ name: search[v].name });
1521  }
1522  }
1523  collection.push({ id: entitiesObj.name + entity.name, name: entity.name, collection: entity.collection, edited: false, version: versions[0].name, versions: versions });
1524  }
1525  }
1526  dataObj.push({ id: entitiesObj.name, name: entitiesObj.name, entities: collection });
1527  collectionNames.push(entitiesObj.name);
1528  }
1529  }
1530 
1531  var displayColumns = [
1532  {
1533  text: "Entity Name",
1534  dataField: "name",
1535  editable: false,
1536  cellClassName: CellClass
1537  },
1538  {
1539  text: "Version",
1540  dataField: "version",
1541  editable: true,
1542  columntype: "template",
1543  initEditor: function (rowKey, cellvalue, editor) {
1544  var row = tag.find("#grid").jqxTreeGrid("getRow", rowKey);
1545  var index1 = -1;
1546  var index2 = -1;
1547  for (var r in dataObj) {
1548  if (dataObj.hasOwnProperty(r)) {
1549  if (row.id.indexOf(dataObj[r].name) === 0) {
1550  index1 = r;
1551  }
1552  }
1553  }
1554  if (index1 >= 0) {
1555  var localData = dataObj[index1];
1556  for (var rr in localData.entities) {
1557  if (localData.entities.hasOwnProperty(rr)) {
1558  if (row.id === localData.entities[rr].id) {
1559  index2 = rr;
1560  }
1561  }
1562  }
1563  if (index2 >= 0) {
1564  var versionsSource = { datatype: "array", datafields: [{ name: "name", type: "string" }], localdata: dataObj[index1].entities[index2].versions };
1565  // ReSharper disable once InconsistentNaming
1566  var versionsAdapter = new $.jqx.dataAdapter(versionsSource, { autoBind: true });
1567  editor.jqxDropDownList({ source: versionsAdapter, displayMember: "name", valueMember: "name", selectedIndex: 0 }).jqxDropDownList("refresh");
1568  }
1569  }
1570  // set the editor's current value. The callback is called each time the editor is displayed.
1571  editor.jqxDropDownList("selectItem", cellvalue);
1572  },
1573  getEditorValue: function (row, cellvalue, editor) {
1574  // return the editor's value.
1575  return editor.val();
1576  },
1577  cellClassName: CellClass
1578  }
1579  ];
1580 
1581  tag.html("<br><button type=\"button\" class=\"miniButton\" id=\"all1\"> Select All </button><button type=\"button\" class=\"miniButton\" id=\"none1\"> Select None </button><br>" +
1582  "<div id=\"grid\" class=\"jqxTreeGrid\"></div><br>" +
1583  "<button type=\"button\" class=\"miniButton\" id=\"all2\"> Select All </button><button type=\"button\" class=\"miniButton\" id=\"none2\"> Select None </button>");
1584 
1585  tag.find("#none1").jqxButton({ height: 30 });
1586  tag.find("#all1").jqxButton({ height: 30 });
1587  tag.find("#none2").jqxButton({ height: 30 });
1588  tag.find("#all2").jqxButton({ height: 30 });
1589 
1590  var dataFields = [
1591  { name: "id", type: "string", editable: false, display: false },
1592  { name: "name", type: "string", editable: false, display: true },
1593  { name: "version", type: "string", editable: true, display: true },
1594  { name: "versions", type: "array", editable: false, display: false },
1595  { name: "edited", type: "boolean", editable: false, display: false },
1596  { name: "entities", type: "array", editable: false, display: false }
1597  ];
1598  var source = {
1599  dataType: "json",
1600  dataFields: dataFields,
1601  id: "id",
1602  hierarchy: {
1603  root: "entities"
1604  },
1605  localData: dataObj
1606  };
1607  // ReSharper disable once InconsistentNaming
1608  var dataAdapter = new $.jqx.dataAdapter(source);
1609  // create Tree Grid
1610  tag.find("#grid").jqxTreeGrid({
1611  width: "100%",
1612  source: dataAdapter,
1613  sortable: true,
1614  editable: true,
1615  columnsResize: true,
1616  checkboxes: true,
1617  hierarchicalCheckboxes: true,
1618  columns: displayColumns
1619  });
1620  for (var n in collectionNames) {
1621  if (collectionNames.hasOwnProperty(n)) {
1622  tag.find("#grid").jqxTreeGrid("lockRow", collectionNames[n]);
1623  }
1624  }
1625 
1626 
1627 
1628  tag.find("#none1").mousedown(function () {
1629  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1630  for (var r in rows) {
1631  if (rows.hasOwnProperty(r)) {
1632  if (rows[r].checked) {
1633  tag.find("#grid").jqxTreeGrid("uncheckRow", rows[r].uid);
1634  }
1635  }
1636  }
1637  });
1638  tag.find("#all1").mousedown(function () {
1639  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1640  for (var r in rows) {
1641  if (rows.hasOwnProperty(r)) {
1642  if (!rows[r].checked) {
1643  tag.find("#grid").jqxTreeGrid("checkRow", rows[r].uid);
1644  }
1645  }
1646  }
1647  });
1648  tag.find("#none2").mousedown(function () {
1649  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1650  for (var r in rows) {
1651  if (rows.hasOwnProperty(r)) {
1652  if (rows[r].checked) {
1653  tag.find("#grid").jqxTreeGrid("uncheckRow", rows[r].uid);
1654  }
1655  }
1656  }
1657  });
1658 
1659  tag.find("#all2").mousedown(function () {
1660  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1661  for (var r in rows) {
1662  if (rows.hasOwnProperty(r)) {
1663  if (!rows[r].checked) {
1664  tag.find("#grid").jqxTreeGrid("checkRow", rows[r].uid);
1665  }
1666  }
1667  }
1668  });
1669  });
1670 }
1671 
1672 function SaveConfig() {
1673  console.log("Saving Configuration Changes");
1674  var files = [];
1675  $(".file-tab.editedValue a")
1676  .each(function () {
1677  var log = $("#changes", $(this).attr("href")).val();
1678  var collection = $("#metadataCollection", $(this).attr("href")).val();
1679  var entities = [];
1680  var rows = $("#metadataEntities", $(this).attr("href")).jqxTreeGrid('getRows');
1681  for (var i = 0; i < rows.length; i++) {
1682  // get a row.
1683  entities.push(rows[i].name);
1684  }
1685  var version = $("#metadataVersion", $(this).attr("href")).val();
1686  files.push({ entities: entities, changelog: log, collection: collection, version: version });
1687  });
1688  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1689  AjaxPost("/db/saveConfig",
1690  {
1691  oldConfigName: CurrentNamedConfig,
1692  newConfigName: $("#configName").val(),
1693  files: files,
1694  user: UserId
1695  },
1696  function (res) {
1697  if (res !== null && res !== undefined && res.Success) {
1698  UpdateHeader(false, false, "Configuration Saved.");
1699  GetConfigList();
1700  } else {
1701  UpdateHeader(true, false, "Failed to save configuration!");
1702  }
1703  });
1704  EditedValues = {};
1705 };
1706 
1707 function DiscardConfig() {
1708  console.log("Discarding Configuration Changes");
1709  var files = [];
1710  $(".file-tab.editedValue").each(function () {
1711  files.push({ name: $(this).text() });
1712  });
1713  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1714  AjaxPost("/db/discardConfig", { configName: CurrentNamedConfig, files: files, user: UserId }, function (res) {
1715  if (res.Success) {
1716  GetConfigList();
1717  } else {
1718  UpdateHeader(true, false, "Failed to discard configuration. Make sure you have a valid configuration selected.\nIf this problem persists, call an expert.");
1719  }
1720  });
1721  EditedValues = {};
1722 };
1723 
1724 function GetDbConfig() {
1725  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1726  AjaxGet("/db/getDbConfig",
1727  function (data) {
1728  if (data.Success) {
1729  $("#dbType").val(data.dbprovider).trigger("change");
1730  $("#baseDir").val(data.baseDir);
1731  $("#databases").html(data.data.join("")).trigger("create").selectmenu("refresh");
1732  var database = GetUrlParameter("database");
1733  if (database !== undefined && database !== "") {
1734  $("#databases").val(database).trigger("change");
1735  } else {
1736  $("#databases").val(data.instanceName).trigger("change");
1737  }
1738  }
1739  GetConfigList();
1740  });
1741 }
1742 
1743 function UpdateDbConfig() {
1744  var config = {
1745  dbprovider: $("#dbType").val(),
1746  baseDir: $("#baseDir").val(),
1747  instanceName: $("#databases").val()
1748  };
1749  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1750  AjaxPost("/db/updateDbConfig",
1751  config,
1752  function (res) {
1753  if (res.Success) {
1754  GetDbConfig();
1755  } else {
1756  UpdateHeader(true,
1757  false,
1758  "Failed to update Database Module Configuration. Please See server logs for more details.");
1759  }
1760  }
1761  );
1762 }
1763 
1764 function MakeNewDatabase() {
1765  var newDBName = { name: $("#newDatabaseName").val() };
1766  AjaxPost("/db/makeNewDBInstance",
1767  newDBName, function (res) {
1768  if (res.Success) {
1769  GetDbConfig();
1770  } else {
1771  UpdateHeader(true, false, "Failed to create new Database Instance. Please see server logs for more details.");
1772  }
1773  }
1774  );
1775 }
1776 
1777 function RegisterButtonFunctions() {
1778  $("#loadConfigButton").on("click", function () { LoadConfig(); });
1779  $("#getConfigListButton").on("click", function () { GetConfigList(); });
1780  $("#saveConfigButton").on("click", function () { SaveConfig(); });
1781  $("#discardConfigButton").on("click", function () { DiscardConfig(); });
1782  $("#searchCurrentConfigButton").on("click", function () { SearchCurrentConfig(); });
1783  $("#baseConfigButton").on("click", function () { BaseConfig(); });
1784  $("#saveNewConfigButton").on("click", function () { SaveNewConfig(); });
1785  $("#baseExportConfigButton").on("click", function () { BaseExportConfig(); });
1786  $("#exportFilesButton").on("click", function () { ExportFiles(); });
1787  $("#addRowToFileUploaderButton").on("click", function () { AddRowToFileUploader(); });
1788  $("#uploadFhiclFileButton").on("click", function () { UploadFhiclFile(); });
1789  $("#updateDBConfigButton").on("click", function () { UpdateDbConfig(); });
1790  $("#createNewDatabaseButton").on("click", function () { MakeNewDatabase(); });
1791 }
1792 
1793 (function ($, sr) {
1794 
1795  // debouncing function from John Hann
1796  // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
1797  var debounce = function (func, threshold, execAsap) {
1798  var timeout;
1799 
1800  return function debounced() {
1801  var obj = this, args = arguments;
1802  function delayed() {
1803  if (!execAsap)
1804  func.apply(obj, args);
1805  timeout = null;
1806  };
1807 
1808  if (timeout)
1809  clearTimeout(timeout);
1810  else if (execAsap)
1811  func.apply(obj, args);
1812 
1813  timeout = setTimeout(delayed, threshold || 100);
1814  };
1815  };
1816  // smartresize
1817  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1818  jQuery.fn[sr] = function (fn) { return fn ? this.bind("resize", debounce(fn)) : this.trigger(sr); };
1819 
1820 })(jQuery, "smartresize");
1821 
1822 $(document).ready(function () {
1823  DefaultColor = $("#header").css("background-color");
1824  DefaultShadow = $("#header").css("text-shadow");
1825 
1826  $.get("/db/Tables.html", function (data) {
1827  TableHtml = data;
1828  });
1829 
1830  $.get("/db/FileInfo.html", function (data) {
1831  InfoHtml = data;
1832  });
1833 
1834  $.get("/db/TreeGrid.html", function (data) {
1835  TreeGridHtml = data;
1836  });
1837 
1838  $.get("/db/Dialog.html", function (data) { DialogContentHtml = data; });
1839 
1840  $(".configInfo-tab").on("click", function () {
1841  ResizeTextAreas();
1842  });
1843 
1844  RegisterTabFunctions();
1845  $(".tabs #tab1").show().siblings().hide();
1846 
1847  $(".triggersModified").change(function () {
1848  UpdateHeader(false, true, "There are pending unsaved changes. Please save or discard before closing the editor!");
1849  });
1850 
1851  $(window).smartresize(function () {
1852  $(".jqxTreeGrid").jqxTreeGrid({ width: "100%" }).jqxTreeGrid("refresh");
1853  });
1854 
1855  $("#newConfig").on("collapsibleexpand", function () {
1856  SetupEntityVersionPicker($("#configurationPicker"));
1857  });
1858 
1859  $("#exportFile").on("collapsibleexpand", function () {
1860  SetupEntityVersionPicker($("#filePicker"));
1861  });
1862 
1863  $("#uploadFile")
1864  .on("collapsibleexpand",
1865  function () {
1866  SetupFileUploadTable($("#fileUploader"));
1867  });
1868 
1869  $("#dbType").on("change", function () {
1870  var dbtype = $("#dbType").val();
1871  if (dbtype === "filesystem") {
1872  $("#filesystemdbConfig").show();
1873  $("#mongodbConfig").hide();
1874  } else if (dbtype === "mongo") {
1875  $("#filesystemdbConfig").hide();
1876  $("#mongodbConfig").show();
1877  }
1878  }).trigger("change");
1879 
1880  $("#databases").on("change", function () {
1881  var instanceName = $("#databases").val();
1882  if (instanceName === "NONE") {
1883  $("#newDatabaseFS").show();
1884  } else {
1885  $("#newDatabaseFS").hide();
1886  UpdateDbConfig();
1887  }
1888  }).trigger("change");
1889 
1890  GetDbConfig();
1891 
1892  RegisterButtonFunctions();
1893 });