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