artdaq_node_server  v1_00_09
 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 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  do {
555  if ((parent === null || parent === undefined) && currentName.indexOf("___") >= 0) {
556  var parentSelector = tag.selector.split(" ").slice(0, -1).join(" ");
557  var parentRow = $(parentSelector + " #treeGrid").jqxTreeGrid("getRow", currentName.slice(0, -4));
558  parent = parentRow[0];
559  }
560  if (parent !== null && parent !== undefined) {
561  currentName = parent.name;
562  path = parent.name + "/" + path;
563  parent = parent.parent;
564  }
565  } while (parent !== null && parent !== undefined);
566 
567  if (CurrentCollection && CurrentEntity) {
568  path = CurrentCollection + "/" + CurrentEntity + "/" + path;
569  } else {
570  var pathTmp = path.split('/');
571  CurrentCollection = pathTmp.shift();
572  CurrentEntity = pathTmp.shift();
573  }
574  console.log("Path is " + path);
575 
576  var columnDataField = args.dataField;
577  // column name
578  var columnName = args.dataField;
579  for (var i in args.owner._columns) {
580  if (args.owner._columns.hasOwnProperty(i)) {
581  if (args.owner._columns[i].dataField === columnName) {
582  columnName = args.owner._columns[i].text;
583  break;
584  }
585  }
586  }
587  EditedValues[CurrentCollection + "/" + CurrentEntity + "/" + rowKey] = true;
588  $("li.active :visible").parent().addClass("editedValue");
589  // cell's value.
590  var value = args.value;
591  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
592  AjaxPost("/db/Update", {
593  configName: CurrentNamedConfig,
594  table: path,
595  column: columnDataField,
596  id: rowData.id,
597  name: rowData.name,
598  value: value,
599  user: UserId
600  }, function (retval) {
601  if (retval.Success) {
602  var now = new Date;
603  var selector = $("#changes", $(".file-tab.active a").attr("href"));
604  selector.val(now.toISOString() + ": Edit - File: " + CurrentCollection + "/" + CurrentEntity + ", Name: " + rowData.name + ", Column: " + columnName + ", Value: " + value + "\n" + selector.val());
605  $("#masterChanges").val(now.toISOString() + ": Edit - File: " + CurrentCollection + "/" + CurrentEntity + ", Name: " + rowData.name + ", Column: " + columnName + ", Value: " + value + "\n" + $("#masterChanges").val());
606  UpdateHeader(false, true, "There are pending unsaved changes. Please save or discard before closing the editor!");
607  } else {
608  UpdateHeader(true, false, "Sending Update to server failed");
609  }
610  });
611  });
612 };
613 
614 function CellClass(row, dataField, cellText, rowData) {
615  var edited = false;
616  for (var val in EditedValues) {
617  if (EditedValues.hasOwnProperty(val)) {
618  if (val === CurrentCollection + "/" + CurrentEntity + "/" + rowData.uid) {
619  edited = EditedValues[val];
620  break;
621  }
622  }
623  }
624  if (edited) {
625  return "editedValue";
626  }
627  return "value";
628 };
629 
630 function TranslateColumns(columns) {
631  var displayColumns = [];
632 
633  for (var c in columns) {
634  if (columns.hasOwnProperty(c)) {
635  var title = columns[c].title;
636  if (title === undefined || title === null || title.length === 0) {
637  title = columns[c].name.charAt(0).toUpperCase() + columns[c].name.slice(1);
638  }
639  if (columns[c].type === "string" && columns[c].display) {
640  displayColumns.push({
641  text: title,
642  dataField: columns[c].name,
643  editable: columns[c].editable,
644  columnGroup: columns[c].columnGroup,
645  createEditor: CreateRowEditor,
646  initEditor: InitRowEditor,
647  getEditorValue: GetRowEditorValue,
648  cellClassName: CellClass
649  });
650  } else if (columns[c].type === "number" && columns[c].display) {
651  columns[c].type = "string";
652  columns[c].dataType = "number";
653  displayColumns.push({
654  text: title,
655  dataField: columns[c].name,
656  editable: columns[c].editable,
657  columnGroup: columns[c].columnGroup,
658  createEditor: CreateRowEditor,
659  initEditor: InitRowEditor,
660  getEditorValue: GetRowEditorValue,
661  cellClassName: CellClass
662  });
663  }
664  }
665  }
666  return displayColumns;
667 }
668 
669 function LoadTable(tag) {
670  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
671  AjaxPost("/db/GetData", { configName: CurrentNamedConfig, entity: CurrentEntity, collection: CurrentCollection, user: UserId }, function (data) {
672  if (!data.Success) {
673  UpdateHeader(true, false, "Fetch of data from database failed");
674  return;
675  }
676  var columns = data.data.columns;
677 
678  MakeTreeGrid(tag, columns, data.data.children); //.trigger('create');
679 
680  });
681 };
682 
683 function SetupConfigVersionPicker(element, target) {
684 
685  element.on("change", function () {
686  var thisConfig = element.find(":selected").text();
687  var configVersionHtml = "";
688  for (var version in ConfigVersionData[thisConfig]) {
689  if (ConfigVersionData[thisConfig].hasOwnProperty(version)) {
690  configVersionHtml += "<option name=\"" + ConfigVersionData[thisConfig][version].name
691  + "\" value=" + ConfigVersionData[thisConfig][version].data + ">" + ConfigVersionData[thisConfig][version].version + "</option>";
692  }
693  }
694  $("#" + target).html(configVersionHtml).trigger("create").selectmenu("refresh");
695  $("#" + target).html($("#" + target + " option").sort(function (a, b) {
696  return parseInt(a.text) === parseInt(b.text) ? 0 : parseInt(a.text) > parseInt(b.text) ? -1 : 1;
697  }));
698  var option = $("#" + target).find('option:eq(0)');
699  option.prop('selected', true);
700 $("#"+target).trigger("create").selectmenu("refresh");
701  });
702 }
703 
704 function GetConfigList() {
705  UpdateHeader(false, false, "");
706  $("#masterChanges").val("");
707  ResizeTextAreas();
708  for (var i = 2; i <= LastTabId; i++) {
709  $("#tab" + i).remove();
710  $("#tablink" + i).remove();
711  }
712 
713  $("#reloadConfigsButton").text("Reload Configurations");
714 
715  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
716  AjaxPost("/db/NamedConfigs", { configFilter: $("#configurationFilter").val(), user: UserId }, function (data) {
717  if (!data.Success) {
718  UpdateHeader(true, false, "Error retrieving Configuration list. Please contact an expert!");
719  return;
720  }
721  var configsHtml = "";
722  for (var name in data.data) {
723  if (data.data.hasOwnProperty(name)) {
724  configsHtml += "<option>" + name + "</option>";
725  ConfigVersionData[name] = data.data[name];
726  }
727  }
728  $("#configs").html(configsHtml).trigger("create").selectmenu("refresh");
729  $("#oldConfigName").html(configsHtml).trigger("create").selectmenu("refresh");
730  $("#exportConfigName").html(configsHtml).trigger("create").selectmenu("refresh");
731 
732  SetupConfigVersionPicker($("#configs"), "configversions");
733  $("#configs").trigger("change");
734  SetupConfigVersionPicker($("#oldConfigName"), "oldConfigVersion");
735  $("#oldConfigName").trigger("change");
736  SetupConfigVersionPicker($("#exportConfigName"), "exportConfigVersion");
737  $("#exportConfigName").trigger("change");
738 
739  var config = GetUrlParameter("configs");
740  if (config !== undefined) {
741  $("#configs").val(config);
742  }
743  });
744  $("#configLoad").collapsible("option", "disabled", false).collapsible("option", "collapsed", false);
745  $("#configSave").collapsible("option", "disabled", true).collapsible("option", "collapsed", true);
746  $("#configMetadata").collapsible("option", "disabled", true).collapsible("option", "collapsed", true);
747  $("#searchConfig").collapsible("option", "disabled", false);
748  $("#exportFile").collapsible("option", "collapsed", true);
749  $("#newConfig").collapsible("option", "collapsed", true);
750 };
751 
752 function RegisterTabFunctions() {
753  $(".file-tab a").off();
754  $(".collection-tab a").off();
755  $(".tabs .tab-links a").off().on("click", function (e) {
756  var currentAttrValue = $(this).attr("href");
757  var tab = $(this).parent();
758  var div = tab.parent();
759  div.scrollLeft(0);
760  var left = Math.floor(tab.position().left - 20);
761  div.scrollLeft(left);
762  // Show/Hide Tabs
763  $(".tabs " + currentAttrValue).show().siblings().hide();
764 
765  // Change/remove current tab to active
766  $(this).parent("li").addClass("active").siblings().removeClass("active");
767 
768  e.preventDefault();
769  });
770  $(".collection-tab a").on("click", function () {
771  CurrentCollection = $(this).text();
772  });
773  $(".file-tab a").on("click", function () {
774  var fileName = $(this).text();
775  CurrentEntity = fileName;
776  LoadFile($(this).attr("href"));
777  ResizeTextAreas();
778  });
779 };
780 
781 function GetUrlParameter(sParam) {
782  var sUrlVariables = window.location.search.substring(1).split("&");
783  //console.log("sUrlVariables: " + sUrlVariables);
784  for (var i = 0; i < sUrlVariables.length; i++) {
785  var sParameterName = sUrlVariables[i].split("=");
786  //console.log("Comparing " + sParam + " to " + sParameterName[0] + " (" + sParameterName[1] + ")");
787  if (sParameterName[0].search(sParam) >= 0) {
788  //console.log("Returning " + sParameterName[1]);
789  return sParameterName[1];
790  }
791  }
792  return "";
793 };
794 
795 function LoadConfigMetadata() {
796  console.log("Loading configuration metadata");
797  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
798  AjaxPost("/db/LoadConfigMetadata", { configName: CurrentNamedConfig, user: UserId }, function (metadata) {
799  if (!metadata.Success) {
800  UpdateHeader(true, false, "Error loading configuration metadata from database");
801  return;
802  }
803  var metadataObj = metadata.data;
804  //console.log(metadata);
805 
806  var displayColumns = [
807  {
808  text: "Entity Name",
809  dataField: "name",
810  editable: false
811  },
812  {
813  text: "Collection Name",
814  dataField: "collection",
815  editable: false
816  },
817  {
818  text: "Version",
819  dataField: "version",
820  editable: false
821  },
822  {
823  text: "Edit", cellsAlign: "center", align: "center", columnType: "none", editable: false, sortable: false, dataField: null, cellsRenderer: function (row) {
824  // render custom column.
825  return "<button data-row='" + row + "' class='editButtons' onclick=''>Edit</button>";
826  }
827  }
828  ];
829  var dataFields = [
830  { name: "name", type: "string", editable: false, display: true },
831  { name: "collection", type: "string", editable: false, display: true },
832  { name: "file", type: "string", editable: false, display: false },
833  { name: "version", type: "string", editable: false, display: true }
834  ];
835  var source = {
836  dataType: "json",
837  dataFields: dataFields,
838  id: "file",
839  hierarchy: {
840  root: "values"
841  },
842  localData: metadataObj.entities
843  };
844  // ReSharper disable once InconsistentNaming
845  var dataAdapter = new $.jqx.dataAdapter(source);
846  // create Tree Grid
847  var grid = $("#configurationEntities");
848  grid.addClass("jqxTreeGrid").jqxTreeGrid(
849  {
850  width: "100%",
851  source: dataAdapter,
852  editable: false,
853  sortable: true,
854  columnsResize: true,
855  columns: displayColumns,
856  rendering: function () {
857  // destroys all buttons.
858  if ($(".editButtons").length > 0) {
859  $(".editButtons").jqxButton("destroy");
860  }
861  },
862  rendered: function () {
863  if ($(".editButtons").length > 0) {
864  $(".editButtons").jqxButton();
865 
866  var editClick = function (event) {
867  // get clicked row.
868  var rowKey = event.target.getAttribute("data-row");
869  var row = grid.jqxTreeGrid("getRow", rowKey);
870  var collection = row["collection"];
871  var collectionTab = $(".collection-tab[collection-name=\"" + collection + "\"] a");
872  collectionTab.trigger("click");
873  var file = row["name"];
874  var fileNameTab = $(collectionTab[0].hash).find(".file-tab[file-name=\"" + file + "\"] a");
875  fileNameTab.trigger("click");
876  };
877  $(".editButtons").on("click", function (event) {
878  editClick(event);
879  return false;
880  });
881 
882  }
883  }
884  });
885  });
886 };
887 
888 function AddEntityToFile(id) {
889  var newEntityName = $(id + " #newEntityName").val();
890  $(id + " #newEntityName").val("");
891  if (newEntityName !== "") {
892  AjaxPost("/db/AddEntityToFile", { configName: CurrentNamedConfig, entity: CurrentEntity, collection: CurrentCollection, user: UserId, name: newEntityName }, function (res) {
893  if (res.Success) {
894  LoadFile(id);
895  }
896  else {
897  UpdateHeader(true, false, "Error adding Entity to configuration file");
898  }
899  });
900  }
901 }
902 
903 function LoadFile(id) {
904  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
905  AjaxPost("/db/LoadFileMetadata", { configName: CurrentNamedConfig, entity: CurrentEntity, collection: CurrentCollection, user: UserId }, function (metadata) {
906  if (!metadata.Success) {
907  UpdateHeader(true, false, "Error loading file metadata from database");
908  return;
909  }
910  console.log("Loading file metadata");
911  var metadataObj = metadata.data;
912  console.log(metadata);
913  $(id + " #metadataCollection").val(metadataObj.collection);
914  $(id + " #metadataVersion").val(metadataObj.version);
915  $(id + " #changeLog").val(metadataObj.changelog);
916  //$(id + " #addNewEntity").on("click", function () { AddEntityToFile(id); });
917  var displayColumns = [
918  {
919  text: "Name",
920  dataField: "name",
921  editable: false
922  },
923  {
924  text: "Date Assigned",
925  dataField: "assigned",
926  editable: false
927  }
928  ];
929  var dataFields = [
930  { name: "name", type: "string", editable: false, display: true },
931  { name: "assigned", type: "date", editable: false, display: true }
932  ];
933  var entitiesSource = {
934  dataType: "json",
935  dataFields: dataFields,
936  id: "name",
937  hierarchy: {
938  root: "values"
939  },
940  localData: metadataObj.entities
941  };
942  // ReSharper disable once InconsistentNaming
943  var entitiesDataAdapter = new $.jqx.dataAdapter(entitiesSource);
944  // create Tree Grid
945  $(id + " #metadataEntities").addClass("jqxTreeGrid").jqxTreeGrid(
946  {
947  width: "100%",
948  source: entitiesDataAdapter,
949  editable: false,
950  sortable: true,
951  columnsResize: true,
952  columns: displayColumns
953  });
954  var configsSource = {
955  dataType: "json",
956  dataFields: dataFields,
957  id: "name",
958  hierarchy: {
959  root: "values"
960  },
961  localData: metadataObj.configurations
962  };
963  // ReSharper disable once InconsistentNaming
964  var configsDataAdapter = new $.jqx.dataAdapter(configsSource);
965  // create Tree Grid
966  $(id + " #metadataConfigurations").addClass("jqxTreeGrid").jqxTreeGrid(
967  {
968  width: "100%",
969  source: configsDataAdapter,
970  editable: false,
971  sortable: true,
972  columnsResize: true,
973  columns: displayColumns
974  });
975  LoadTable($(id + " #fileTable"));
976  });
977 };
978 
979 function SearchCurrentConfig() {
980  console.log("Searching Configuration");
981 
982  var searchKey = $("#searchKey").val();
983  console.log("SearchKey is " + searchKey);
984  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
985  AjaxPost("/db/SearchLoadedConfig", { configName: CurrentNamedConfig, user: UserId, searchKey: searchKey }, function (results) {
986  if (!results.Success) {
987  UpdateHeader(true, false, "Error searching configuration");
988  return;
989  }
990 
991  MakeTreeGrid($("#searchResults"), results.columns, results.collections);
992  });
993 }
994 
995 function LoadConfig() {
996  console.log("Loading Configuration");
997  $("#masterChanges").val("");
998  UpdateHeader(false, false, "");
999  var selected = $("#configversions").find(":selected");
1000  if (selected.text() === "No Configurations Found" || selected.text() === "Click \"Load Configurations\" To Load Configuration Names") { return; }
1001  CurrentNamedConfig = selected.attr("name");
1002  $("#configName").val(CurrentNamedConfig);
1003  for (var i = 2; i <= LastTabId; i++) {
1004  $("#tab" + i).remove();
1005  $("#tablink" + i).remove();
1006  }
1007  LastTabId = 1;
1008  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1009  AjaxPost("/db/LoadNamedConfig", { configName: CurrentNamedConfig, query: selected.val(), user: UserId }, function (config) {
1010  if (!config.Success) {
1011  UpdateHeader(true, false, "Error loading configuration files from database");
1012  return;
1013  }
1014  ResizeTextAreas();
1015  for (var collection in config.collections) {
1016  if (config.collections.hasOwnProperty(collection)) {
1017  LastTabId++;
1018  var parentTab = LastTabId;
1019  $("#tabLinks").append("<li id=\"tablink" + LastTabId + "\"collection-name=\"" + config.collections[collection].name + "\" tabNum=\"" + LastTabId + "\" class=\"collection-tab\"><a href=\"#tab" + LastTabId + "\">" + config
1020  .collections[collection].name + "</a></li>");
1021  $("#tabContents").append("<div id=tab" + LastTabId + " class=\"tab\"></div>");
1022  $("#tab" + LastTabId).html(TableHtml);
1023 
1024  for (var file in config.collections[collection].files) {
1025  if (config.collections[collection].files.hasOwnProperty(file)) {
1026  var name = config.collections[collection].files[file];
1027 
1028  LastTabId++;
1029  $("#tab" + parentTab + " #tabLinks").append("<li id=\"tablink" + LastTabId + "\" file-name=\"" + name + "\" tabNum=\"" + LastTabId + "\" class=\"file-tab\"><a href=\"#tab" + LastTabId + "\">" + name + "</a></li>");
1030  $("#tab" + parentTab + " #tabContents").append("<div id=tab" + LastTabId + " class=\"tab\"></div>");
1031  $("#tab" + LastTabId).html(InfoHtml).trigger("create");;
1032  }
1033  }
1034  }
1035  }
1036  $("#configLoad").collapsible("option", "disabled", false).collapsible("option", "collapsed", true);
1037  $("#configSave").collapsible("option", "disabled", false).collapsible("option", "collapsed", false);
1038  LoadConfigMetadata();
1039  $("#configMetadata").collapsible("option", "disabled", false).collapsible("option", "collapsed", false);
1040  $("#searchConfig").collapsible("option", "disabled", false);
1041  RegisterTabFunctions();
1042  });
1043 };
1044 
1045 function BaseConfig() {
1046  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1047  AjaxPost("/db/LoadConfigMetadata", { configName: $("#oldConfigName :selected").text(), user: UserId }, function (metadata) {
1048  if (!metadata.Success) {
1049  UpdateHeader(true, false, "Error loading configuration metadata from database");
1050  return;
1051  }
1052  var metadataObj = metadata.data;
1053 
1054  // Unselect Everything!
1055  $("#newConfigName").val($("#oldConfigName :selected").text());
1056  var tag = $("#configurationPicker");
1057  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1058  for (var r in rows) {
1059  if (rows.hasOwnProperty(r)) {
1060  var thisRow = rows[r];
1061  var isChecked = false;
1062  for (var e in metadataObj.entities) {
1063  if (isChecked) break;
1064  if (metadataObj.entities.hasOwnProperty(e)) {
1065  var entity = metadataObj.entities[e];
1066  if (thisRow.name === entity.collection) {
1067  for (var r in thisRow.records) {
1068  if (thisRow.records.hasOwnProperty(r)) {
1069  var record = thisRow.records[r];
1070  if (record.id === entity.collection + entity.name) {
1071  tag.find("#grid").jqxTreeGrid("updateRow", record.uid, { name: entity.name, version: entity.version });
1072  tag.find("#grid").jqxTreeGrid("checkRow", record.uid);
1073  isChecked = true;
1074  break;
1075  }
1076  }
1077  }
1078  }
1079  }
1080  }
1081  if (!isChecked && thisRow.checked) {
1082  tag.find("#grid").jqxTreeGrid("uncheckRow", thisRow.uid);
1083  }
1084  }
1085  }
1086  });
1087 };
1088 
1089 function BaseExportConfig() {
1090  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1091  AjaxPost("/db/LoadConfigMetadata", { configName: $("#exportConfigName :selected").text(), user: UserId }, function (metadata) {
1092  if (!metadata.Success) {
1093  UpdateHeader(true, false, "Error loading configuration metadata from database");
1094  return;
1095  }
1096  ExportTarFileName = $("#exportConfigName :selected").text();
1097  var metadataObj = metadata.data;
1098 
1099  // Unselect Everything!
1100  var tag = $("#filePicker");
1101  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1102  for (var r in rows) {
1103  if (rows.hasOwnProperty(r)) {
1104  var thisRow = rows[r];
1105  var isChecked = false;
1106  for (var e in metadataObj.entities) {
1107  if (isChecked) break;
1108  if (metadataObj.entities.hasOwnProperty(e)) {
1109  var entity = metadataObj.entities[e];
1110  if (thisRow.name === entity.collection) {
1111  for (var r in thisRow.records) {
1112  if (thisRow.records.hasOwnProperty(r)) {
1113  var record = thisRow.records[r];
1114  if (record.id === entity.collection + entity.name) {
1115  tag.find("#grid").jqxTreeGrid("updateRow", record.uid, { name: entity.name, version: entity.version });
1116  tag.find("#grid").jqxTreeGrid("checkRow", record.uid);
1117  isChecked = true;
1118  break;
1119  }
1120  }
1121  }
1122  }
1123  }
1124  }
1125  if (!isChecked && thisRow.checked) {
1126  tag.find("#grid").jqxTreeGrid("uncheckRow", thisRow.uid);
1127  }
1128  }
1129  }
1130 
1131  });
1132 };
1133 
1134 function SaveNewConfig() {
1135  console.log("Saving New Configuration");
1136  var tag = $("#configurationPicker");
1137  var rows = tag.find("#grid").jqxTreeGrid("getCheckedRows");
1138  var configObj = {
1139  entities: []
1140  };
1141 
1142  for (var r in rows) {
1143  if (rows.hasOwnProperty(r) &&
1144  (typeof rows[r].collection !== "undefined" && rows[r].collection.length > 0) &&
1145  (typeof rows[r].version !== "undefined" && rows[r].version.length > 0)) {
1146  configObj.entities.push({ name: rows[r].name, version: rows[r].version, collection: rows[r].collection });
1147  }
1148  }
1149 
1150  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1151  AjaxPost("/db/MakeNewConfig", { user: UserId, config: JSON.stringify(configObj), name: $("#newConfigName").val() }, function (retval) {
1152  if (retval.Success) {
1153  $("#newConfig").collapsible("option", "collapsed", true);
1154  GetConfigList();
1155  } else {
1156  UpdateHeader(true, false, "MakeNewConfig operation failed.");
1157  }
1158  });
1159 };
1160 
1161 function ExportFiles() {
1162  console.log("Exporting Files");
1163  var tag = $("#filePicker");
1164  var rows = tag.find("#grid").jqxTreeGrid("getCheckedRows");
1165  var configObj = {
1166  entities: []
1167  };
1168 
1169  for (var r in rows) {
1170  if (rows.hasOwnProperty(r) &&
1171  (typeof rows[r].collection !== "undefined" && rows[r].collection.length > 0) &&
1172  (typeof rows[r].version !== "undefined" && rows[r].version.length > 0)) {
1173  configObj.entities.push({ name: rows[r].name, version: rows[r].version, collection: rows[r].collection });
1174  }
1175  }
1176 
1177  //http://stackoverflow.com/questions/16086162/handle-file-download-from-ajax-post
1178  var xhr = new XMLHttpRequest();
1179  xhr.open("POST", "/db/DownloadConfigurationFile", true);
1180  xhr.responseType = "arraybuffer";
1181  xhr.onload = function () {
1182  if (this.status === 200) {
1183  var filename = "";
1184  var disposition = xhr.getResponseHeader("Content-Disposition");
1185  if (disposition && disposition.indexOf("attachment") !== -1) {
1186  var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
1187  var matches = filenameRegex.exec(disposition);
1188  if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, "");
1189  }
1190  var type = xhr.getResponseHeader("Content-Type");
1191 
1192  var blob = new Blob([this.response], { type: type });
1193  if (typeof window.navigator.msSaveBlob !== "undefined") {
1194  // 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."
1195  window.navigator.msSaveBlob(blob, filename);
1196  } else {
1197  var url = window.URL || window.webkitURL;
1198  var downloadUrl = url.createObjectURL(blob);
1199 
1200  if (filename) {
1201  // use HTML5 a[download] attribute to specify filename
1202  var a = document.createElement("a");
1203  // safari doesn't support this yet
1204  if (typeof a.download === "undefined") {
1205  window.location = downloadUrl;
1206  } else {
1207  a.href = downloadUrl;
1208  a.download = filename;
1209  document.body.appendChild(a);
1210  a.click();
1211  }
1212  } else {
1213  window.location = downloadUrl;
1214  }
1215 
1216  setTimeout(function () { url.revokeObjectURL(downloadUrl); }, 100); // cleanup
1217  }
1218  } else if (this.status === 500) {
1219  UpdateHeader(true, false, "An error occurred on the server. Contact an expert if the situation persists");
1220  }
1221  };
1222  xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
1223  xhr.send($.param({ user: UserId, config: JSON.stringify(configObj), tarFileName: ExportTarFileName, type: $("#exportFileFormat :selected").val() }));
1224  ExportTarFileName = "export";
1225 };
1226 
1227 function AddRowToFileUploader() {
1228  $("#fileUploader").find("#grid").jqxTreeGrid("addRow", null, {});
1229 }
1230 
1231 function SetupFileUploadTable(tag) {
1232  var displayColumns = [
1233  {
1234  text: "File",
1235  dataField: "fileName",
1236  editable: true,
1237  cellClassName: CellClass,
1238  columntype: "template",
1239  initEditor: function (row, cellvalue, editor) {
1240  if (cellvalue === "" || cellvalue === "undefined" || !cellvalue) {
1241  editor.html("<input type=\"file\" id=\"fileName\"/>");
1242  } else {
1243  editor.text(cellvalue);
1244  }
1245  },
1246  getEditorValue: function (row, cellvalue, editor) {
1247  // return the editor's value.
1248  if (cellvalue === "" || cellvalue === "undefined" || !cellvalue) {
1249  var fileName = editor.find("#fileName")[0];
1250  UploadFiles[row] = fileName.files[0];
1251  return fileName.files[0].name;
1252  }
1253  return cellvalue;
1254  }
1255  },
1256  {
1257  text: "File Type",
1258  dataField: "fileType",
1259  editable: true,
1260  cellClassName: CellClass,
1261  columntype: "template",
1262  initEditor: function (row, cellvalue, editor) {
1263  // ReSharper disable once InconsistentNaming
1264  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 });
1265  editor.jqxDropDownList({ source: versionsAdapter, displayMember: "name", valueMember: "value", selectedIndex: 0 }).jqxDropDownList("refresh");
1266  // set the editor's current value. The callback is called each time the editor is displayed.
1267  editor.jqxDropDownList("selectItem", cellvalue);
1268  },
1269  getEditorValue: function (row, cellvalue, editor) {
1270  // return the editor's value.
1271  return editor.val();
1272  }
1273  },
1274  {
1275  text: "Collection",
1276  dataField: "collection",
1277  editable: true,
1278  cellClassName: CellClass
1279  },
1280  {
1281  text: "Entity Name",
1282  dataField: "entity",
1283  editable: true,
1284  cellClassName: CellClass
1285  },
1286  {
1287  text: "Version",
1288  dataField: "version",
1289  editable: true,
1290  cellClassName: CellClass
1291  },
1292  {
1293  text: "Remove", cellsAlign: "center", align: "center", columnType: "none", editable: false, sortable: false, dataField: null, cellsRenderer: function (row) {
1294  // render custom column.
1295  return "<button data-row='" + row + "' class='removeButtons' onclick=''>Remove</button>";
1296  }
1297  }
1298  ];
1299 
1300  tag.html("<div id=\"grid\" class=\"jqxTreeGrid\"></div><br>");
1301 
1302  var dataFields = [
1303  { name: "id", type: "string", editable: false, display: false },
1304  { name: "fileName", type: "string", editable: true, display: true },
1305  { name: "fileType", type: "string", editable: true, display: true },
1306  { name: "collection", type: "string", editable: true, display: true },
1307  { name: "entity", type: "string", editable: true, display: true },
1308  { name: "version", type: "string", editable: true, display: true }
1309  ];
1310  var source = {
1311  dataType: "json",
1312  dataFields: dataFields,
1313  id: "id",
1314  hierarchy: {
1315  root: "entities"
1316  },
1317  localData: {}
1318  };
1319  // ReSharper disable once InconsistentNaming
1320  var dataAdapter = new $.jqx.dataAdapter(source);
1321  // create Tree Grid
1322  tag.find("#grid").jqxTreeGrid({
1323  width: "100%",
1324  source: dataAdapter,
1325  sortable: true,
1326  editable: true,
1327  columnsResize: true,
1328  columns: displayColumns,
1329  rendering: function () {
1330  // destroys all buttons.
1331  if ($(".removeButtons").length > 0) {
1332  $(".removeButtons").jqxButton("destroy");
1333  }
1334  },
1335  rendered: function () {
1336  if ($(".removeButtons").length > 0) {
1337  $(".removeButtons").jqxButton();
1338 
1339  var removeClick = function (event) {
1340  // get clicked row.
1341  var rowKey = event.target.getAttribute("data-row");
1342  delete UploadFiles[rowKey];
1343  tag.find("#grid").jqxTreeGrid("deleteRow", rowKey);
1344  };
1345  $(".removeButtons").on("click", function (event) {
1346  removeClick(event);
1347  return false;
1348  });
1349 
1350  }
1351  }
1352  });
1353 }
1354 
1355 function SetupReader(file) {
1356  var f = UploadFiles[file];
1357  var r = new FileReader();
1358  r.fileid = file;
1359  r.onload = function (e) {
1360  var contents = e.target.result;
1361  var thisfile = e.target.fileid;
1362  var row = $("#fileUploader").find("#grid").jqxTreeGrid("getRow", thisfile);
1363  console.log("Calling AjaxPost with parameters: Collection: " +
1364  row.collection +
1365  ", Version: " +
1366  row.version +
1367  ", Entity: " +
1368  row.entity +
1369  ", Type: " +
1370  row.fileType + ", fileName: " + row.fileName + ", file.name: " + f.name);
1371 
1372  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1373  AjaxPost("/db/UploadConfigurationFile",
1374  {
1375  file: contents,
1376  collection: row.collection,
1377  version: row.version,
1378  entity: row.entity,
1379  user: UserId,
1380  type: row.fileType
1381  },
1382  function (res) {
1383  if (!res.Success) {
1384  UpdateHeader(true, false, "File upload failed");
1385  } else {
1386  console.log("Upload: " + row.fileName + " returned: " + JSON.stringify(res));
1387  }
1388  delete UploadFiles[thisfile];
1389  });
1390  };
1391  r.readAsText(f);
1392 }
1393 
1394 function UploadFhiclFile() {
1395  $("#uploadFhiclFileButton").text("Uploading... Please Wait.");
1396  //http://www.htmlgoodies.com/beyond/javascript/read-text-files-using-the-javascript-filereader.html
1397  var didAnything = false;
1398  for (var file in UploadFiles) {
1399  if (UploadFiles.hasOwnProperty(file)) {
1400  didAnything = true;
1401  var f = UploadFiles[file];
1402 
1403  var row = $("#fileUploader").find("#grid").jqxTreeGrid("getRow", file);
1404  if (!f) {
1405  alert("Failed to load file");
1406  continue;
1407  }
1408 
1409  if (row.fileType === "fhicl" && f.name.search(".fcl") === -1) {
1410  alert(f.name + " is not a valid fhicl file.");
1411  continue;
1412  }
1413 
1414  if (row.fileType === "json" && f.name.search(".json") === -1) {
1415  alert(f.name + " is not a valid json file.");
1416  continue;
1417  }
1418 
1419  if ((row.fileType !== "fhicl" &&
1420  row.fileType !== "json") ||
1421  row.collection === "" ||
1422  row.version === "" ||
1423  row.entity === "") {
1424  alert(f.name + " missing needed metadata");
1425  continue;
1426  }
1427 
1428  SetupReader(file);
1429  }
1430  break;
1431  }
1432 
1433  if (!didAnything) {
1434  $("#uploadFile").collapsible("option", "collapsed", true);
1435  $("#uploadFhiclFileButton").text("Store File(s) In Database");
1436  } else {
1437  setTimeout(function () { UploadFhiclFile(); }, 1000);
1438  }
1439 };
1440 
1441 function SetupEntityVersionPicker(tag) {
1442  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1443  AjaxGet("/db/EntitiesAndVersions", function (data) {
1444  console.log(JSON.stringify(data));
1445  if (!data.Success) {
1446  UpdateHeader(true, false, "Error retrieving entities and versions lists. Please contact an expert!");
1447  return;
1448  }
1449  var collectionsObj = data.collections;
1450  var dataObj = [];
1451  var collectionNames = [];
1452  for (var c in collectionsObj) {
1453  if (collectionsObj.hasOwnProperty(c)) {
1454  var collection = [];
1455  var entitiesObj = collectionsObj[c];
1456  for (var e in entitiesObj.entities) {
1457  if (entitiesObj.entities.hasOwnProperty(e)) {
1458  var entity = entitiesObj.entities[e];
1459  var versions = [];
1460  var search = entity.versions.search;
1461  for (var v in search) {
1462  if (search.hasOwnProperty(v)) {
1463  versions.push({ name: search[v].name });
1464  }
1465  }
1466  collection.push({ id: entitiesObj.name + entity.name, name: entity.name, collection: entity.collection, edited: false, version: versions[0].name, versions: versions });
1467  }
1468  }
1469  dataObj.push({ id: entitiesObj.name, name: entitiesObj.name, entities: collection });
1470  collectionNames.push(entitiesObj.name);
1471  }
1472  }
1473 
1474  var displayColumns = [
1475  {
1476  text: "Entity Name",
1477  dataField: "name",
1478  editable: false,
1479  cellClassName: CellClass
1480  },
1481  {
1482  text: "Version",
1483  dataField: "version",
1484  editable: true,
1485  columntype: "template",
1486  initEditor: function (rowKey, cellvalue, editor) {
1487  var row = tag.find("#grid").jqxTreeGrid("getRow", rowKey);
1488  var index1 = -1;
1489  var index2 = -1;
1490  for (var r in dataObj) {
1491  if (dataObj.hasOwnProperty(r)) {
1492  if (row.id.indexOf(dataObj[r].name) === 0) {
1493  index1 = r;
1494  }
1495  }
1496  }
1497  if (index1 >= 0) {
1498  var localData = dataObj[index1];
1499  for (var rr in localData.entities) {
1500  if (localData.entities.hasOwnProperty(rr)) {
1501  if (row.id === localData.entities[rr].id) {
1502  index2 = rr;
1503  }
1504  }
1505  }
1506  if (index2 >= 0) {
1507  var versionsSource = { datatype: "array", datafields: [{ name: "name", type: "string" }], localdata: dataObj[index1].entities[index2].versions };
1508  // ReSharper disable once InconsistentNaming
1509  var versionsAdapter = new $.jqx.dataAdapter(versionsSource, { autoBind: true });
1510  editor.jqxDropDownList({ source: versionsAdapter, displayMember: "name", valueMember: "name", selectedIndex: 0 }).jqxDropDownList("refresh");
1511  }
1512  }
1513  // set the editor's current value. The callback is called each time the editor is displayed.
1514  editor.jqxDropDownList("selectItem", cellvalue);
1515  },
1516  getEditorValue: function (row, cellvalue, editor) {
1517  // return the editor's value.
1518  return editor.val();
1519  },
1520  cellClassName: CellClass
1521  }
1522  ];
1523 
1524  tag.html("<br><button type=\"button\" class=\"miniButton\" id=\"all1\"> Select All </button><button type=\"button\" class=\"miniButton\" id=\"none1\"> Select None </button><br>" +
1525  "<div id=\"grid\" class=\"jqxTreeGrid\"></div><br>" +
1526  "<button type=\"button\" class=\"miniButton\" id=\"all2\"> Select All </button><button type=\"button\" class=\"miniButton\" id=\"none2\"> Select None </button>");
1527 
1528  tag.find("#none1").jqxButton({ height: 30 });
1529  tag.find("#all1").jqxButton({ height: 30 });
1530  tag.find("#none2").jqxButton({ height: 30 });
1531  tag.find("#all2").jqxButton({ height: 30 });
1532 
1533  var dataFields = [
1534  { name: "id", type: "string", editable: false, display: false },
1535  { name: "name", type: "string", editable: false, display: true },
1536  { name: "version", type: "string", editable: true, display: true },
1537  { name: "versions", type: "array", editable: false, display: false },
1538  { name: "edited", type: "boolean", editable: false, display: false },
1539  { name: "entities", type: "array", editable: false, display: false }
1540  ];
1541  var source = {
1542  dataType: "json",
1543  dataFields: dataFields,
1544  id: "id",
1545  hierarchy: {
1546  root: "entities"
1547  },
1548  localData: dataObj
1549  };
1550  // ReSharper disable once InconsistentNaming
1551  var dataAdapter = new $.jqx.dataAdapter(source);
1552  // create Tree Grid
1553  tag.find("#grid").jqxTreeGrid({
1554  width: "100%",
1555  source: dataAdapter,
1556  sortable: true,
1557  editable: true,
1558  columnsResize: true,
1559  checkboxes: true,
1560  hierarchicalCheckboxes: true,
1561  columns: displayColumns
1562  });
1563  for (var n in collectionNames) {
1564  if (collectionNames.hasOwnProperty(n)) {
1565  tag.find("#grid").jqxTreeGrid("lockRow", collectionNames[n]);
1566  }
1567  }
1568 
1569 
1570 
1571  tag.find("#none1").mousedown(function () {
1572  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1573  for (var r in rows) {
1574  if (rows.hasOwnProperty(r)) {
1575  if (rows[r].checked) {
1576  tag.find("#grid").jqxTreeGrid("uncheckRow", rows[r].uid);
1577  }
1578  }
1579  }
1580  });
1581  tag.find("#all1").mousedown(function () {
1582  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1583  for (var r in rows) {
1584  if (rows.hasOwnProperty(r)) {
1585  if (!rows[r].checked) {
1586  tag.find("#grid").jqxTreeGrid("checkRow", rows[r].uid);
1587  }
1588  }
1589  }
1590  });
1591  tag.find("#none2").mousedown(function () {
1592  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1593  for (var r in rows) {
1594  if (rows.hasOwnProperty(r)) {
1595  if (rows[r].checked) {
1596  tag.find("#grid").jqxTreeGrid("uncheckRow", rows[r].uid);
1597  }
1598  }
1599  }
1600  });
1601 
1602  tag.find("#all2").mousedown(function () {
1603  var rows = tag.find("#grid").jqxTreeGrid("getRows");
1604  for (var r in rows) {
1605  if (rows.hasOwnProperty(r)) {
1606  if (!rows[r].checked) {
1607  tag.find("#grid").jqxTreeGrid("checkRow", rows[r].uid);
1608  }
1609  }
1610  }
1611  });
1612  });
1613 }
1614 
1615 function SaveConfig() {
1616  console.log("Saving Configuration Changes");
1617  var files = [];
1618  $(".file-tab.editedValue a")
1619  .each(function () {
1620  var log = $("#changes", $(this).attr("href")).val();
1621  var collection = $("#metadataCollection", $(this).attr("href")).val();
1622  var entities = [];
1623  var rows = $("#metadataEntities", $(this).attr("href")).jqxTreeGrid('getRows');
1624  for (var i = 0; i < rows.length; i++) {
1625  // get a row.
1626  entities.push(rows[i].name);
1627  }
1628  var version = $("#metadataVersion", $(this).attr("href")).val();
1629  files.push({ entities: entities, changelog: log, collection: collection, version: version });
1630  });
1631  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1632  AjaxPost("/db/saveConfig",
1633  {
1634  oldConfigName: CurrentNamedConfig,
1635  newConfigName: $("#configName").val(),
1636  files: files,
1637  user: UserId
1638  },
1639  function (res) {
1640  if (res !== null && res !== undefined && res.Success) {
1641  UpdateHeader(false, false, "Configuration Saved.");
1642  GetConfigList();
1643  } else {
1644  UpdateHeader(true, false, "Failed to save configuration!");
1645  }
1646  });
1647  EditedValues = {};
1648 };
1649 
1650 function DiscardConfig() {
1651  console.log("Discarding Configuration Changes");
1652  var files = [];
1653  $(".file-tab.editedValue").each(function () {
1654  files.push({ name: $(this).text() });
1655  });
1656  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1657  AjaxPost("/db/discardConfig", { configName: CurrentNamedConfig, files: files, user: UserId }, function (res) {
1658  if (res.Success) {
1659  GetConfigList();
1660  } else {
1661  UpdateHeader(true, false, "Failed to discard configuration. Make sure you have a valid configuration selected.\nIf this problem persists, call an expert.");
1662  }
1663  });
1664  EditedValues = {};
1665 };
1666 
1667 function GetDbConfig() {
1668  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1669  AjaxGet("/db/getDbConfig",
1670  function (data) {
1671  if (data.Success) {
1672  $("#dbType").val(data.dbprovider).trigger("change");
1673  $("#baseDir").val(data.baseDir);
1674  $("#databases").html(data.data.join("")).trigger("create").selectmenu("refresh");
1675  var database = GetUrlParameter("database");
1676  if (database !== undefined && database !== "") {
1677  $("#databases").val(database).trigger("change");
1678  } else {
1679  $("#databases").val(data.instanceName).trigger("change");
1680  }
1681  }
1682  GetConfigList();
1683  });
1684 }
1685 
1686 function UpdateDbConfig() {
1687  var config = {
1688  dbprovider: $("#dbType").val(),
1689  baseDir: $("#baseDir").val(),
1690  instanceName: $("#databases").val()
1691  };
1692  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1693  AjaxPost("/db/updateDbConfig",
1694  config,
1695  function (res) {
1696  if (res.Success) {
1697  GetDbConfig();
1698  } else {
1699  UpdateHeader(true,
1700  false,
1701  "Failed to update Database Module Configuration. Please See server logs for more details.");
1702  }
1703  }
1704  );
1705 }
1706 
1707 function MakeNewDatabase() {
1708  var newDBName = { name: $("#newDatabaseName").val() };
1709  AjaxPost("/db/makeNewDBInstance",
1710  newDBName, function (res) {
1711  if (res.Success) {
1712  GetDbConfig();
1713  } else {
1714  UpdateHeader(true, false, "Failed to create new Database Instance. Please see server logs for more details.");
1715  }
1716  }
1717  );
1718 }
1719 
1720 function RegisterButtonFunctions() {
1721  $("#loadConfigButton").on("click", function () { LoadConfig(); });
1722  $("#getConfigListButton").on("click", function () { GetConfigList(); });
1723  $("#saveConfigButton").on("click", function () { SaveConfig(); });
1724  $("#discardConfigButton").on("click", function () { DiscardConfig(); });
1725  $("#searchCurrentConfigButton").on("click", function () { SearchCurrentConfig(); });
1726  $("#baseConfigButton").on("click", function () { BaseConfig(); });
1727  $("#saveNewConfigButton").on("click", function () { SaveNewConfig(); });
1728  $("#baseExportConfigButton").on("click", function () { BaseExportConfig(); });
1729  $("#exportFilesButton").on("click", function () { ExportFiles(); });
1730  $("#addRowToFileUploaderButton").on("click", function () { AddRowToFileUploader(); });
1731  $("#uploadFhiclFileButton").on("click", function () { UploadFhiclFile(); });
1732  $("#updateDBConfigButton").on("click", function () { UpdateDbConfig(); });
1733  $("#createNewDatabaseButton").on("click", function () { MakeNewDatabase(); });
1734 }
1735 
1736 (function ($, sr) {
1737 
1738  // debouncing function from John Hann
1739  // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
1740  var debounce = function (func, threshold, execAsap) {
1741  var timeout;
1742 
1743  return function debounced() {
1744  var obj = this, args = arguments;
1745  function delayed() {
1746  if (!execAsap)
1747  func.apply(obj, args);
1748  timeout = null;
1749  };
1750 
1751  if (timeout)
1752  clearTimeout(timeout);
1753  else if (execAsap)
1754  func.apply(obj, args);
1755 
1756  timeout = setTimeout(delayed, threshold || 100);
1757  };
1758  };
1759  // smartresize
1760  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
1761  jQuery.fn[sr] = function (fn) { return fn ? this.bind("resize", debounce(fn)) : this.trigger(sr); };
1762 
1763 })(jQuery, "smartresize");
1764 
1765 $(document).ready(function () {
1766  DefaultColor = $("#header").css("background-color");
1767  DefaultShadow = $("#header").css("text-shadow");
1768 
1769  $.get("/db/Tables.html", function (data) {
1770  TableHtml = data;
1771  });
1772 
1773  $.get("/db/FileInfo.html", function (data) {
1774  InfoHtml = data;
1775  });
1776 
1777  $.get("/db/TreeGrid.html", function (data) {
1778  TreeGridHtml = data;
1779  });
1780 
1781  $.get("/db/Dialog.html", function (data) { DialogContentHtml = data; });
1782 
1783  $(".configInfo-tab").on("click", function () {
1784  ResizeTextAreas();
1785  });
1786 
1787  RegisterTabFunctions();
1788  $(".tabs #tab1").show().siblings().hide();
1789 
1790  $(".triggersModified").change(function () {
1791  UpdateHeader(false, true, "There are pending unsaved changes. Please save or discard before closing the editor!");
1792  });
1793 
1794  $(window).smartresize(function () {
1795  $(".jqxTreeGrid").jqxTreeGrid({ width: "100%" }).jqxTreeGrid("refresh");
1796  });
1797 
1798  $("#newConfig").on("collapsibleexpand", function () {
1799  SetupEntityVersionPicker($("#configurationPicker"));
1800  });
1801 
1802  $("#exportFile").on("collapsibleexpand", function () {
1803  SetupEntityVersionPicker($("#filePicker"));
1804  });
1805 
1806  $("#uploadFile")
1807  .on("collapsibleexpand",
1808  function () {
1809  SetupFileUploadTable($("#fileUploader"));
1810  });
1811 
1812  $("#dbType").on("change", function () {
1813  var dbtype = $("#dbType").val();
1814  if (dbtype === "filesystem") {
1815  $("#filesystemdbConfig").show();
1816  $("#mongodbConfig").hide();
1817  } else if (dbtype === "mongo") {
1818  $("#filesystemdbConfig").hide();
1819  $("#mongodbConfig").show();
1820  }
1821  }).trigger("change");
1822 
1823  $("#databases").on("change", function () {
1824  var instanceName = $("#databases").val();
1825  if (instanceName === "NONE") {
1826  $("#newDatabaseFS").show();
1827  } else {
1828  $("#newDatabaseFS").hide();
1829  UpdateDbConfig();
1830  }
1831  }).trigger("change");
1832 
1833  GetDbConfig();
1834 
1835  RegisterButtonFunctions();
1836 });