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