diff --git a/annotation/.classpath b/annotation/.classpath index fae1a2b37d5e3386c9651caedb78b9bd107715bd..71a86ecfe6e0f3ee403397c37963606b5852c085 100644 --- a/annotation/.classpath +++ b/annotation/.classpath @@ -30,6 +30,7 @@ <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> <attributes> <attribute name="maven.pomderived" value="true"/> + <attribute name="org.eclipse.jst.component.nondependency" value=""/> </attributes> </classpathentry> <classpathentry kind="output" path="target/classes"/> diff --git a/frontend-js/package-lock.json b/frontend-js/package-lock.json index 1741782522f591149e01444af0a2f19449afba8a..5fb93d8111736e82a00723c2e8b8ee581c6f3e85 100644 --- a/frontend-js/package-lock.json +++ b/frontend-js/package-lock.json @@ -863,11 +863,19 @@ "version": "1.10.16", "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.16.tgz", "integrity": "sha1-SwUtEIKCQmG2ju2dInQbcR09JGk=", - "dev": true, "requires": { "jquery": "1.12.1" } }, + "datatables.net-rowreorder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/datatables.net-rowreorder/-/datatables.net-rowreorder-1.2.3.tgz", + "integrity": "sha1-dSDLkiCV8UXCFsfR7Yv6mwxbTlc=", + "requires": { + "datatables.net": "1.10.16", + "jquery": "1.12.1" + } + }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", diff --git a/frontend-js/package.json b/frontend-js/package.json index 5d8c93317d26c8ab9a71c2ad0b0d461c967d0276..b2a012041bc0dbb6b5e24e72a738d25953177663 100644 --- a/frontend-js/package.json +++ b/frontend-js/package.json @@ -38,6 +38,7 @@ "uglifyjs": "^2.4.10" }, "dependencies": { + "datatables.net-rowreorder": "^1.2.3", "dual-listbox": "1.0.7", "file-saver": "^1.3.3", "http-status-codes": "^1.3.0", diff --git a/frontend-js/src/main/css/global.css b/frontend-js/src/main/css/global.css index 153fdc543f05d64d5841f67aad878af6a9c23c13..f1019f273c1005dd7a1c3bebf5a5fcedcb061fce 100644 --- a/frontend-js/src/main/css/global.css +++ b/frontend-js/src/main/css/global.css @@ -540,19 +540,21 @@ h1 { border: 1px #ddd solid; } -.minerva-annotator-params { +.minerva-annotator-params { padding: 5px; } + .minerva-annotator-params-header { font-weight: bold; - text-align: center; + text-align: center; padding-bottom: 5px; } + .minerva-annotator-param { display: table; width: 100%; - padding-bottom: 5px; + padding-bottom: 5px; } .minerva-annotator-param-name { @@ -560,7 +562,7 @@ h1 { vertical-align: middle; width: 30%; padding-right: 5px; - + } .minerva-annotator-param-value { @@ -577,7 +579,7 @@ h1 { position: relative } -.minerva-annotators-params-header>div { +.minerva-annotators-params-header > div { position: absolute; left: 20px; top: -11px; @@ -587,8 +589,8 @@ h1 { font-weight: bold; } -.minerva-annotator-param-name .annotator-tooltip, .minerva-annotation-group .annotator-tooltip{ - visibility: hidden; +.minerva-annotator-param-name .annotator-tooltip, .minerva-annotation-group .annotator-tooltip { + visibility: hidden; background-color: rgba(0, 0, 0, .5); color: #fff; text-align: left; @@ -608,19 +610,24 @@ h1 { width: 250px; } -.minerva-annotator-param-name>span:hover .annotator-tooltip, .minerva-annotation-group-header>span:hover .annotator-tooltip { +.minerva-annotator-param-name > span:hover .annotator-tooltip, .minerva-annotation-group-header > span:hover .annotator-tooltip { visibility: visible; } -.minerva-annotation-group-header>span { +.minerva-annotation-group-header > span { position: relative; } -.minerva-annotation-group .tooltip-icon, .minerva-annotator-param-name .tooltip-icon{ +.minerva-annotation-group .tooltip-icon, .minerva-annotator-param-name .tooltip-icon { font-size: 0.8em; left: 3px; top: -3px; font-weight: 700; } +table.dataTable tbody td.no_padding, table.dataTable thead th.no_padding { + padding-top: 8px; + padding-left: 3px; + padding-right: 3px; +} diff --git a/frontend-js/src/main/js/Functions.js b/frontend-js/src/main/js/Functions.js index 015b969c7e815ebb3cda7556622e54045142883f..7ec7cea9466f829ac17676b7d485065317f32978 100644 --- a/frontend-js/src/main/js/Functions.js +++ b/frontend-js/src/main/js/Functions.js @@ -286,6 +286,9 @@ Functions.createElement = function (params) { if (params.title !== null && params.title !== undefined) { result.title = params.title; } + if (params.data !== null && params.data !== undefined) { + $(result).attr("data", params.data); + } return result; }; diff --git a/frontend-js/src/main/js/ServerConnector.js b/frontend-js/src/main/js/ServerConnector.js index 8e7134f3aea9474b91c9a96b1d50e0193732b6cb..12033eba9eb06e11b047b6e8a10bc0cae6336a89 100644 --- a/frontend-js/src/main/js/ServerConnector.js +++ b/frontend-js/src/main/js/ServerConnector.js @@ -1116,19 +1116,17 @@ ServerConnector.getOverlays = function (params) { creator: params.creator, publicOverlay: params.publicOverlay }; - return new Promise(function (resolve, reject) { - self.getProjectId(params.projectId).then(function (result) { - queryParams.projectId = result; - return self.sendGetRequest(self.getOverlaysUrl(queryParams, filterParams)); - }).then(function (content) { - var arr = JSON.parse(content); - var result = []; - for (var i = 0; i < arr.length; i++) { - var overlay = new LayoutData(arr[i]); - result.push(overlay); - } - resolve(result); - }, reject); + return self.getProjectId(params.projectId).then(function (result) { + queryParams.projectId = result; + return self.sendGetRequest(self.getOverlaysUrl(queryParams, filterParams)); + }).then(function (content) { + var arr = JSON.parse(content); + var result = []; + for (var i = 0; i < arr.length; i++) { + var overlay = new LayoutData(arr[i]); + result.push(overlay); + } + return result; }); }; diff --git a/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js b/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js index 110085be7290cf541c26f81742eb2f56c99294a7..0513d712d3d828de2b52be884a8d3267469d4939 100644 --- a/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js +++ b/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js @@ -34,6 +34,39 @@ function OverlayPanel(params) { var titleElement = this.getControlElement(PanelControlElementType.OVERLAY_CUSTOM_OVERLAY_TITLE); self.setCustomOverlaysMessage(titleElement.innerHTML); + $(self.getElement()).on("click", "[name='overlayToggle']", function () { + var thisCheckbox = this; + var overlayId = $(thisCheckbox).attr("data"); + var toggleOverlayPromise; + if (thisCheckbox.checked) { + toggleOverlayPromise = self.getMap().openDataOverlay(overlayId); + } else { + toggleOverlayPromise = self.getMap().removeSelectedLayout(overlayId); + } + $(thisCheckbox).prop("disabled", true); + return toggleOverlayPromise.then(null, GuiConnector.alert).finally(function () { + $(thisCheckbox).prop("disabled", false); + }); + }); + $(self.getElement()).on("click", "[name='overlayLink']", function () { + var overlayId = $(this).attr("data"); + self.getMap().openDataOverlay(overlayId); + $(this.parentNode.parentNode).addClass('active').siblings().removeClass('active'); + }); + $(self.getElement()).on("click", "[name='download-overlay']", function () { + var overlayId = $(this).attr("data"); + return ServerConnector.getOverlaySourceDownloadUrl({ + overlayId: overlayId + }).then(function (url) { + return self.downloadFile(url); + }).then(null, GuiConnector.alert); + }); + $(self.getElement()).on("click", "[name='editButton']", function () { + var overlayId = $(this).attr("data"); + return self.getMap().getModel().getLayoutDataById(overlayId).then(function (overlay) { + return self.openEditOverlayDialog(overlay); + }); + }); } OverlayPanel.prototype = Object.create(Panel.prototype); @@ -99,6 +132,8 @@ OverlayPanel.prototype._createOverlayPanelGui = function () { content: "Add overlay" }); centerTag.appendChild(addOverlayButton); + + this.setControlElement(PanelControlElementType.OVERLAY_ADD_OVERLAY_BUTTON, addOverlayButton); }; @@ -108,11 +143,8 @@ OverlayPanel.prototype.clear = function () { table.removeChild(table.firstChild); } - table = this.getControlElement(PanelControlElementType.OVERLAY_CUSTOM_OVERLAY_TABLE); - while (table.firstChild) { - table.removeChild(table.firstChild); - } - + table = $(this.getControlElement(PanelControlElementType.OVERLAY_CUSTOM_OVERLAY_TABLE)).DataTable(); + table.clear().draw(); }; OverlayPanel.prototype.createTableHeader = function (edit) { @@ -156,31 +188,17 @@ OverlayPanel.prototype.createOverlayRow = function (overlay, checked) { var viewTd = document.createElement("td"); if (overlay.getInputDataAvailable()) { - var checkbox = document.createElement("input"); - checkbox.type = "checkbox"; + var checkbox = Functions.createElement({ + type: "input", + inputType: "checkbox", + name: "overlayToggle", + data: overlay.getId() + }); checkbox.checked = checked; - checkbox.onclick = function () { - var thisCheckbox = this; - var toggleOverlayPromise; - if (thisCheckbox.checked) { - toggleOverlayPromise = self.getMap().openDataOverlay(overlay.getId()); - } else { - toggleOverlayPromise = self.getMap().removeSelectedLayout(overlay.getId()); - } - $(thisCheckbox).prop("disabled", true); - return toggleOverlayPromise.then(null, GuiConnector.alert).finally(function () { - $(thisCheckbox).prop("disabled", false); - }); - }; viewTd.appendChild(checkbox); } else { var img = guiUtils.createIcon("icons/search.png"); - var link = document.createElement("a"); - link.href = "#"; - link.onclick = function () { - self.getMap().openDataOverlay(overlay.getId()); - $(result).addClass('active').siblings().removeClass('active'); - }; + var link = Functions.createElement({type: "a", href: "#", name: "overlayLink", data: overlay.getId()}); link.appendChild(img); viewTd.appendChild(link); } @@ -188,16 +206,13 @@ OverlayPanel.prototype.createOverlayRow = function (overlay, checked) { var dataTd = document.createElement("td"); if (overlay.getInputDataAvailable()) { - var button = document.createElement("button"); - button.setAttribute("name", "download-overlay-" + overlay.getId()); - button.onclick = function () { - return ServerConnector.getOverlaySourceDownloadUrl({ - overlayId: overlay.getId() - }).then(function (url) { - return self.downloadFile(url); - }).then(null, GuiConnector.alert); - }; - button.innerHTML = "<span class='ui-icon ui-icon-arrowthickstop-1-s'></span>"; + var button = Functions.createElement({ + type: "button", + name: "download-overlay", + data: overlay.getId(), + content: "<span class='ui-icon ui-icon-arrowthickstop-1-s'></span>", + xss: false + }); dataTd.appendChild(button); } @@ -205,19 +220,51 @@ OverlayPanel.prototype.createOverlayRow = function (overlay, checked) { if (overlay.getCreator() !== "" && overlay.getCreator() !== undefined) { var editTd = document.createElement("td"); - var editButton = document.createElement("button"); - editButton.setAttribute("name", "editButton"); - editButton.onclick = function () { - return self.openEditOverlayDialog(overlay); - }; - editButton.innerHTML = "<span class='ui-icon ui-icon-document'></span>"; - editTd.appendChild(editButton); + button = Functions.createElement({ + type: "button", + name: "editButton", + data: overlay.getId(), + content: "<span class='ui-icon ui-icon-document'></span>", + xss: false + }); + editTd.appendChild(button); result.appendChild(editTd); } result.title = overlay.getDescription(); return result; }; +OverlayPanel.prototype.overlayToDataRow = function (overlay, checked, index) { + var self = this; + // if (checked && !overlay.getInputDataAvailable()) { + // result.className = "active"; + // } + // result.title = overlay.getDescription(); + + var result = []; + result[0] = index + 1; + result[1] = overlay.getName(); + + if (overlay.getInputDataAvailable()) { + var checkedString = ""; + if (checked) { + checkedString = " checked "; + } + result[2] = "<input type='checkbox' " + checkedString + " data='" + overlay.getId() + "' name='overlayToggle'/>"; + result[3] = "<button data='" + overlay.getId() + "' name='download-overlay'><span class='ui-icon ui-icon-arrowthickstop-1-s'></span></button>"; + } else { + result[2] = "<a href='#' data='" + overlay.getId() + "' name='overlayLink'><img src='" + GuiConnector.getImgPrefix() + "icons/search.png' style='float: left' hspace='5'/></a>"; + result[3] = ""; + } + + if (overlay.getCreator() !== "" && overlay.getCreator() !== undefined) { + result[4] = "<button data='" + overlay.getId() + "' name='editButton'><span class='ui-icon ui-icon-document'></span></button>"; + } else { + result[4] = ""; + } + return result; +}; + OverlayPanel.prototype.openEditOverlayDialog = function (overlay) { var self = this; var guiUtils = self.getGuiUtils(); @@ -339,19 +386,25 @@ OverlayPanel.prototype.refresh = function (showDefault) { } else { title.innerHTML = self.getCustomOverlaysMessage(); addButton.style.display = "block"; + var tableElement = self.getControlElement(PanelControlElementType.OVERLAY_CUSTOM_OVERLAY_TABLE); - table = self.getControlElement(PanelControlElementType.OVERLAY_CUSTOM_OVERLAY_TABLE); - table.appendChild(self.createTableHeader(true)); - - body = document.createElement("tbody"); - table.appendChild(body); + table = $(tableElement).on('order.dt', function (e) { + if ($(tableElement).dataTable().fnSettings().aaSorting[0][0] === 0) { + table.rowReorder.enable(); + } else { + table.rowReorder.disable(); + } + }).DataTable(); + var data = []; for (i = 0; i < customOverlays.length; i++) { overlay = customOverlays[i]; if (showDefault && overlay.isDefaultOverlay()) { selectedOverlay[overlay.getId()] = true; } - body.appendChild(self.createOverlayRow(overlay, selectedOverlay[overlay.getId()])); + data.push(self.overlayToDataRow(overlay, selectedOverlay[overlay.getId()], i)); } + table.clear().rows.add(data).draw(); + } self.onresize(); @@ -400,8 +453,31 @@ OverlayPanel.prototype.openAddOverlayDialog = function () { OverlayPanel.prototype.init = function () { var backgroundOverlay = ServerConnector.getSessionData().getSelectedBackgroundOverlay(); var showDefault = (backgroundOverlay === undefined || backgroundOverlay === "undefined"); - logger.debug(ServerConnector.getSessionData().getSelectedBackgroundOverlay()); - logger.debug(showDefault); + $(this.getControlElement(PanelControlElementType.OVERLAY_CUSTOM_OVERLAY_TABLE)).DataTable({ + columns: [{ + title: 'No', + className: "no_padding" + }, { + title: 'Name' + }, { + title: 'View', + orderable: false, + className: "no_padding" + }, { + title: 'Data', + orderable: false, + className: "no_padding" + }, { + title: 'Edit', + orderable: false, + className: "no_padding" + }], + paging: false, + searching: false, + info: false, + rowReorder: true + }); + return this.refresh(showDefault); }; @@ -417,6 +493,11 @@ OverlayPanel.prototype.removeOverlay = function (overlay) { OverlayPanel.prototype.destroy = function () { var self = this; Panel.prototype.destroy.call(this); + var customOverlayDataTable = self.getControlElement(PanelControlElementType.OVERLAY_CUSTOM_OVERLAY_TABLE); + if ($.fn.DataTable.isDataTable(customOverlayDataTable)) { + $(customOverlayDataTable).DataTable().destroy(); + } + if (self._addOverlayDialog !== undefined) { return self._addOverlayDialog.destroy(); } else { diff --git a/frontend-js/src/test/js/gui/leftPanel/OverlayPanel-test.js b/frontend-js/src/test/js/gui/leftPanel/OverlayPanel-test.js index 780cfe10c2fafc31c2097e979532e46d9982f8a9..b67812e01bcda3aff5d242d5ac1b43120ea5e0a9 100644 --- a/frontend-js/src/test/js/gui/leftPanel/OverlayPanel-test.js +++ b/frontend-js/src/test/js/gui/leftPanel/OverlayPanel-test.js @@ -3,6 +3,7 @@ require('../../mocha-config.js'); var OverlayPanel = require('../../../../main/js/gui/leftPanel/OverlayPanel'); +var ServerConnector = require('../../ServerConnector-mock'); var chai = require('chai'); var assert = chai.assert; @@ -13,24 +14,46 @@ describe('OverlayPanel', function () { it('constructor', function () { var map = helper.createCustomMap(); - new OverlayPanel({ + var overlay = new OverlayPanel({ element: testDiv, customMap: map }); assert.equal(logger.getWarnings().length, 0); + return overlay.destroy(); }); - it('refresh', function () { - var map = helper.createCustomMap(); - - var panel = new OverlayPanel({ - element: testDiv, - customMap: map + describe('refresh', function () { + it('anonymous', function () { + var map = helper.createCustomMap(); + + var panel = new OverlayPanel({ + element: testDiv, + customMap: map + }); + return panel.init().then(function () { + return panel.refresh(); + }).then(function () { + assert.ok(panel.getElement().innerHTML.indexOf("testLayout") >= 0); + assert.ok(panel.getElement().innerHTML.indexOf("YOU ARE NOT LOGGED") >= 0); + return panel.destroy(); + }); }); - - return panel.refresh().then(function () { - assert.ok(panel.getElement().innerHTML.indexOf("testLayout") >= 0); - assert.ok(panel.getElement().innerHTML.indexOf("YOU ARE NOT LOGGED") >= 0); + it('admin', function () { + helper.loginAsAdmin(); + var map = helper.createCustomMap(); + + var panel = new OverlayPanel({ + element: testDiv, + customMap: map + }); + + return panel.init().then(function () { + + return panel.refresh(); + }).then(function () { + assert.ok(panel.getElement().innerHTML.indexOf("testLayout") >= 0); + return panel.destroy(); + }); }); }); @@ -59,8 +82,25 @@ describe('OverlayPanel', function () { } } assert.ok(openButton); - openButton.onclick(); - return panel.destroy(); + return helper.triggerJqueryEvent(openButton, "click").then(function(){ + return panel.destroy(); + }); + }); + it('select background', function () { + var panel; + return ServerConnector.getProject().then(function (project) { + panel = new OverlayPanel({ + element: testDiv, + customMap: helper.createCustomMap(project) + }); + return panel.init(); + }).then(function () { + var backgroundLink = $("[name='overlayLink']", panel.getElement())[0]; + assert.ok($(backgroundLink).attr("data")); + return helper.triggerJqueryEvent(backgroundLink, "click"); + }).then(function () { + return panel.destroy(); + }); }); it('download', function () { var map = helper.createCustomMap(); @@ -74,7 +114,9 @@ describe('OverlayPanel', function () { customMap: map }); - return panel.refresh().then(function () { + return panel.init().then(function () { + return panel.refresh(); + }).then(function () { var buttons = panel.getElement().getElementsByTagName("button"); var downloadButton; for (var i = 0; i < buttons.length; i++) { @@ -85,9 +127,10 @@ describe('OverlayPanel', function () { } assert.ok(downloadButton); assert.notOk(panel.getLastDownloadUrl()); - return downloadButton.onclick(); + return helper.triggerJqueryEvent(downloadButton, "click"); }).then(function () { assert.ok(panel.getLastDownloadUrl()); + return panel.destroy(); }); }); @@ -104,5 +147,23 @@ describe('OverlayPanel', function () { }); }); + it('openEditOverlayDialog', function () { + helper.loginAsAdmin(); + var panel; + return ServerConnector.getProject().then(function (project) { + panel = new OverlayPanel({ + element: testDiv, + customMap: helper.createCustomMap(project) + }); + return panel.init(); + }).then(function () { + var backgroundLink = $("[name='editButton']", panel.getElement())[0]; + assert.ok($(backgroundLink).attr("data")); + return helper.triggerJqueryEvent(backgroundLink, "click"); + }).then(function () { + return panel.destroy(); + }); + }); + }); diff --git a/frontend-js/src/test/js/helper.js b/frontend-js/src/test/js/helper.js index 682e21268d502facdb28e25c44a5ec09b010f326..113d9e898bcd1a03ef3b558e5ca2d8b59f1eecac 100644 --- a/frontend-js/src/test/js/helper.js +++ b/frontend-js/src/test/js/helper.js @@ -442,6 +442,27 @@ Helper.prototype.loginAsAdmin = function () { ServerConnector.getSessionData().setToken("ADMIN_TOKEN_ID"); }; +Helper.prototype.triggerJqueryEvent = function (element, eventType) { + var domElements = $("*"); + var promises = []; + for (var i = 0; i < domElements.length; i++) { + var domElement = domElements[i]; + $.each($._data(domElement, "events"), function (type, events) { + if (type === eventType) { + for (var j = 0; j < events.length; j++) { + var event = events[j]; + var eventTargets = $(event.selector, domElement); + for (var k = 0; k < eventTargets.length; k++) { + if (element === eventTargets[k]) { + promises.push(event.handler.call(element)) + } + } + } + } + }); + } + return Promise.all(promises); +}; Helper.EPSILON = 1e-6; diff --git a/frontend-js/src/test/js/mocha-config.js b/frontend-js/src/test/js/mocha-config.js index eb9151b5729ecce4c11dcbb2fd178a46f6a03aee..6c715e1587dadd48f1763e8ba1259ff70749bef3 100644 --- a/frontend-js/src/test/js/mocha-config.js +++ b/frontend-js/src/test/js/mocha-config.js @@ -107,6 +107,7 @@ before(function () { require("bootstrap"); require('datatables.net')(window, $); + require('datatables.net-rowreorder')(window, $); require('spectrum-colorpicker'); global.tinycolor = window.tinycolor; require('jstree'); diff --git a/frontend-js/testFiles/apiCalls/projects/sample/overlays/creator=admin&publicOverlay=false&token=ADMIN_TOKEN_ID& b/frontend-js/testFiles/apiCalls/projects/sample/overlays/creator=admin&publicOverlay=false&token=ADMIN_TOKEN_ID& index 0637a088a01e8ddab3bf3fa98dbe804cbde1a0dc..3929d3d47205f9d096201a7da7592db33e0c6854 100644 --- a/frontend-js/testFiles/apiCalls/projects/sample/overlays/creator=admin&publicOverlay=false&token=ADMIN_TOKEN_ID& +++ b/frontend-js/testFiles/apiCalls/projects/sample/overlays/creator=admin&publicOverlay=false&token=ADMIN_TOKEN_ID& @@ -1 +1 @@ -[] \ No newline at end of file +[{"idObject":26107,"modelId":15781,"name":"s1","description":"","status":"OK","publicOverlay":false,"defaultOverlay":false,"progress":"0.00","directory":"5e8ff9bf55ba3508199d22e984129be6/.26107","creator":"admin","inputDataAvailable":"true"},{"idObject":26108,"modelId":15781,"name":"s2","description":"","status":"OK","publicOverlay":false,"defaultOverlay":false,"progress":"0.00","directory":"5e8ff9bf55ba3508199d22e984129be6/.26108","creator":"admin","inputDataAvailable":"true"}] \ No newline at end of file diff --git a/web/src/main/webapp/index.xhtml b/web/src/main/webapp/index.xhtml index 4173a0d9b725208930f7bba7f48de251ed0d5583..559c0a9a37ee724a18cdadba6a9f966a4b208e51 100644 --- a/web/src/main/webapp/index.xhtml +++ b/web/src/main/webapp/index.xhtml @@ -19,9 +19,12 @@ <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script src="https://cdn.datatables.net/1.10.13/js/jquery.dataTables.min.js"></script> + <script src="https://cdn.datatables.net/rowreorder/1.2.3/js/dataTables.rowReorder.min.js"></script> + <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.13/css/jquery.dataTables.min.css"/> <link rel="stylesheet" type="text/css" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"/> + <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/rowreorder/1.2.3/css/rowReorder.dataTables.min.css"/> <link rel="shortcut icon" href="./resources/images/favicon.png" type="image/png" />