Skip to content
21 changes: 16 additions & 5 deletions js/extensions/bindings/datatableBinding.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ define([
'xss',
'moment',
'services/MomentAPI',
'utils/DatatablePaginationUtils',
'datatables.net-buttons',
'colvis',
'datatables.net-buttons-html5',
Expand All @@ -16,7 +17,8 @@ define([
config,
filterXSS,
moment,
momentApi
momentApi,
paginationUtils
) {

function renderSelected(s, p, d) {
Expand Down Expand Up @@ -59,6 +61,13 @@ define([
return abxX < absY ? -1 : abxX>absY ? 1 : 0;
}

function redrawTable(table, mode) {
// drawing may access observables, which updating we do not want to trigger a redraw to the table
// see: https://knockoutjs.com/documentation/computed-dependency-tracking.html#IgnoringDependencies
const func = mode ? table.draw.bind(null, mode) : table.draw;
ko.ignoreDependencies(func);
}

ko.bindingHandlers.dataTable = {

init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
Expand Down Expand Up @@ -130,7 +139,8 @@ define([
ko.applyBindings(bindingContext, $(element).find('thead')[0]);
}

$(element).DataTable(binding.options);
const datatable = $(element).DataTable(binding.options);
paginationUtils.applyPaginationListeners(element, datatable, binding);

if (binding.api != null)
{
Expand Down Expand Up @@ -185,9 +195,10 @@ define([
if (data.length > 0)
table.rows.add(data);

// drawing may access observables, which updating we do not want to trigger a redraw to the table
// see: https://knockoutjs.com/documentation/computed-dependency-tracking.html#IgnoringDependencies
ko.ignoreDependencies(table.draw);
paginationUtils.applyDtSearch(table);
paginationUtils.applyDtSorting(table);
redrawTable(table);
paginationUtils.applyDtPage(table, redrawTable);
}


Expand Down
3 changes: 3 additions & 0 deletions js/pages/Router.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ define(
'knockout',
'const',
'services/EventBus',
'datatables.net',
'director',
],
(
Expand All @@ -19,6 +20,7 @@ define(
ko,
constants,
EventBus,
dataTables
) => {
class AtlasRouter {
constructor() {
Expand Down Expand Up @@ -122,6 +124,7 @@ define(
setCurrentView(view, routerParams = false) {
if (view !== this.currentView()) {
this.currentView('loading');
dataTables.ext._unique = 0;
}
if (routerParams !== false) {
this.routerParams(routerParams);
Expand Down
14 changes: 7 additions & 7 deletions js/pages/cohort-definitions/cohort-definition-manager.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,28 @@
</div>

<ul class="nav nav-tabs">
<li role="presentation" data-bind="css: { active: $component.tabMode() == 'definition' }, click: function() { $component.tabMode('definition'); }">
<li role="presentation" data-bind="css: { active: $component.tabMode() == 'definition' }, click: function() { $component.selectTab('definition'); }">
<a>Definition <i data-bind="click: function () { $component.cohortDefinitionOpened(true) }" class="fa fa-question-circle-o"></i></a>
</li>

<li role="presentation" data-bind="css: { active: $component.tabMode() == 'conceptsets' }, click: function() { $component.tabMode('conceptsets'); }">
<li role="presentation" data-bind="css: { active: $component.tabMode() == 'conceptsets' }, click: function() { $component.selectTab('conceptsets'); }">
<a>Concept Sets</a>
</li>

<li role="presentation" data-bind="css: { active: $component.tabMode() == 'generation' }, click: function() { $component.tabMode('generation'); }">
<li role="presentation" data-bind="css: { active: $component.tabMode() == 'generation' }, click: function() { $component.selectTab('generation'); }">
<a>Generation</a>
</li>

<li role="presentation" data-bind="css: { active: $component.tabMode() == 'reporting' }, click: function() { $component.tabMode('reporting'); }">
<li role="presentation" data-bind="css: { active: $component.tabMode() == 'reporting' }, click: function() { $component.selectTab('reporting'); }">
<a>Reporting</a>
</li>
<!--
<li role="presentation" data-bind="css: { active: $component.tabMode() == 'explore' }, click: function() { $component.tabMode('explore'); }"><a>Explore</a></li>
<li role="presentation" data-bind="css: { active: $component.tabMode() == 'explore' }, click: function() { $component.selectTab('explore'); }"><a>Explore</a></li>
-->
<li role="presentation" data-bind="css: { active: $component.tabMode() == 'export' }, click: () => $component.tabMode('export')">
<li role="presentation" data-bind="css: { active: $component.tabMode() == 'export' }, click: () => $component.selectTab('export')">
<a>Export</a>
</li>
<li role="presentation" data-bind="css: { active: $component.tabMode() === 'warnings' }, click: function(){ $component.tabMode('warnings'); } ">
<li role="presentation" data-bind="css: { active: $component.tabMode() === 'warnings' }, click: function(){ $component.selectTab('warnings'); } ">
<a data-bind="attr: { class: warningClass }">Messages <span class="badge" data-bind="text: warningsTotals, visible: warningsTotals() > 0"></span></a>
</li>
</ul>
Expand Down
4 changes: 4 additions & 0 deletions js/pages/cohort-definitions/cohort-definition-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,10 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
}
}

selectTab(key) {
commonUtils.routeTo(`/cohortdefinition/${this.currentCohortDefinition().id()}/${key}`);
}

getSourceInfo(source) {
const info = this.currentCohortDefinitionInfo();
for (var i = 0; i < info.length; i++) {
Expand Down
32 changes: 32 additions & 0 deletions js/utils/CommonUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,36 @@ define([
return tooltipText.replace(/'/g, "\\'").replace(/"/g, '&quot;');
}

const getPathTo = function(element) {
if (element.id!=='')
return 'id("'+element.id+'")';
if (element===document.body)
return element.tagName;

let ix = 0;
const siblings = element.parentNode.childNodes;
for (let i= 0; i<siblings.length; i++) {
const sibling = siblings[i];
if (sibling===element)
return getPathTo(element.parentNode)+'/'+element.tagName+'['+(ix+1)+']';
if (sibling.nodeType===1 && sibling.tagName===element.tagName)
ix++;
}
};

const calculateStringHash = function(string) {
let hash = 0;
if (string.length == 0) {
return hash;
}
for (var i = 0; i < string.length; i++) {
var char = string.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
};

return {
build,
confirmAndDelete,
Expand All @@ -229,5 +259,7 @@ define([
normalizeUrl,
toggleConceptSetCheckbox,
escapeTooltip,
getPathTo,
calculateStringHash,
};
});
167 changes: 167 additions & 0 deletions js/utils/DatatablePaginationUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
define([
'utils/CommonUtils',
'urijs',
], (
CommonUtils,
URI
) => {
const PAGE_PARAM = 'dtPage';
const SEARCH_PARAM = 'dtSearch';
const ORDER_PARAM = 'dtOrder';
const PARAM_SEPARATOR = '_';

function buildDtParamName(datatable, param) {
const elPath = CommonUtils.getPathTo(datatable.table().container());
const elId = CommonUtils.calculateStringHash(elPath);
return param + PARAM_SEPARATOR + elId;
}

function getDtParamValue(param) {
const currentUrl = URI(document.location.href);
const fragment = URI(currentUrl.fragment());
const params = fragment.search(true);
return params[param];
}

function setUrlParams(obj) {
const currentUrl = URI(document.location.href);
const fragment = URI(currentUrl.fragment());
Object.keys(obj).forEach(k => {
fragment.removeSearch(k).addSearch(k, obj[k]);
});
const updatedUrl = currentUrl.fragment(fragment.toString()).toString();
document.location = updatedUrl;
}

function getPageParamName(datatable) {
return buildDtParamName(datatable, PAGE_PARAM);
}

function getPageNumFromUrl(datatable) {
return +getDtParamValue(getPageParamName(datatable));
}

function setPageNumToUrl(datatable, num) {
setUrlParams({
[getPageParamName(datatable)]: num
});
}

function getSearchParamName(datatable) {
return buildDtParamName(datatable, SEARCH_PARAM);
}

function getSearchFromUrl(datatable) {
return getDtParamValue(getSearchParamName(datatable));
}

function setSearchToUrl(datatable, searchStr) {
setUrlParams({
[getSearchParamName(datatable)]: searchStr,
[getPageParamName(datatable)]: 0
});
}

function getOrderParamName(datatable) {
return buildDtParamName(datatable, ORDER_PARAM);
}

function getOrderFromUrl(datatable) {
const rawValue = getDtParamValue(getOrderParamName(datatable));
if (!rawValue) {
return null;
}
const parts = rawValue.split(',');
return {
column: +parts[0],
direction: parts[1]
};
}

function setOrderToUrl(datatable, column, direction) {

setUrlParams({
[getOrderParamName(datatable)]: column + ',' + direction,
[getPageParamName(datatable)]: 0
});
}

function getOrderCol(order) {

return !!order[0]
? Array.isArray(order[0]) ? order[0][0] : order[0]
: undefined;
}

function getOrderDir(order) {

return !!order[0]
? Array.isArray(order[0]) ? order[0][1] : order[1]
: undefined;
}

function applyPaginationListeners(element, datatable, binding) {
const {defaultColumnIdx, defaultOrderDir} = binding.options && binding.options.order && (getOrderCol(binding.options.order) || getOrderDir(binding.options.order))
? {defaultColumnIdx: getOrderCol(binding.options.order), defaultOrderDir: getOrderDir(binding.options.order)}
: {defaultColumnIdx: 0, defaultOrderDir: 'asc'};

$(element).on('page.dt', function () {
const info = datatable.page.info();
setPageNumToUrl(datatable, info.page);
});

$(element).on('search.dt', function () {
const currentSearchStr = getSearchFromUrl(datatable) || '';
const newSearchStr = datatable.search();
if (currentSearchStr !== newSearchStr) {
setSearchToUrl(datatable, newSearchStr);
}
});

$(element).on('order.dt', function () {
const currentOrder = getOrderFromUrl(datatable);

const newOrder = datatable.order();
if (!Array.isArray(newOrder) || !newOrder.length) {
return;
}
const newColumnIdx = getOrderCol(newOrder);
const newOrderDir = getOrderDir(newOrder);

const isOrderChanged = !currentOrder || currentOrder.column !== newColumnIdx || currentOrder.direction !== newOrderDir;
const isOrderChangedFromDefault = !(!currentOrder && newColumnIdx === defaultColumnIdx && newOrderDir === defaultOrderDir);
if (isOrderChanged && isOrderChangedFromDefault) {
setOrderToUrl(datatable, newColumnIdx, newOrderDir);
}
});
}

function applyDtSearch(table) {
const currentSearchStr = getSearchFromUrl(table);
if (currentSearchStr) {
table.search(currentSearchStr);
}
}

function applyDtSorting(table) {
const currentOrder = getOrderFromUrl(table);
if (currentOrder && currentOrder.column && currentOrder.direction) {
table.order([[currentOrder.column, currentOrder.direction]]);
}
}

function applyDtPage(table, redrawTable) {
const currentPage = getPageNumFromUrl(table);
if (currentPage) {
table.page(currentPage);
redrawTable(table, 'page');
}
}

return {
applyPaginationListeners,
applyDtSearch,
applyDtSorting,
applyDtPage,
}
});