Skip to content
34 changes: 18 additions & 16 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,24 +84,26 @@
<p data-bind="html: appInitializationErrorMessage"></p>
</div>
</div>
<!-- ko if: $data.currentCohortDefinition() || $data.currentConceptSet() -->
<!-- ko if: $data.currentCohortDefinition() != null && $data.router.currentView() !='cohort-definition-manager' && $data.router.currentView() != 'loading' -->
<div class="breadcrumb-container">
<i class="fa fa-arrow-left"></i>
<a data-bind="attr: { href: '#/cohortdefinition/' + $data.currentCohortDefinition().id()}, text: $data.currentCohortDefinition().name"></a>
<!-- ko if: $data.currentConceptSet() != null && $data.currentConceptSetSource() == 'cohort' -->
<i class="fa fa-chevron-right"></i>
<a data-bind="attr: { href: '#/cohortdefinition/' + $data.currentCohortDefinition().id() + '/conceptsets/' + $data.currentConceptSet().id + '/details'}, text:$data.currentConceptSet().name()"></a>
<div>
<!-- ko if: $data.currentCohortDefinition() || $data.currentConceptSet() -->
<!-- ko if: $data.currentCohortDefinition() != null && $data.router.currentView() !='cohort-definition-manager' && $data.router.currentView() != 'loading' -->
<div class="breadcrumb-container">
<i class="fa fa-arrow-left"></i>
<a data-bind="attr: { href: '#/cohortdefinition/' + $data.currentCohortDefinition().id()}, text: $data.currentCohortDefinition().name"></a>
<!-- ko if: $data.currentConceptSet() != null && $data.currentConceptSetSource() == 'cohort' -->
<i class="fa fa-chevron-right"></i>
<a data-bind="attr: { href: '#/cohortdefinition/' + $data.currentCohortDefinition().id() + '/conceptsets/' + $data.currentConceptSet().id + '/details'}, text:$data.currentConceptSet().name()"></a>
<!-- /ko -->
</div>
<!-- /ko -->
<!-- ko if: $data.currentConceptSet() && $data.currentConceptSetSource() == 'repository' && $data.router.currentView() != 'conceptset-manager' -->
<div class="breadcrumb-container">
<i class="fa fa-arrow-left"></i>
<a data-bind="attr: { href: '#/conceptset/' + $data.currentConceptSet().id + '/details'}, text:$data.currentConceptSet().name"></a>
</div>
<!-- /ko -->
<!-- /ko -->
</div>
<!-- /ko -->
<!-- ko if: $data.currentConceptSet() && $data.currentConceptSetSource() == 'repository' && $data.router.currentView() != 'conceptset-manager' -->
<div class="breadcrumb-container">
<i class="fa fa-arrow-left"></i>
<a data-bind="attr: { href: '#/conceptset/' + $data.currentConceptSet().id + '/details'}, text:$data.currentConceptSet().name"></a>
</div>
<!-- /ko -->
<!-- /ko -->

<!-- ko ifnot: EventBus.errorMsg() -->
<div data-bind="if: $data.router.currentViewAccessible" class="flexed">
Expand Down
2 changes: 1 addition & 1 deletion js/components/faceted-datatable.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="text-center" data-bind="if: $component.componentLoading(), css: {visible:componentLoading(), hidden: !componentLoading()}">
<h3><i class="fa fa-spinner fa-spin"></i> Loading ...</h3>
</div>
<div data-bind="ifnot: $component.componentLoading(), css: {visible: !componentLoading(), hidden: componentLoading()}">
<div data-bind="css: {visible: !componentLoading(), hidden: componentLoading()}">
<div class="row">
<div class="feFilter" data-bind="visible:$component.facets().length > 0, css: {'col-xs-2': $component.facets().length > 0}">
<div data-bind="with: $component.facets">
Expand Down
65 changes: 51 additions & 14 deletions js/components/faceted-datatable.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
define(['knockout', 'text!./faceted-datatable.html', 'crossfilter', 'colvis', ], function (ko, view, crossfilter) {
define(
['knockout', 'text!./faceted-datatable.html', 'crossfilter', 'utils/DatatablePaginationUtils', 'colvis', ],
function (ko, view, crossfilter, DatatablePaginationUtils)
{

function facetedDatatable(params) {
function getDtElement(facetedDtEl) {
return $(facetedDtEl).find('table');
}

function getSavedSearch(element) {
return DatatablePaginationUtils.getFacets(getDtElement(element).dataTable().DataTable());
}

function facetedDatatable(params, componentInfo) {
var self = this;
var subscriptions = [];

self.element = componentInfo.element;
self.selectedData = params.selectedData || null;
self.headersTemplateId = params.headersTemplateId;
self.reference = params.reference;
Expand Down Expand Up @@ -66,9 +78,16 @@ define(['knockout', 'text!./faceted-datatable.html', 'crossfilter', 'colvis', ],
self.scrollY = params.scrollY || null;
self.scrollCollapse = params.scrollCollapse || false;

self.updateFilters = function (data, event) {
self.selectFacetItems = function(targetCaption, selectedFacetValues) {
const facetItems = self.facets().find(f => f.caption === targetCaption).facetItems;
if (facetItems) {
facetItems.forEach(fi => self.updateFilters(fi, null, selectedFacetValues.includes(fi.key), false));
}
};

self.updateFilters = function (data, event, selected, emitEvent = true) {
var facet = data.facet;
data.selected(!data.selected());
data.selected(typeof selected !== 'undefined' ? selected : !data.selected());
if (data.selected()) {
if (!facet.selectedItems.hasOwnProperty(data.key)) {
facet.selectedItems[data.key] = data;
Expand All @@ -88,6 +107,8 @@ define(['knockout', 'text!./faceted-datatable.html', 'crossfilter', 'colvis', ],
});
}
self.data.valueHasMutated();

emitEvent && getDtElement(self.element).trigger('facet.dt', data);
}

// additional helper function to help with crossfilter-ing dimensions that contain nulls
Expand Down Expand Up @@ -126,16 +147,22 @@ define(['knockout', 'text!./faceted-datatable.html', 'crossfilter', 'colvis', ],
self.facets.push(facet);
});
// Iterate over the facets and set any defaults
const savedSearch = getSavedSearch(self.element);
$.each(self.options.Facets, function (i, facetConfig) {
if (facetConfig.defaultFacets && facetConfig.defaultFacets.length > 0) {
$.each(facetConfig.defaultFacets, function (d, defaultFacet) {
var facetItem = $.grep(self.facets()[i].facetItems, function (f) {
return f.key == defaultFacet;
});
if (facetItem.length > 0) {
self.updateFilters(facetItem[0], null);
}
})
const savedFacetValues = savedSearch[facetConfig.caption];
if (savedFacetValues) {
self.selectFacetItems(facetConfig.caption, savedFacetValues);
} else {
if (facetConfig.defaultFacets && facetConfig.defaultFacets.length > 0) {
$.each(facetConfig.defaultFacets, function (d, defaultFacet) {
var facetItem = $.grep(self.facets()[i].facetItems, function (f) {
return f.key == defaultFacet;
});
if (facetItem.length > 0) {
self.updateFilters(facetItem[0], null, true, false);
}
})
}
}
});
}
Expand All @@ -144,6 +171,16 @@ define(['knockout', 'text!./faceted-datatable.html', 'crossfilter', 'colvis', ],
})
);

$(self.element).on('refresh.faceted-dt', () => {
if (Array.isArray(self.options.Facets)) {
const savedSearch = getSavedSearch(self.element);
self.options.Facets.forEach(facetConfig => {
const savedFacetValues = savedSearch[facetConfig.caption] || facetConfig.defaultFacets || [];
self.selectFacetItems(facetConfig.caption, savedFacetValues);
});
}
});

// init component
if (ko.isComputed(self.reference)) {
// valueHasMutated doesn't work for computed
Expand Down Expand Up @@ -190,7 +227,7 @@ define(['knockout', 'text!./faceted-datatable.html', 'crossfilter', 'colvis', ],
};

var component = {
viewModel: facetedDatatable,
viewModel: {createViewModel: (params, info) => new facetedDatatable(params, info)},
template: view
};

Expand Down
11 changes: 6 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 @@ -130,7 +132,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 +188,7 @@ 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.refreshTable(table);
}


Expand Down
10 changes: 10 additions & 0 deletions js/pages/Router.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ define(
'knockout',
'const',
'services/EventBus',
'datatables.net',
'utils/DatatablePaginationUtils',
'director',
],
(
Expand All @@ -19,6 +21,8 @@ define(
ko,
constants,
EventBus,
dataTables,
DatatablePaginationUtils
) => {
class AtlasRouter {
constructor() {
Expand Down Expand Up @@ -127,6 +131,12 @@ define(
this.routerParams(routerParams);
}
this.currentView(view);
setTimeout(() => {
$.fn.dataTable.tables().forEach(dt => {
DatatablePaginationUtils.refreshTable($(dt).dataTable().DataTable());
});
$('faceted-datatable').trigger('refresh.faceted-dt');
});
}
}
return new AtlasRouter();
Expand Down
22 changes: 12 additions & 10 deletions js/pages/cohort-definitions/cohort-definition-manager.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<loading data-bind="visible: $component.isLoading()"></loading>
<div data-bind="if: currentCohortDefinition() && $root.router.currentView() == 'cohort-definition-manager' && !$component.isLoading()" class="flexed">
<heading-title params="name: cohortDefinitionCaption(), icon: 'users', theme: 'dark'"></heading-title>
<!-- ko if: currentCohortDefinition().id() != 0 -->
<!-- ko component: {name: 'authorship', params: getAuthorship()} --> <!-- /ko -->
<!-- /ko -->
<div class="authorship-container">
<!-- ko if: currentCohortDefinition().id() != 0 -->
<!-- ko component: {name: 'authorship', params: getAuthorship()} --> <!-- /ko -->
<!-- /ko -->
</div>
<access-denied params="isAuthenticated: isAuthenticated, isPermitted: hasAccess"></access-denied>

<!-- ko if: hasAccess -->
Expand Down Expand Up @@ -34,28 +36,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
8 changes: 6 additions & 2 deletions js/pages/cohort-definitions/cohort-definition-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -1054,15 +1054,19 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
}
}

onRouterParamsChanged(params) {
const { cohortDefinitionId, conceptSetId, mode = 'definition', sourceKey } = params;
onRouterParamsChanged(changedParamsMap, newParams) {
const { cohortDefinitionId, conceptSetId, mode = 'definition', sourceKey } = newParams || changedParamsMap;
this.clearConceptSet();
this.tabMode(mode);
if (!this.checkifDataLoaded(cohortDefinitionId, conceptSetId, sourceKey)) {
this.prepareCohortDefinition(cohortDefinitionId, conceptSetId, sourceKey);
}
}

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
2 changes: 1 addition & 1 deletion js/pages/cohort-definitions/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ define(
var sourceKey = qs.sourceKey || null;
router.setCurrentView('cohort-definition-manager', {
cohortDefinitionId,
mode: 'definition',
mode: view,
sourceKey,
});
sharedState.ConceptSet.source('cohort');
Expand Down
30 changes: 30 additions & 0 deletions js/utils/CommonUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,34 @@ define([
return tooltipText.replace(/'/g, "\\'").replace(/"/g, '&quot;');
}

const getPathTo = function(element) {
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 +257,7 @@ define([
normalizeUrl,
toggleConceptSetCheckbox,
escapeTooltip,
getPathTo,
calculateStringHash,
};
});
Loading