diff --git a/client/src/app/instance/search/components/result/datatable.component.html b/client/src/app/instance/search/components/result/datatable.component.html index 9d8ec1e0ad64a28ff1544f2fc0433e7ba73350db..6affb07eac267fd9f6ab6fbb8857a4aec479ddd2 100644 --- a/client/src/app/instance/search/components/result/datatable.component.html +++ b/client/src/app/instance/search/components/result/datatable.component.html @@ -1,11 +1,11 @@ <app-spinner *ngIf="dataIsLoading"></app-spinner> <div class="table-responsive" *ngIf="dataIsLoaded"> - <table class="table table-hover" aria-describedby="List of results"> + <table id="datatable" class="table table-hover" aria-describedby="List of results"> <thead> <tr> <th *ngIf="dataset.datatable_selectable_rows" scope="col" class="select">#</th> - <th *ngFor="let attribute of getOutputList()" scope="col"> + <th *ngFor="let attribute of getOutputList()" scope="col" draggable="true" class="datatable-title"> <app-attribute-label [label]="attribute.label" [description]="attribute.description"></app-attribute-label> <span *ngIf="attribute.id === sortedCol && attribute.order_by" class="pl-2" class="clickable" (click)="sort(attribute.id)"> diff --git a/client/src/app/instance/search/components/result/datatable.component.scss b/client/src/app/instance/search/components/result/datatable.component.scss index d40e1d1dac814cf5d102188e107a1358420f18ff..2894dff1a7b6893f06b5988777ef77ce7fa5d7a5 100644 --- a/client/src/app/instance/search/components/result/datatable.component.scss +++ b/client/src/app/instance/search/components/result/datatable.component.scss @@ -9,6 +9,11 @@ table th:not(.select) { min-width: 130px; + cursor: move; +} + +.over { + border-right: 3px dotted #666; } .data-selected { diff --git a/client/src/app/instance/search/components/result/datatable.component.ts b/client/src/app/instance/search/components/result/datatable.component.ts index 95cd763f07618d853931201f0268fa447ae117d9..46ca4651cbcbd9b75d7908ae71a9181c737b9880 100644 --- a/client/src/app/instance/search/components/result/datatable.component.ts +++ b/client/src/app/instance/search/components/result/datatable.component.ts @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { Instance, @@ -30,7 +30,7 @@ import { Pagination, PaginationOrder, SearchQueryParams } from 'src/app/instance templateUrl: 'datatable.component.html', styleUrls: ['datatable.component.scss'], }) -export class DatatableComponent implements OnInit { +export class DatatableComponent implements OnInit, OnChanges { @Input() dataset: Dataset; @Input() instance: Instance; @Input() attributeList: Attribute[]; @@ -42,6 +42,7 @@ export class DatatableComponent implements OnInit { @Input() dataIsLoaded: boolean; @Input() selectedData: any[] = []; @Output() retrieveData: EventEmitter<Pagination> = new EventEmitter(); + @Output() updateOutputList: EventEmitter<number[]> = new EventEmitter(); @Output() addSelectedData: EventEmitter<number | string> = new EventEmitter(); @Output() deleteSelectedData: EventEmitter<number | string> = new EventEmitter(); @Output() downloadFile: EventEmitter<{url: string, filename: string}> = new EventEmitter(); @@ -62,6 +63,100 @@ export class DatatableComponent implements OnInit { })); } + ngOnChanges(changes: SimpleChanges) { + if (changes.dataIsLoaded && changes.dataIsLoaded.currentValue) { + Promise.resolve(null).then(() => { + // Query the table + const table = document.getElementById('datatable'); + + let dragSrcEl; + let outputSrcId; + let clonedOutputList; + const updateOutputList = this.updateOutputList; + + const getOutputList = () => [...this.outputList]; + + function handleDragStart(e) { + this.style.opacity = '0.4'; + clonedOutputList = getOutputList(); + + dragSrcEl = this; + for (let i = 0; i < items.length; i++) { + if (items[i] === this) { + outputSrcId = clonedOutputList[i]; + } + } + } + + function handleDragOver(e) { + e.preventDefault(); + return false; + } + + function handleDragEnter(e) { + this.classList.add('over'); + } + + function handleDragLeave(e) { + this.classList.remove('over'); + } + + function handleDragEnd(e) { + this.style.opacity = '1'; + + items.forEach(function (item) { + item.classList.remove('over'); + }); + } + + function handleDrop(e) { + e.stopPropagation(); // stops the browser from redirecting. + + if (dragSrcEl !== this) { + + // Remove src attribute ID into cloned output list + const index = clonedOutputList.indexOf(outputSrcId); + clonedOutputList.splice(index, 1); + + console.log(clonedOutputList); + + // Add src attribute ID + for (let i = 0; i < items.length; i++) { + if (items[i] === this) { + clonedOutputList.splice(i, 0, outputSrcId); + } + } + + // Dispatch new output list + updateOutputList.emit(clonedOutputList); + } + + return false; + } + + let items = table.querySelectorAll('.datatable-title'); + items.forEach(function (item) { + item.addEventListener('dragstart', handleDragStart); + item.addEventListener('dragover', handleDragOver); + item.addEventListener('dragenter', handleDragEnter); + item.addEventListener('dragleave', handleDragLeave); + item.addEventListener('dragend', handleDragEnd); + item.addEventListener('drop', handleDrop); + }); + }); + } + + if (changes.outputList && !changes.outputList.firstChange) { + Promise.resolve(null).then(() => this.retrieveData.emit({ + dname: this.dataset.name, + page: this.page, + nbItems: this.nbItems, + sortedCol: this.sortedCol, + order: this.sortedOrder + })); + } + } + /** * Returns renderer configuration for the given attribute. * @@ -97,8 +192,7 @@ export class DatatableComponent implements OnInit { * @return Attribute[] */ getOutputList(): Attribute[] { - return this.attributeList - .filter(a => this.outputList.includes(a.id)); + return this.outputList.map(attributeId => this.attributeList.find(a => a.id === attributeId)); } /** diff --git a/client/src/app/instance/search/containers/result.component.html b/client/src/app/instance/search/containers/result.component.html index 3bf167ec96ca6be076b8b88db8abf1b0d3290771..8e355cb02fdd2d4013733ce1d34d5da2c37842e4 100644 --- a/client/src/app/instance/search/containers/result.component.html +++ b/client/src/app/instance/search/containers/result.component.html @@ -104,6 +104,7 @@ [dataIsLoaded]="dataIsLoaded | async" [selectedData]="selectedData | async" (retrieveData)="retrieveData($event)" + (updateOutputList)="updateOutputList($event)" (addSelectedData)="addSearchData($event)" (deleteSelectedData)="deleteSearchData($event)" (downloadFile)="downloadFile($event)"> diff --git a/client/src/app/instance/search/containers/result.component.ts b/client/src/app/instance/search/containers/result.component.ts index b844c58262d593606dda50b1569c6144b319d695..83ab3e45bf985c4ade37ec1095ada5fb809058df 100644 --- a/client/src/app/instance/search/containers/result.component.ts +++ b/client/src/app/instance/search/containers/result.component.ts @@ -158,6 +158,15 @@ export class ResultComponent extends AbstractSearchComponent { this.store.dispatch(archiveActions.startTaskCreateArchive({ query })); } + /** + * Dispatches action to update output list selection with the given updated output list. + * + * @param {number[]} outputList - The updated output list. + */ + updateOutputList(outputList: number[]): void { + this.store.dispatch(searchActions.updateOutputList({ outputList })); + } + /** * Dispatches action to destroy search results. */