From 1a6504bfb0c7535005c35480d4cfc761ad7eb66b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Wed, 6 Jul 2022 21:50:14 +0200
Subject: [PATCH] Refactor detail page => WIP 2

---
 .../detail/default-object.component.html      | 11 ---
 .../detail/default-object.component.spec.ts   | 34 -------
 .../detail/default-object.component.ts        | 29 ------
 .../search/components/detail/index.ts         |  9 --
 .../detail/object-data.component.html         | 58 -----------
 .../detail/object-data.component.spec.ts      | 49 ----------
 .../detail/object-data.component.ts           | 86 -----------------
 .../search/components/detail/spectra/index.ts |  7 --
 .../spectra/spectra-object.component.html     | 35 -------
 .../spectra/spectra-object.component.scss     | 13 ---
 .../spectra/spectra-object.component.spec.ts  | 52 ----------
 .../spectra/spectra-object.component.ts       | 82 ----------------
 .../app/instance/search/components/index.ts   |  4 +-
 .../components/detail-content.component.html  |  6 +-
 .../components/detail-content.component.ts    | 18 +++-
 .../detail/containers/detail.component.html   |  1 +
 .../instance/search/detail/detail.module.ts   | 12 ++-
 .../dynamic-content/components/index.ts       | 14 ---
 .../display-object.component.html             |  0
 .../display-object.component.ts               |  5 +-
 .../display-ra-dec.component.html             | 11 +++
 .../display-ra-dec.component.ts               | 36 +++++++
 .../display-spectra.component.html            |  1 +
 .../display-spectra.component.ts              | 47 +++++++++
 .../dynamic-components/index.ts               | 12 ++-
 .../spectra-graph}/point.ts                   |  0
 .../dynamic-components/spectra-graph}/rays.ts |  0
 .../spectra-graph.component.html              |  0
 .../spectra-graph.component.scss              |  0
 .../spectra-graph}/spectra-graph.component.ts |  0
 .../spectra-graph}/spectra-type.ts            |  0
 .../dynamic-content/dynamic-content.module.ts | 53 ----------
 .../search/detail/dynamic-content/index.ts    | 14 +++
 .../detail/dynamic-content/parsers/index.ts   |  4 +-
 .../instance/store/effects/detail.effects.ts  |  4 +-
 .../instance/store/services/detail.service.ts |  4 +-
 .../components/datatable.component.html       |  0
 .../components/datatable.component.ts         |  0
 .../app/instance/webpage/components/index.ts  |  4 +-
 .../components/webpage-content.component.html |  2 +-
 .../components/webpage-content.component.ts   | 14 ++-
 .../dynamic-content/components/index.ts       | 14 ---
 .../dynamic-router-link.component.html        | 18 ----
 .../dynamic-router-link.component.scss        |  3 -
 .../dynamic-router-link.component.ts          | 18 ----
 .../dynamic-components/index.ts               |  4 +-
 .../dynamic-content/dynamic-content.module.ts | 53 ----------
 .../instance/webpage/dynamic-content/index.ts | 14 +++
 .../parsers/dynamic-router-link-parser.ts     | 96 -------------------
 .../webpage/dynamic-content/parsers/index.ts  |  4 +-
 .../app/instance/webpage/webpage.module.ts    | 12 ++-
 .../dynamic-router-link.component.html        |  0
 .../dynamic-router-link.component.scss        |  0
 .../dynamic-router-link.component.ts          |  0
 .../dynamic-components/index.ts               |  5 +
 .../src/app/shared/dynamic-content/index.ts   | 14 +++
 .../parsers/dynamic-router-link-parser.ts     |  2 +-
 .../shared/dynamic-content/parsers/index.ts   |  5 +
 client/src/app/shared/shared.module.ts        | 18 +++-
 conf-dev/create-db.sh                         |  8 +-
 60 files changed, 244 insertions(+), 775 deletions(-)
 delete mode 100644 client/src/app/instance/search/components/detail/default-object.component.html
 delete mode 100644 client/src/app/instance/search/components/detail/default-object.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/detail/default-object.component.ts
 delete mode 100644 client/src/app/instance/search/components/detail/index.ts
 delete mode 100644 client/src/app/instance/search/components/detail/object-data.component.html
 delete mode 100644 client/src/app/instance/search/components/detail/object-data.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/detail/object-data.component.ts
 delete mode 100644 client/src/app/instance/search/components/detail/spectra/index.ts
 delete mode 100644 client/src/app/instance/search/components/detail/spectra/spectra-object.component.html
 delete mode 100644 client/src/app/instance/search/components/detail/spectra/spectra-object.component.scss
 delete mode 100644 client/src/app/instance/search/components/detail/spectra/spectra-object.component.spec.ts
 delete mode 100644 client/src/app/instance/search/components/detail/spectra/spectra-object.component.ts
 delete mode 100644 client/src/app/instance/search/detail/dynamic-content/components/index.ts
 rename client/src/app/instance/search/detail/dynamic-content/{components => dynamic-components}/display-object.component.html (100%)
 rename client/src/app/instance/search/detail/dynamic-content/{components => dynamic-components}/display-object.component.ts (85%)
 create mode 100644 client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-ra-dec.component.html
 create mode 100644 client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-ra-dec.component.ts
 create mode 100644 client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-spectra.component.html
 create mode 100644 client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-spectra.component.ts
 rename client/src/app/instance/search/{components/detail/spectra/graph => detail/dynamic-content/dynamic-components/spectra-graph}/point.ts (100%)
 rename client/src/app/instance/search/{components/detail/spectra/graph => detail/dynamic-content/dynamic-components/spectra-graph}/rays.ts (100%)
 rename client/src/app/instance/search/{components/detail/spectra/graph => detail/dynamic-content/dynamic-components/spectra-graph}/spectra-graph.component.html (100%)
 rename client/src/app/instance/search/{components/detail/spectra/graph => detail/dynamic-content/dynamic-components/spectra-graph}/spectra-graph.component.scss (100%)
 rename client/src/app/instance/search/{components/detail/spectra/graph => detail/dynamic-content/dynamic-components/spectra-graph}/spectra-graph.component.ts (100%)
 rename client/src/app/instance/search/{components/detail/spectra/graph => detail/dynamic-content/dynamic-components/spectra-graph}/spectra-type.ts (100%)
 delete mode 100644 client/src/app/instance/search/detail/dynamic-content/dynamic-content.module.ts
 create mode 100644 client/src/app/instance/search/detail/dynamic-content/index.ts
 rename client/src/app/instance/webpage/{dynamic-content => }/components/datatable.component.html (100%)
 rename client/src/app/instance/webpage/{dynamic-content => }/components/datatable.component.ts (100%)
 delete mode 100644 client/src/app/instance/webpage/dynamic-content/components/index.ts
 delete mode 100644 client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.html
 delete mode 100644 client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.scss
 delete mode 100644 client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.ts
 delete mode 100644 client/src/app/instance/webpage/dynamic-content/dynamic-content.module.ts
 create mode 100644 client/src/app/instance/webpage/dynamic-content/index.ts
 delete mode 100644 client/src/app/instance/webpage/dynamic-content/parsers/dynamic-router-link-parser.ts
 rename client/src/app/{instance/search/detail => shared}/dynamic-content/dynamic-components/dynamic-router-link.component.html (100%)
 rename client/src/app/{instance/search/detail => shared}/dynamic-content/dynamic-components/dynamic-router-link.component.scss (100%)
 rename client/src/app/{instance/search/detail => shared}/dynamic-content/dynamic-components/dynamic-router-link.component.ts (100%)
 create mode 100644 client/src/app/shared/dynamic-content/dynamic-components/index.ts
 create mode 100644 client/src/app/shared/dynamic-content/index.ts
 rename client/src/app/{instance/search/detail => shared}/dynamic-content/parsers/dynamic-router-link-parser.ts (99%)
 create mode 100644 client/src/app/shared/dynamic-content/parsers/index.ts

diff --git a/client/src/app/instance/search/components/detail/default-object.component.html b/client/src/app/instance/search/components/detail/default-object.component.html
deleted file mode 100644
index bc6843ae..00000000
--- a/client/src/app/instance/search/components/detail/default-object.component.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<div class="row justify-content-center">
-    <div class="col col-lg-10 col-xl-8 mt-4">
-        <app-object-data
-            [datasetSelected]="datasetSelected"
-            [outputFamilyList]="outputFamilyList"
-            [outputCategoryList]="outputCategoryList"
-            [attributeList]="attributeList"
-            [object]="object">
-        </app-object-data>
-    </div>
-</div>
diff --git a/client/src/app/instance/search/components/detail/default-object.component.spec.ts b/client/src/app/instance/search/components/detail/default-object.component.spec.ts
deleted file mode 100644
index 24698efe..00000000
--- a/client/src/app/instance/search/components/detail/default-object.component.spec.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { Component, Input } from '@angular/core';
-
-import { DefaultObjectComponent } from './default-object.component';
-import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models';
-
-describe('[Instance][Search][Component][Detail][Default] DefaultObjectComponent', () => {
-    @Component({ selector: 'app-object-data', template: '' })
-    class ObjectDataStubComponent {
-        @Input() datasetSelected: string;
-        @Input() outputFamilyList: OutputFamily[];
-        @Input() outputCategoryList: OutputCategory[];
-        @Input() attributeList: Attribute[];
-        @Input() object: any;
-    }
-
-    let component: DefaultObjectComponent;
-    let fixture: ComponentFixture<DefaultObjectComponent>;
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                DefaultObjectComponent,
-                ObjectDataStubComponent
-            ]
-        });
-        fixture = TestBed.createComponent(DefaultObjectComponent);
-        component = fixture.componentInstance;
-    }));
-
-    it('should create the component', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/client/src/app/instance/search/components/detail/default-object.component.ts b/client/src/app/instance/search/components/detail/default-object.component.ts
deleted file mode 100644
index b690b814..00000000
--- a/client/src/app/instance/search/components/detail/default-object.component.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
-
-import { Attribute, OutputFamily, OutputCategory } from 'src/app/metamodel/models';
-
-/**
- * @class
- * @classdesc Detail default object component.
- */
-@Component({
-    selector: 'app-default-object',
-    templateUrl: 'default-object.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class DefaultObjectComponent {
-    @Input() datasetSelected: string;
-    @Input() outputFamilyList: OutputFamily[];
-    @Input() outputCategoryList: OutputCategory[];
-    @Input() attributeList: Attribute[];
-    @Input() object: any;
-}
diff --git a/client/src/app/instance/search/components/detail/index.ts b/client/src/app/instance/search/components/detail/index.ts
deleted file mode 100644
index 86a0c1e9..00000000
--- a/client/src/app/instance/search/components/detail/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { ObjectDataComponent } from './object-data.component';
-import { DefaultObjectComponent } from './default-object.component';
-import { spectraComponents } from './spectra';
-
-export const detailsComponents = [
-    ObjectDataComponent,
-    DefaultObjectComponent,
-    spectraComponents
-];
diff --git a/client/src/app/instance/search/components/detail/object-data.component.html b/client/src/app/instance/search/components/detail/object-data.component.html
deleted file mode 100644
index e9411be0..00000000
--- a/client/src/app/instance/search/components/detail/object-data.component.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<div *ngIf="getAttributeRa() && getAttributeDec()" class="row">
-    <div class="col-12">
-        <table class="table mb-1" aria-describedby="Object coordinates">
-            <tr>
-                <th scope="col">Alpha</th>
-                <th scope="col">Delta</th>
-                <th scope="col" class="text-center" rowspan="2"><img src="assets/cesam_anis80.png" alt="CeSAM logo" /></th>
-            </tr>
-            <tr>
-                <td>{{ object[getAttributeRa().label] }}</td>
-                <td>{{ object[getAttributeDec().label] }}</td>
-            </tr>
-        </table>
-        <hr class="mt-0 mb-4">
-    </div>
-</div>
-
-<!-- Accordion families -->
-<accordion [isAnimated]="true">
-    <accordion-group *ngFor="let family of outputFamilyList" #ag [isOpen]="true" class="pl-2">
-        <button class="btn btn-link btn-block clearfix pb-2" accordion-heading>
-            <span class="pull-left float-left text-primary">
-                {{ family.label }}
-                &nbsp;
-                <span *ngIf="ag.isOpen">
-                    <span class="fas fa-chevron-up"></span>
-                </span>
-                <span *ngIf="!ag.isOpen">
-                    <span class="fas fa-chevron-down"></span>
-                </span>
-            </span>
-        </button>
-
-        <!-- Accordion categories -->
-        <accordion [isAnimated]="true">
-            <accordion-group *ngFor="let category of getCategoryByFamily(family.id)" #ag [isOpen]="true" class="pl-4">
-                <button class="btn btn-link btn-block clearfix pb-2" accordion-heading>
-                    <span class="pull-left float-left text-primary">
-                        {{ category.label }}
-                        &nbsp;
-                        <span *ngIf="ag.isOpen">
-                            <span class="fas fa-chevron-up"></span>
-                        </span>
-                        <span *ngIf="!ag.isOpen">
-                            <span class="fas fa-chevron-down"></span>
-                        </span>
-                    </span>
-                </button>
-
-                <!-- Output list -->
-                <div *ngFor="let attribute of getAttributesVisibleByCategory(category.id)" class="row pb-2">
-                    <div class="col-5 font-weight-bold">{{ attribute.form_label }}</div>
-                    <div class="col">{{ object[attribute.label] }}</div>
-                </div>
-            </accordion-group>
-        </accordion>
-    </accordion-group>
-</accordion>
diff --git a/client/src/app/instance/search/components/detail/object-data.component.spec.ts b/client/src/app/instance/search/components/detail/object-data.component.spec.ts
deleted file mode 100644
index cdc9ba29..00000000
--- a/client/src/app/instance/search/components/detail/object-data.component.spec.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-
-import { AccordionModule } from 'ngx-bootstrap/accordion';
-
-import { ObjectDataComponent } from './object-data.component';
-import { AppConfigService } from '../../../../app-config.service';
-import { ATTRIBUTE_LIST, CATEGORY_LIST } from '../../../../../test-data';
-
-describe('[Instance][Search][Component][Detail] ObjectDataComponent', () => {
-    let component: ObjectDataComponent;
-    let fixture: ComponentFixture<ObjectDataComponent>;
-    let appConfigServiceStub = new AppConfigService();
-
-    beforeEach(waitForAsync(() => {
-        TestBed.configureTestingModule({
-            declarations: [ObjectDataComponent],
-            imports: [AccordionModule.forRoot()],
-            providers: [{ provide: AppConfigService, useValue: appConfigServiceStub }]
-        });
-        fixture = TestBed.createComponent(ObjectDataComponent);
-        component = fixture.componentInstance;
-    }));
-
-    it('should create the component', () => {
-        expect(component).toBeTruthy();
-    });
-
-    it('#getCategoryByFamily() should return categories for the given family', () => {
-        component.outputCategoryList = CATEGORY_LIST;
-        expect(component.getCategoryByFamily(1).length).toEqual(2);
-    });
-
-    it('#getAttributesVisibleByCategory() should return visible attributes for the given category', () => {
-        component.attributeList = ATTRIBUTE_LIST;
-        expect(component.getAttributesVisibleByCategory(1).length).toEqual(1);
-        expect(component.getAttributesVisibleByCategory(1)[0].id).toEqual(2);
-    });
-
-    it('#getAttributesVisible() should return visible attributes', () => {
-        component.attributeList = ATTRIBUTE_LIST;
-        expect(component.getAttributesVisible().length).toEqual(2);
-    });
-
-    it('#getDownloadHref() should return URL', () => {
-        appConfigServiceStub.apiUrl = 'http://test.com';
-        component.datasetSelected = 'myDataset';
-        expect(component.getDownloadHref('myAttributeLabel')).toEqual('http://test.com/download-file/myDataset/myAttributeLabel');
-    });
-});
diff --git a/client/src/app/instance/search/components/detail/object-data.component.ts b/client/src/app/instance/search/components/detail/object-data.component.ts
deleted file mode 100644
index 34068a6f..00000000
--- a/client/src/app/instance/search/components/detail/object-data.component.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
-
-import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models';
-import { getHost } from 'src/app/shared/utils';
-import { AppConfigService } from 'src/app/app-config.service';
-
-/**
- * @class
- * @classdesc Detail object data component.
- */
-@Component({
-    selector: 'app-object-data',
-    templateUrl: 'object-data.component.html',
-    changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class ObjectDataComponent {
-    @Input() datasetSelected: string;
-    @Input() outputFamilyList: OutputFamily[];
-    @Input() outputCategoryList: OutputCategory[];
-    @Input() attributeList: Attribute[];
-    @Input() object: any;
-
-    constructor(private appConfig: AppConfigService) { }
-
-    /**
-     * Returns category list for the given output family ID.
-     *
-     * @param  {number} idFamily - The output family ID.
-     *
-     * @return OutputCategory[]
-     */
-    getCategoryByFamily(idFamily: number): OutputCategory[] {
-        return this.outputCategoryList
-            .filter(category => category.id_output_family === idFamily);
-    }
-
-    /**
-     * Returns attribute list for the given output category ID that are visible in detail page.
-     *
-     * @param  {number} idCategory - The output category ID.
-     *
-     * @return Attribute[]
-     */
-    getAttributesVisibleByCategory(idCategory: number): Attribute[] {
-        return this.attributeList
-            .filter(a => a.id_detail_output_category)
-            .filter(a => a.id_output_category === idCategory);
-    }
-
-    /**
-     * Returns attribute list that are visible in detail page.
-     *
-     * @return Attribute[]
-     */
-    getAttributesVisible(): Attribute[] {
-        return this.attributeList.filter(a => a.id_detail_output_category);
-    }
-
-    /**
-     * Returns URL where download object.
-     *
-     * @param  {string} attributeLabel - The attribute label.
-     *
-     * @return string
-     */
-    getDownloadHref(attributeLabel: string): string {
-        return `${getHost(this.appConfig.apiUrl)}/download-file/${this.datasetSelected}/${attributeLabel}`;
-    }
-
-    getAttributeRa() {
-        return this.attributeList[2];
-    }
-
-    getAttributeDec() {
-        return this.attributeList[3];
-    }
-}
diff --git a/client/src/app/instance/search/components/detail/spectra/index.ts b/client/src/app/instance/search/components/detail/spectra/index.ts
deleted file mode 100644
index 77b5e039..00000000
--- a/client/src/app/instance/search/components/detail/spectra/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { SpectraObjectComponent } from "./spectra-object.component";
-import { SpectraGraphComponent } from "./graph/spectra-graph.component";
-
-export const spectraComponents = [
-    SpectraObjectComponent,
-    SpectraGraphComponent
-];
diff --git a/client/src/app/instance/search/components/detail/spectra/spectra-object.component.html b/client/src/app/instance/search/components/detail/spectra/spectra-object.component.html
deleted file mode 100644
index 8e1360dc..00000000
--- a/client/src/app/instance/search/components/detail/spectra/spectra-object.component.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<div class="row">
-    <div *ngIf="getAttributeSpectraGraph()" class="col col-md-8 col-sm-12">
-        <div *ngIf="spectraIsLoading" id="div-spinner" class="text-center">
-            <span class="fas fa-circle-notch fa-spin fa-3x"></span>
-            <span class="sr-only">Loading...</span>
-        </div>
-        <app-spectra-graph *ngIf="spectraIsLoaded" [z]="getZ()" [spectraCSV]="spectraCSV"></app-spectra-graph>
-    </div>
-
-    <div [ngClass]="{'col-md-4 col-sm-12': getAttributeSpectraGraph()}" class="col mt-4">
-        <div *ngIf="getSpectra()" class="jumbotron row mb-3 p-4">
-            <div class="col-auto align-self-center">
-                <p>Download:</p>
-            </div>
-            <div class="w-100 d-block d-xl-none"></div>
-            <div class="col">
-                <div class="row justify-content-center">
-                    <div class="col-auto">
-                        <a [href]="getSpectra()" class="btn btn-lg btn-block dl-btn">
-                            Download SPECTRA archive
-                        </a>
-                    </div>
-                </div>
-            </div>
-        </div>
-
-        <app-object-data
-            [datasetSelected]="datasetSelected"
-            [outputFamilyList]="outputFamilyList"
-            [outputCategoryList]="outputCategoryList"
-            [attributeList]="attributeList"
-            [object]="object">
-        </app-object-data>
-    </div>
-</div>
diff --git a/client/src/app/instance/search/components/detail/spectra/spectra-object.component.scss b/client/src/app/instance/search/components/detail/spectra/spectra-object.component.scss
deleted file mode 100644
index 260a2036..00000000
--- a/client/src/app/instance/search/components/detail/spectra/spectra-object.component.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-.dl-btn {
-    height: 80px;
-    display: inline-block;
-}
diff --git a/client/src/app/instance/search/components/detail/spectra/spectra-object.component.spec.ts b/client/src/app/instance/search/components/detail/spectra/spectra-object.component.spec.ts
deleted file mode 100644
index 890caacf..00000000
--- a/client/src/app/instance/search/components/detail/spectra/spectra-object.component.spec.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { Component, Input } from '@angular/core';
-
-import { SpectraObjectComponent } from './spectra-object.component';
-import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models';
-import { AppConfigService } from 'src/app/app-config.service';
-import { ATTRIBUTE_LIST, OBJECT_DETAIL } from 'src/test-data';
-
-describe('[Instance][Search][Component][Detail][Spectra] SpectraObjectComponent', () => {
-    @Component({ selector: 'app-spectra-graph', template: '' })
-    class SpectraGraphStubComponent {
-        @Input() z: number;
-        @Input() spectraCSV: string;
-    }
-
-    @Component({ selector: 'app-object-data', template: '' })
-    class ObjectDataStubComponent {
-        @Input() datasetSelected: string;
-        @Input() outputFamilyList: OutputFamily[];
-        @Input() outputCategoryList: OutputCategory[];
-        @Input() attributeList: Attribute[];
-        @Input() object: any;
-    }
-
-    let component: SpectraObjectComponent;
-    let fixture: ComponentFixture<SpectraObjectComponent>;
-    let appConfigServiceStub = new AppConfigService();
-
-    beforeEach(() => {
-        TestBed.configureTestingModule({
-            declarations: [
-                SpectraObjectComponent,
-                SpectraGraphStubComponent,
-                ObjectDataStubComponent
-            ],
-            providers: [{ provide: AppConfigService, useValue: appConfigServiceStub }]
-        });
-        fixture = TestBed.createComponent(SpectraObjectComponent);
-        component = fixture.componentInstance;
-    });
-
-    it('should create the component', () => {
-        expect(component).toBeTruthy();
-    });
-
-    it('#ngOnInit() should emit getSpectraCSV event if an attribute have spectra_graph renderer_detail', () => {
-        component.attributeList = ATTRIBUTE_LIST;
-        component.object = OBJECT_DETAIL;
-        component.getSpectraCSV.subscribe((event: string) => expect(event).toEqual('spec1d'));
-        component.ngOnInit();
-    });
-});
diff --git a/client/src/app/instance/search/components/detail/spectra/spectra-object.component.ts b/client/src/app/instance/search/components/detail/spectra/spectra-object.component.ts
deleted file mode 100644
index aa64933c..00000000
--- a/client/src/app/instance/search/components/detail/spectra/spectra-object.component.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-import { Component, Input, ChangeDetectionStrategy, Output, EventEmitter, OnInit } from '@angular/core';
-
-import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models';
-import { getHost } from 'src/app/shared/utils';
-import { AppConfigService } from 'src/app/app-config.service';
-
-/**
- * @class
- * @classdesc Detail spectra object component.
- *
- * @implements OnInit
- */
-@Component({
-    selector: 'app-spectra-object',
-    templateUrl: 'spectra-object.component.html',
-    styleUrls: ['spectra-object.component.scss'],
-    changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class SpectraObjectComponent implements OnInit {
-    @Input() datasetSelected: string;
-    @Input() outputFamilyList: OutputFamily[];
-    @Input() outputCategoryList: OutputCategory[];
-    @Input() attributeList: Attribute[];
-    @Input() object: any;
-    @Input() spectraIsLoading: boolean;
-    @Input() spectraIsLoaded: boolean;
-    @Input() spectraCSV: string;
-    @Output() getSpectraCSV: EventEmitter<string> = new EventEmitter();
-
-    constructor(private appConfig: AppConfigService) { }
-
-    ngOnInit(): void {
-        const attributeSpectraGraph = this.getAttributeSpectraGraph();
-        if (attributeSpectraGraph) {
-            Promise.resolve(null).then(() => this.getSpectraCSV.emit(this.object[attributeSpectraGraph.label]));
-        }
-    }
-
-    /**
-     * Returns spectra file URL.
-     *
-     * @return string
-     */
-    getSpectra(): string {
-        const spectraAttribute = this.attributeList[0];
-        return `${getHost(this.appConfig.apiUrl)}/download-file/${this.datasetSelected}/${this.object[spectraAttribute.label]}`;
-    }
-
-    /**
-     * Returns detail rendered spectra graph attribute.
-     *
-     * @return Attribute
-     */
-    getAttributeSpectraGraph(): Attribute {
-        return this.attributeList[1];
-    }
-
-    /**
-     * Returns Z.
-     *
-     * @return number
-     */
-    getZ(): number {
-        return 0;
-        // const spectraGraphRendererConfig = this.getAttributeSpectraGraph().renderer_detail_config as SpectraGraphRendererConfig;
-        // if (spectraGraphRendererConfig.z) {
-        //     const attributeZ = this.attributeList.find(attribute => attribute.id === spectraGraphRendererConfig.z);
-        //     return +this.object[attributeZ.label];
-        // } else {
-        //     return 0;
-        // }
-    }
-}
diff --git a/client/src/app/instance/search/components/index.ts b/client/src/app/instance/search/components/index.ts
index f4057d78..9a0df624 100644
--- a/client/src/app/instance/search/components/index.ts
+++ b/client/src/app/instance/search/components/index.ts
@@ -4,7 +4,6 @@ import { datasetComponents } from './dataset';
 import { criteriaComponents } from './criteria';
 import { outputComponents } from './output';
 import { resultComponents } from './result';
-import { detailsComponents } from './detail';
 
 export const dummiesComponents = [
     ProgressBarComponent,
@@ -12,6 +11,5 @@ export const dummiesComponents = [
     datasetComponents,
     criteriaComponents,
     outputComponents,
-    resultComponents,
-    detailsComponents
+    resultComponents
 ];
\ No newline at end of file
diff --git a/client/src/app/instance/search/detail/components/detail-content.component.html b/client/src/app/instance/search/detail/components/detail-content.component.html
index daab406d..b0961d8f 100644
--- a/client/src/app/instance/search/detail/components/detail-content.component.html
+++ b/client/src/app/instance/search/detail/components/detail-content.component.html
@@ -1 +1,5 @@
-<ngx-dynamic-hooks [content]="detailConfig.content" [context]="getContext()"></ngx-dynamic-hooks>
\ No newline at end of file
+<ngx-dynamic-hooks 
+    [content]="detailConfig.content"
+    [parsers]="getParsers()"
+    [context]="getContext()">
+</ngx-dynamic-hooks>
diff --git a/client/src/app/instance/search/detail/components/detail-content.component.ts b/client/src/app/instance/search/detail/components/detail-content.component.ts
index 4e1adf83..ca7dfd60 100644
--- a/client/src/app/instance/search/detail/components/detail-content.component.ts
+++ b/client/src/app/instance/search/detail/components/detail-content.component.ts
@@ -7,9 +7,11 @@
  * file that was distributed with this source code.
  */
 
-import { Component, Input } from '@angular/core';
+import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 
-import { DetailConfig, Attribute, OutputFamily, OutputCategory} from 'src/app/metamodel/models';
+import { DetailConfig, Dataset, Attribute, OutputFamily, OutputCategory } from 'src/app/metamodel/models';
+import { globalParsers } from 'src/app/shared/dynamic-content';
+import { componentParsers } from '../dynamic-content';
 
 /**
  * @class
@@ -17,18 +19,28 @@ import { DetailConfig, Attribute, OutputFamily, OutputCategory} from 'src/app/me
  */
 @Component({
     selector: 'app-detail-content',
-    templateUrl: 'detail-content.component.html'
+    templateUrl: 'detail-content.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class DetailContentComponent {
     @Input() detailConfig: DetailConfig;
     @Input() object: any;
+    @Input() datasetName: string;
     @Input() attributeList: Attribute[];
     @Input() outputFamilyList: OutputFamily[];
     @Input() outputCategoryList: OutputCategory[];
 
+    getParsers() {
+        return [
+            ...globalParsers,
+            ...componentParsers
+        ];
+    }
+
     getContext() {
         return {
             object: this.object,
+            datasetName: this.datasetName,
             attributeList: this.attributeList,
             outputFamilyList: this.outputFamilyList,
             outputCategoryList: this.outputCategoryList
diff --git a/client/src/app/instance/search/detail/containers/detail.component.html b/client/src/app/instance/search/detail/containers/detail.component.html
index 9fc60ad4..22aab0c4 100644
--- a/client/src/app/instance/search/detail/containers/detail.component.html
+++ b/client/src/app/instance/search/detail/containers/detail.component.html
@@ -20,6 +20,7 @@
         <app-detail-content 
             [detailConfig]="detailConfig | async"
             [object]="object | async"
+            [datasetName]="datasetSelected | async"
             [attributeList]="attributeList | async"
             [outputFamilyList]="outputFamilyList | async"
             [outputCategoryList]="outputCategoryList | async">
diff --git a/client/src/app/instance/search/detail/detail.module.ts b/client/src/app/instance/search/detail/detail.module.ts
index a23e0508..f272f917 100644
--- a/client/src/app/instance/search/detail/detail.module.ts
+++ b/client/src/app/instance/search/detail/detail.module.ts
@@ -10,9 +10,9 @@
 import { NgModule } from '@angular/core';
 
 import { SharedModule } from 'src/app/shared/shared.module';
-import { DynamicContentModule } from './dynamic-content/dynamic-content.module';
 import { DetailRoutingModule, routedComponents } from './detail-routing.module';
 import { dummiesComponents } from './components';
+import { hookParsers, dynamicComponents } from './dynamic-content';
 
 /**
  * @class
@@ -21,12 +21,18 @@ import { dummiesComponents } from './components';
 @NgModule({
     imports: [
         SharedModule,
-        DynamicContentModule,
         DetailRoutingModule
     ],
     declarations: [
         routedComponents,
-        dummiesComponents
+        dummiesComponents,
+        dynamicComponents
+    ],
+    entryComponents: [
+        dynamicComponents
+    ],
+    providers: [
+        hookParsers
     ]
 })
 export class DetailModule { }
diff --git a/client/src/app/instance/search/detail/dynamic-content/components/index.ts b/client/src/app/instance/search/detail/dynamic-content/components/index.ts
deleted file mode 100644
index ff4f0ea4..00000000
--- a/client/src/app/instance/search/detail/dynamic-content/components/index.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-import { DisplayObjectComponent } from './display-object.component';
-
-export const dummiesComponents = [
-    DisplayObjectComponent
-];
diff --git a/client/src/app/instance/search/detail/dynamic-content/components/display-object.component.html b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-object.component.html
similarity index 100%
rename from client/src/app/instance/search/detail/dynamic-content/components/display-object.component.html
rename to client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-object.component.html
diff --git a/client/src/app/instance/search/detail/dynamic-content/components/display-object.component.ts b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-object.component.ts
similarity index 85%
rename from client/src/app/instance/search/detail/dynamic-content/components/display-object.component.ts
rename to client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-object.component.ts
index c290ab7e..1c029c11 100644
--- a/client/src/app/instance/search/detail/dynamic-content/components/display-object.component.ts
+++ b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-object.component.ts
@@ -7,7 +7,7 @@
  * file that was distributed with this source code.
  */
 
-import { Component, Input } from '@angular/core';
+import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 
 import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/models';
 
@@ -17,7 +17,8 @@ import { Attribute, OutputCategory, OutputFamily } from 'src/app/metamodel/model
  */
 @Component({
     selector: 'app-display-object',
-    templateUrl: 'display-object.component.html'
+    templateUrl: 'display-object.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class DisplayObjectComponent {
     @Input() object: any;
diff --git a/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-ra-dec.component.html b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-ra-dec.component.html
new file mode 100644
index 00000000..b779cc47
--- /dev/null
+++ b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-ra-dec.component.html
@@ -0,0 +1,11 @@
+<table class="table mb-1" aria-describedby="Object coordinates">
+    <tr>
+        <th scope="col">Alpha</th>
+        <th scope="col">Delta</th>
+        <th scope="col" class="text-center" rowspan="2"><img src="assets/cesam_anis80.png" alt="CeSAM logo" /></th>
+    </tr>
+    <tr>
+        <td>{{ object[getAttributeRa().label] }}</td>
+        <td>{{ object[getAttributeDec().label] }}</td>
+    </tr>
+</table>
diff --git a/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-ra-dec.component.ts b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-ra-dec.component.ts
new file mode 100644
index 00000000..4715be9b
--- /dev/null
+++ b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-ra-dec.component.ts
@@ -0,0 +1,36 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
+
+import { Attribute } from 'src/app/metamodel/models';
+
+/**
+ * @class
+ * @classdesc Display object component.
+ */
+@Component({
+    selector: 'app-display-ra-dec',
+    templateUrl: 'display-ra-dec.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class DisplayRaDecComponent {
+    @Input() object: any;
+    @Input() attributeList: Attribute[];
+    @Input() attributeRaId: number;
+    @Input() attributeDecId: number;
+
+    getAttributeRa() {
+        return this.attributeList.find(attribute => attribute.id === this.attributeRaId);
+    }
+
+    getAttributeDec() {
+        return this.attributeList.find(attribute => attribute.id === this.attributeDecId);
+    }
+}
diff --git a/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-spectra.component.html b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-spectra.component.html
new file mode 100644
index 00000000..f908a1e0
--- /dev/null
+++ b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-spectra.component.html
@@ -0,0 +1 @@
+<app-spectra-graph *ngIf="spectraCSV | async as data" [z]="getZ()" [spectraCSV]="data"></app-spectra-graph>
diff --git a/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-spectra.component.ts b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-spectra.component.ts
new file mode 100644
index 00000000..fcf7ffed
--- /dev/null
+++ b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/display-spectra.component.ts
@@ -0,0 +1,47 @@
+/**
+ * This file is part of Anis Client.
+ *
+ * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { Observable } from 'rxjs';
+
+import { Attribute } from 'src/app/metamodel/models';
+import { AppConfigService } from 'src/app/app-config.service';
+
+/**
+ * @class
+ * @classdesc Display spectra component.
+ */
+@Component({
+    selector: 'app-display-spectra',
+    templateUrl: 'display-spectra.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class DisplaySpectraComponent {
+    @Input() object: any;
+    @Input() datasetName: string;
+    @Input() attributeList: Attribute[];
+    @Input() attributeSpectraId: number;
+    @Input() attributeZId: number;
+
+    spectraCSV: Observable<string>;
+
+    constructor(private http: HttpClient, private config: AppConfigService) { }
+
+    ngOnInit() {
+        const spectraFile = this.object[this.attributeList.find(attribute => attribute.id === this.attributeSpectraId).label];
+        this.spectraCSV = this.http.get(`${this.config.servicesUrl}/spectra-to-csv/${this.datasetName}?filename=${spectraFile}`, { responseType: 'text' });
+    }
+
+    getZ() {
+        const z = this.object[this.attributeList.find(attribute => attribute.id === this.attributeZId).label];
+        return +z;
+    }
+}
diff --git a/client/src/app/instance/search/detail/dynamic-content/dynamic-components/index.ts b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/index.ts
index 0a6b8f24..8c84be9b 100644
--- a/client/src/app/instance/search/detail/dynamic-content/dynamic-components/index.ts
+++ b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/index.ts
@@ -1,5 +1,11 @@
-import { DynamicRouterLinkComponent } from './dynamic-router-link.component';
+import { DisplayObjectComponent } from './display-object.component';
+import { DisplayRaDecComponent } from './display-ra-dec.component';
+import { DisplaySpectraComponent } from './display-spectra.component';
+import { SpectraGraphComponent } from './spectra-graph/spectra-graph.component';
 
-export const DynamicComponents = [
-    DynamicRouterLinkComponent
+export const dynamicComponents = [
+    DisplayObjectComponent,
+    DisplayRaDecComponent,
+    DisplaySpectraComponent,
+    SpectraGraphComponent
 ];
diff --git a/client/src/app/instance/search/components/detail/spectra/graph/point.ts b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/point.ts
similarity index 100%
rename from client/src/app/instance/search/components/detail/spectra/graph/point.ts
rename to client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/point.ts
diff --git a/client/src/app/instance/search/components/detail/spectra/graph/rays.ts b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/rays.ts
similarity index 100%
rename from client/src/app/instance/search/components/detail/spectra/graph/rays.ts
rename to client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/rays.ts
diff --git a/client/src/app/instance/search/components/detail/spectra/graph/spectra-graph.component.html b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/spectra-graph.component.html
similarity index 100%
rename from client/src/app/instance/search/components/detail/spectra/graph/spectra-graph.component.html
rename to client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/spectra-graph.component.html
diff --git a/client/src/app/instance/search/components/detail/spectra/graph/spectra-graph.component.scss b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/spectra-graph.component.scss
similarity index 100%
rename from client/src/app/instance/search/components/detail/spectra/graph/spectra-graph.component.scss
rename to client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/spectra-graph.component.scss
diff --git a/client/src/app/instance/search/components/detail/spectra/graph/spectra-graph.component.ts b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/spectra-graph.component.ts
similarity index 100%
rename from client/src/app/instance/search/components/detail/spectra/graph/spectra-graph.component.ts
rename to client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/spectra-graph.component.ts
diff --git a/client/src/app/instance/search/components/detail/spectra/graph/spectra-type.ts b/client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/spectra-type.ts
similarity index 100%
rename from client/src/app/instance/search/components/detail/spectra/graph/spectra-type.ts
rename to client/src/app/instance/search/detail/dynamic-content/dynamic-components/spectra-graph/spectra-type.ts
diff --git a/client/src/app/instance/search/detail/dynamic-content/dynamic-content.module.ts b/client/src/app/instance/search/detail/dynamic-content/dynamic-content.module.ts
deleted file mode 100644
index 8f4f2781..00000000
--- a/client/src/app/instance/search/detail/dynamic-content/dynamic-content.module.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-import { NgModule } from '@angular/core';
-import { RouterModule } from '@angular/router';
-
-import { DynamicHooksModule, HookParserEntry } from 'ngx-dynamic-hooks';
-
-import { SharedModule } from 'src/app/shared/shared.module';
-import { hookParsers } from './parsers';
-import { DynamicComponents } from './dynamic-components';
-import { dummiesComponents } from './components';
-
-export const componentParsers: Array<HookParserEntry> = [
-    ...hookParsers,
-    ...DynamicComponents.map(component => {
-        return { component };
-    })
-];
-
-/**
- * @class
- * @classdesc Dynamic content module.
- */
-@NgModule({
-    imports: [
-        SharedModule,
-        RouterModule,
-        DynamicHooksModule.forRoot({
-            globalParsers: componentParsers
-        }),
-    ],
-    exports: [
-        DynamicHooksModule
-    ],
-    providers: [
-        hookParsers
-    ],
-    declarations: [
-        DynamicComponents,
-        dummiesComponents
-    ],
-    entryComponents: [
-        DynamicComponents
-    ]
-})
-export class DynamicContentModule { }
diff --git a/client/src/app/instance/search/detail/dynamic-content/index.ts b/client/src/app/instance/search/detail/dynamic-content/index.ts
new file mode 100644
index 00000000..a8a58f22
--- /dev/null
+++ b/client/src/app/instance/search/detail/dynamic-content/index.ts
@@ -0,0 +1,14 @@
+import { HookParserEntry } from 'ngx-dynamic-hooks';
+
+import { hookParsers } from './parsers';
+import { dynamicComponents } from './dynamic-components';
+
+export const componentParsers: Array<HookParserEntry> = [
+    ...hookParsers,
+    ...dynamicComponents.map(component => {
+        return { component };
+    })
+];
+
+export { hookParsers } from './parsers';
+export { dynamicComponents } from './dynamic-components';
diff --git a/client/src/app/instance/search/detail/dynamic-content/parsers/index.ts b/client/src/app/instance/search/detail/dynamic-content/parsers/index.ts
index bac0c8f9..552c5a60 100644
--- a/client/src/app/instance/search/detail/dynamic-content/parsers/index.ts
+++ b/client/src/app/instance/search/detail/dynamic-content/parsers/index.ts
@@ -1,5 +1,3 @@
-import { DynamicRouterLinkParser } from './dynamic-router-link-parser';
-
 export const hookParsers = [
-    DynamicRouterLinkParser
+    
 ];
diff --git a/client/src/app/instance/store/effects/detail.effects.ts b/client/src/app/instance/store/effects/detail.effects.ts
index ce166cfa..b032e8c8 100644
--- a/client/src/app/instance/store/effects/detail.effects.ts
+++ b/client/src/app/instance/store/effects/detail.effects.ts
@@ -27,7 +27,6 @@ import * as datasetSelector from 'src/app/metamodel/selectors/dataset.selector';
  */
 @Injectable()
 export class DetailEffects {
-
     /**
      * Calls actions to retrieve object.
      */
@@ -42,8 +41,7 @@ export class DetailEffects {
             mergeMap(([, datasetName, attributeList, id]) => this.detailService.retrieveObject(
                 datasetName,
                 attributeList.find(attribute => attribute.order_by).id,
-                id,
-                attributeList.filter(attribute => attribute.id_detail_output_category).map(attribute => attribute.id)
+                id
             ).pipe(
                 map(object => detailActions.retrieveObjectSuccess({ object: object[0] })),
                 catchError(() => of(detailActions.retrieveObjectFail()))
diff --git a/client/src/app/instance/store/services/detail.service.ts b/client/src/app/instance/store/services/detail.service.ts
index e9b644aa..eee2a624 100644
--- a/client/src/app/instance/store/services/detail.service.ts
+++ b/client/src/app/instance/store/services/detail.service.ts
@@ -32,8 +32,8 @@ export class DetailService {
      *
      * @return Observable<any[]>
      */
-    retrieveObject(dname: string, criterionId: number, objectSelected: string, outputList: number[]): Observable<any[]> {
-        const query = `${dname}?c=${criterionId}::eq::${objectSelected}&a=${outputList.join(';')}`;
+    retrieveObject(dname: string, criterionId: number, objectSelected: string): Observable<any[]> {
+        const query = `${dname}?c=${criterionId}::eq::${objectSelected}&a=all`;
         return this.http.get<any[]>(`${this.config.apiUrl}/search/${query}`);
     }
 
diff --git a/client/src/app/instance/webpage/dynamic-content/components/datatable.component.html b/client/src/app/instance/webpage/components/datatable.component.html
similarity index 100%
rename from client/src/app/instance/webpage/dynamic-content/components/datatable.component.html
rename to client/src/app/instance/webpage/components/datatable.component.html
diff --git a/client/src/app/instance/webpage/dynamic-content/components/datatable.component.ts b/client/src/app/instance/webpage/components/datatable.component.ts
similarity index 100%
rename from client/src/app/instance/webpage/dynamic-content/components/datatable.component.ts
rename to client/src/app/instance/webpage/components/datatable.component.ts
diff --git a/client/src/app/instance/webpage/components/index.ts b/client/src/app/instance/webpage/components/index.ts
index 28c122e3..cdbd8374 100644
--- a/client/src/app/instance/webpage/components/index.ts
+++ b/client/src/app/instance/webpage/components/index.ts
@@ -1,5 +1,7 @@
 import { WebpageComponent } from './webpage-content.component';
+import { DatatableComponent } from './datatable.component';
 
 export const dummiesComponents = [
-    WebpageComponent
+    WebpageComponent,
+    DatatableComponent
 ];
diff --git a/client/src/app/instance/webpage/components/webpage-content.component.html b/client/src/app/instance/webpage/components/webpage-content.component.html
index 99c5e9a4..5fff195d 100644
--- a/client/src/app/instance/webpage/components/webpage-content.component.html
+++ b/client/src/app/instance/webpage/components/webpage-content.component.html
@@ -1 +1 @@
-<ngx-dynamic-hooks [content]="webpage.content"></ngx-dynamic-hooks>
\ No newline at end of file
+<ngx-dynamic-hooks [content]="webpage.content" [parsers]="getParsers()"></ngx-dynamic-hooks>
\ No newline at end of file
diff --git a/client/src/app/instance/webpage/components/webpage-content.component.ts b/client/src/app/instance/webpage/components/webpage-content.component.ts
index a8bfd9bb..7559b9c5 100644
--- a/client/src/app/instance/webpage/components/webpage-content.component.ts
+++ b/client/src/app/instance/webpage/components/webpage-content.component.ts
@@ -7,9 +7,11 @@
  * file that was distributed with this source code.
  */
 
-import { Component, Input } from '@angular/core';
+import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
 
 import { Webpage } from 'src/app/metamodel/models';
+import { globalParsers } from 'src/app/shared/dynamic-content';
+import { componentParsers } from '../dynamic-content';
 
 /**
  * @class
@@ -17,8 +19,16 @@ import { Webpage } from 'src/app/metamodel/models';
  */
 @Component({
     selector: 'app-webpage-content',
-    templateUrl: 'webpage-content.component.html'
+    templateUrl: 'webpage-content.component.html',
+    changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class WebpageComponent {
     @Input() webpage: Webpage;
+
+    getParsers() {
+        return [
+            ...globalParsers,
+            ...componentParsers
+        ];
+    }
 }
diff --git a/client/src/app/instance/webpage/dynamic-content/components/index.ts b/client/src/app/instance/webpage/dynamic-content/components/index.ts
deleted file mode 100644
index b3594ebb..00000000
--- a/client/src/app/instance/webpage/dynamic-content/components/index.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-import { DatatableComponent } from './datatable.component';
-
-export const dummiesComponents = [
-    DatatableComponent
-];
diff --git a/client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.html b/client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.html
deleted file mode 100644
index 2f43ac7e..00000000
--- a/client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<a *ngIf="isExternalLink()"
-    [href]="link"
-    [ngClass]="css"
-    [target]="target ? target : '_self'"
->
-    <ng-container *ngTemplateOutlet="contentTpl"></ng-container>
-</a>
-
-<a *ngIf="!isExternalLink()" 
-    [routerLink]="link"
-    [queryParams]="queryParams ? queryParams : {}" 
-    [fragment]="anchorFragment ? anchorFragment : null"
-    [ngClass]="css"
->
-    <ng-container *ngTemplateOutlet="contentTpl"></ng-container>
-</a>
-
-<ng-template #contentTpl><ng-content></ng-content></ng-template>
diff --git a/client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.scss b/client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.scss
deleted file mode 100644
index 1f0440ee..00000000
--- a/client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-:host {
-    display: inline;
-}
\ No newline at end of file
diff --git a/client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.ts b/client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.ts
deleted file mode 100644
index d0e20e09..00000000
--- a/client/src/app/instance/webpage/dynamic-content/dynamic-components/dynamic-router-link.component.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Component, Input } from '@angular/core';
-
-@Component({
-    selector: 'app-dynamic-router-link',
-    templateUrl: 'dynamic-router-link.component.html',
-    styleUrls: [ 'dynamic-router-link.component.scss' ]
-})
-export class DynamicRouterLinkComponent {
-    @Input() link: string;
-    @Input() queryParams: {[key: string]: any};
-    @Input() anchorFragment: string;
-    @Input() css: string;
-    @Input() target: string;
-
-    isExternalLink() {
-        return this.link.startsWith('http');
-    }
-}
diff --git a/client/src/app/instance/webpage/dynamic-content/dynamic-components/index.ts b/client/src/app/instance/webpage/dynamic-content/dynamic-components/index.ts
index 81f64b14..36671afc 100644
--- a/client/src/app/instance/webpage/dynamic-content/dynamic-components/index.ts
+++ b/client/src/app/instance/webpage/dynamic-content/dynamic-components/index.ts
@@ -1,7 +1,5 @@
-import { DynamicRouterLinkComponent } from './dynamic-router-link.component';
 import { DatasetSampleComponent } from './dataset-sample.component';
 
-export const DynamicComponents = [
-    DynamicRouterLinkComponent,
+export const dynamicComponents = [
     DatasetSampleComponent
 ];
diff --git a/client/src/app/instance/webpage/dynamic-content/dynamic-content.module.ts b/client/src/app/instance/webpage/dynamic-content/dynamic-content.module.ts
deleted file mode 100644
index 8f4f2781..00000000
--- a/client/src/app/instance/webpage/dynamic-content/dynamic-content.module.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * This file is part of Anis Client.
- *
- * @copyright Laboratoire d'Astrophysique de Marseille / CNRS
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-import { NgModule } from '@angular/core';
-import { RouterModule } from '@angular/router';
-
-import { DynamicHooksModule, HookParserEntry } from 'ngx-dynamic-hooks';
-
-import { SharedModule } from 'src/app/shared/shared.module';
-import { hookParsers } from './parsers';
-import { DynamicComponents } from './dynamic-components';
-import { dummiesComponents } from './components';
-
-export const componentParsers: Array<HookParserEntry> = [
-    ...hookParsers,
-    ...DynamicComponents.map(component => {
-        return { component };
-    })
-];
-
-/**
- * @class
- * @classdesc Dynamic content module.
- */
-@NgModule({
-    imports: [
-        SharedModule,
-        RouterModule,
-        DynamicHooksModule.forRoot({
-            globalParsers: componentParsers
-        }),
-    ],
-    exports: [
-        DynamicHooksModule
-    ],
-    providers: [
-        hookParsers
-    ],
-    declarations: [
-        DynamicComponents,
-        dummiesComponents
-    ],
-    entryComponents: [
-        DynamicComponents
-    ]
-})
-export class DynamicContentModule { }
diff --git a/client/src/app/instance/webpage/dynamic-content/index.ts b/client/src/app/instance/webpage/dynamic-content/index.ts
new file mode 100644
index 00000000..a8a58f22
--- /dev/null
+++ b/client/src/app/instance/webpage/dynamic-content/index.ts
@@ -0,0 +1,14 @@
+import { HookParserEntry } from 'ngx-dynamic-hooks';
+
+import { hookParsers } from './parsers';
+import { dynamicComponents } from './dynamic-components';
+
+export const componentParsers: Array<HookParserEntry> = [
+    ...hookParsers,
+    ...dynamicComponents.map(component => {
+        return { component };
+    })
+];
+
+export { hookParsers } from './parsers';
+export { dynamicComponents } from './dynamic-components';
diff --git a/client/src/app/instance/webpage/dynamic-content/parsers/dynamic-router-link-parser.ts b/client/src/app/instance/webpage/dynamic-content/parsers/dynamic-router-link-parser.ts
deleted file mode 100644
index cb543c79..00000000
--- a/client/src/app/instance/webpage/dynamic-content/parsers/dynamic-router-link-parser.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { Injectable } from '@angular/core';
-import { HookParser, HookPosition, HookValue, HookComponentData, HookBindings, HookFinder } from 'ngx-dynamic-hooks';
-
-import { DynamicRouterLinkComponent } from '../dynamic-components/dynamic-router-link.component';
-
-@Injectable()
-export class DynamicRouterLinkParser implements HookParser {
-    linkOpeningTagRegex: RegExp;
-    linkClosingTagRegex: RegExp;
-    hrefAttrRegex: RegExp;
-    classAttrRegex: RegExp;
-    targetAttrRegex: RegExp;
-
-    constructor(private hookFinder: HookFinder) {
-        const hrefAttr = '\\s+href\=\\"([^\\"]*?)\\"';
-        const anyOtherAttr = '\\s+[a-zA-Z]+\\=\\"[^\\"]*?\\"';
-        const linkOpeningTag = '\\<a(?:' + anyOtherAttr + ')*?' + hrefAttr + '(?:' + anyOtherAttr + ')*?\\>';
-
-        // Transform into proper regex objects and save for later
-        this.linkOpeningTagRegex = new RegExp(linkOpeningTag, 'gim');
-        this.linkClosingTagRegex = new RegExp('<\\/a>',  'gim');
-        this.hrefAttrRegex = new RegExp(hrefAttr, 'im');
-        this.classAttrRegex = new RegExp('\\s+class\=\\"([^\\"]*?)\\"', 'im');
-        this.targetAttrRegex = new RegExp('\\s+target\=\\"([^\\"]*?)\\"', 'im')
-    }
-
-    public findHooks(content: string, context: any): Array<HookPosition> {
-        // With the regexes we prepared, we can simply use findEnclosingHooks() to retrieve
-        // the HookPositions of all internal <a>-elements from the content string
-        return this.hookFinder.findEnclosingHooks(content, this.linkOpeningTagRegex, this.linkClosingTagRegex);
-    }
-
-    public loadComponent(hookId: number, hookValue: HookValue, context: any, childNodes: Array<Element>): HookComponentData {
-        // Simply return the component class here
-        return {
-            component: DynamicRouterLinkComponent
-        };
-    }
-
-    public getBindings(hookId: number, hookValue: HookValue, context: any): HookBindings {
-        // We can reuse the hrefAttrRegex here as its first capture group is the relative part of the url, 
-        // e.g. '/jedi/windu' from 'https://www.mysite.com/jedi/windu', which is what we need
-        const hrefAttrMatch = hookValue.openingTag.match(this.hrefAttrRegex);
-        let link = hrefAttrMatch[1];
-
-        // The relative part of the link may still contain the query string and the 
-        // anchor fragment, so we need to split it up accordingly
-        const anchorFragmentSplit = link.split('#');
-        link = anchorFragmentSplit[0];
-        const anchorFragment = anchorFragmentSplit.length > 1 ? anchorFragmentSplit[1] : null;
-
-        const queryParamsSplit = link.split('?');
-        link = queryParamsSplit[0];
-        const queryParams = queryParamsSplit.length > 1 ? this.parseQueryString(queryParamsSplit[1]) : {};
-
-        // Select css part
-        let css = null;
-        const classAttrMatch = hookValue.openingTag.match(this.classAttrRegex);
-        if (classAttrMatch) {
-            css = classAttrMatch[1];
-        }
-
-        // Select target part
-        let target = null;
-        const targetAttrMatch = hookValue.openingTag.match(this.targetAttrRegex);
-        if (targetAttrMatch) {
-            target = targetAttrMatch[1];
-        }
-
-        // Give all of these to our DynamicRouterLinkComponent as inputs and we're done!
-        return {
-            inputs: {
-                link,
-                queryParams: queryParams,
-                anchorFragment: anchorFragment,
-                css,
-                target
-            }
-        };
-    }
-
-    /**
-     * A helper function that transforms a query string into a QueryParams object
-     * Approach by Wolfgang Kuehn @ https://stackoverflow.com/a/8649003/3099523
-     *
-     * @param queryParamString - The queryString to parse
-     */
-    private parseQueryString(queryParamString: string): {[key: string]: any} {
-        return JSON.parse('{"' + 
-            decodeURI(queryParamString)
-            .replace(/"/g, '\\"')
-            .replace(/&/g, '","')
-            .replace(/=/g, '":"') + 
-        '"}');
-    }
-}
diff --git a/client/src/app/instance/webpage/dynamic-content/parsers/index.ts b/client/src/app/instance/webpage/dynamic-content/parsers/index.ts
index bac0c8f9..552c5a60 100644
--- a/client/src/app/instance/webpage/dynamic-content/parsers/index.ts
+++ b/client/src/app/instance/webpage/dynamic-content/parsers/index.ts
@@ -1,5 +1,3 @@
-import { DynamicRouterLinkParser } from './dynamic-router-link-parser';
-
 export const hookParsers = [
-    DynamicRouterLinkParser
+    
 ];
diff --git a/client/src/app/instance/webpage/webpage.module.ts b/client/src/app/instance/webpage/webpage.module.ts
index e88ace7b..86a7baca 100644
--- a/client/src/app/instance/webpage/webpage.module.ts
+++ b/client/src/app/instance/webpage/webpage.module.ts
@@ -10,9 +10,9 @@
 import { NgModule } from '@angular/core';
 
 import { SharedModule } from 'src/app/shared/shared.module';
-import { DynamicContentModule } from './dynamic-content/dynamic-content.module';
 import { WebpageRoutingModule, routedComponents } from './webpage-routing.module';
 import { dummiesComponents } from './components';
+import { hookParsers, dynamicComponents } from './dynamic-content';
 
 /**
  * @class
@@ -21,12 +21,18 @@ import { dummiesComponents } from './components';
 @NgModule({
     imports: [
         SharedModule,
-        DynamicContentModule,
         WebpageRoutingModule,
     ],
     declarations: [
         routedComponents,
-        dummiesComponents
+        dummiesComponents,
+        dynamicComponents
+    ],
+    entryComponents: [
+        dynamicComponents
+    ],
+    providers: [
+        hookParsers
     ]
 })
 export class WebpageModule { }
diff --git a/client/src/app/instance/search/detail/dynamic-content/dynamic-components/dynamic-router-link.component.html b/client/src/app/shared/dynamic-content/dynamic-components/dynamic-router-link.component.html
similarity index 100%
rename from client/src/app/instance/search/detail/dynamic-content/dynamic-components/dynamic-router-link.component.html
rename to client/src/app/shared/dynamic-content/dynamic-components/dynamic-router-link.component.html
diff --git a/client/src/app/instance/search/detail/dynamic-content/dynamic-components/dynamic-router-link.component.scss b/client/src/app/shared/dynamic-content/dynamic-components/dynamic-router-link.component.scss
similarity index 100%
rename from client/src/app/instance/search/detail/dynamic-content/dynamic-components/dynamic-router-link.component.scss
rename to client/src/app/shared/dynamic-content/dynamic-components/dynamic-router-link.component.scss
diff --git a/client/src/app/instance/search/detail/dynamic-content/dynamic-components/dynamic-router-link.component.ts b/client/src/app/shared/dynamic-content/dynamic-components/dynamic-router-link.component.ts
similarity index 100%
rename from client/src/app/instance/search/detail/dynamic-content/dynamic-components/dynamic-router-link.component.ts
rename to client/src/app/shared/dynamic-content/dynamic-components/dynamic-router-link.component.ts
diff --git a/client/src/app/shared/dynamic-content/dynamic-components/index.ts b/client/src/app/shared/dynamic-content/dynamic-components/index.ts
new file mode 100644
index 00000000..376e8a42
--- /dev/null
+++ b/client/src/app/shared/dynamic-content/dynamic-components/index.ts
@@ -0,0 +1,5 @@
+import { DynamicRouterLinkComponent } from './dynamic-router-link.component';
+
+export const dynamicComponents = [
+    DynamicRouterLinkComponent
+];
diff --git a/client/src/app/shared/dynamic-content/index.ts b/client/src/app/shared/dynamic-content/index.ts
new file mode 100644
index 00000000..98122e3a
--- /dev/null
+++ b/client/src/app/shared/dynamic-content/index.ts
@@ -0,0 +1,14 @@
+import { HookParserEntry } from 'ngx-dynamic-hooks';
+
+import { hookParsers } from './parsers';
+import { dynamicComponents } from './dynamic-components';
+
+export const globalParsers: Array<HookParserEntry> = [
+    ...hookParsers,
+    ...dynamicComponents.map(component => {
+        return { component };
+    })
+];
+
+export { hookParsers } from './parsers';
+export { dynamicComponents } from './dynamic-components';
diff --git a/client/src/app/instance/search/detail/dynamic-content/parsers/dynamic-router-link-parser.ts b/client/src/app/shared/dynamic-content/parsers/dynamic-router-link-parser.ts
similarity index 99%
rename from client/src/app/instance/search/detail/dynamic-content/parsers/dynamic-router-link-parser.ts
rename to client/src/app/shared/dynamic-content/parsers/dynamic-router-link-parser.ts
index cb543c79..01b5fa6e 100644
--- a/client/src/app/instance/search/detail/dynamic-content/parsers/dynamic-router-link-parser.ts
+++ b/client/src/app/shared/dynamic-content/parsers/dynamic-router-link-parser.ts
@@ -21,7 +21,7 @@ export class DynamicRouterLinkParser implements HookParser {
         this.linkClosingTagRegex = new RegExp('<\\/a>',  'gim');
         this.hrefAttrRegex = new RegExp(hrefAttr, 'im');
         this.classAttrRegex = new RegExp('\\s+class\=\\"([^\\"]*?)\\"', 'im');
-        this.targetAttrRegex = new RegExp('\\s+target\=\\"([^\\"]*?)\\"', 'im')
+        this.targetAttrRegex = new RegExp('\\s+target\=\\"([^\\"]*?)\\"', 'im');
     }
 
     public findHooks(content: string, context: any): Array<HookPosition> {
diff --git a/client/src/app/shared/dynamic-content/parsers/index.ts b/client/src/app/shared/dynamic-content/parsers/index.ts
new file mode 100644
index 00000000..bac0c8f9
--- /dev/null
+++ b/client/src/app/shared/dynamic-content/parsers/index.ts
@@ -0,0 +1,5 @@
+import { DynamicRouterLinkParser } from './dynamic-router-link-parser';
+
+export const hookParsers = [
+    DynamicRouterLinkParser
+];
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 5455daca..750a89c0 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -23,9 +23,11 @@ import { PaginationModule } from 'ngx-bootstrap/pagination';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
 import { NgSelectModule } from '@ng-select/ng-select';
 import { NgxJsonViewerModule } from 'ngx-json-viewer';
+import { DynamicHooksModule } from 'ngx-dynamic-hooks';
 
 import { sharedComponents } from './components';
 import { sharedPipes } from './pipes';
+import { hookParsers, dynamicComponents } from './dynamic-content';
 
 /**
  * @class
@@ -34,7 +36,14 @@ import { sharedPipes } from './pipes';
 @NgModule({
     declarations: [
         sharedComponents,
-        sharedPipes
+        sharedPipes,
+        dynamicComponents
+    ],
+    entryComponents: [
+        dynamicComponents
+    ],
+    providers: [
+        hookParsers
     ],
     imports: [
         CommonModule,
@@ -51,7 +60,8 @@ import { sharedPipes } from './pipes';
         PaginationModule.forRoot(),
         ProgressbarModule.forRoot(),
         NgSelectModule,
-        NgxJsonViewerModule
+        NgxJsonViewerModule,
+        DynamicHooksModule.forRoot({}),
     ],
     exports: [
         CommonModule,
@@ -68,8 +78,10 @@ import { sharedPipes } from './pipes';
         ProgressbarModule,
         NgSelectModule,
         NgxJsonViewerModule,
+        DynamicHooksModule,
         sharedComponents,
-        sharedPipes
+        sharedPipes,
+        dynamicComponents
     ]
 })
 export class SharedModule { }
diff --git a/conf-dev/create-db.sh b/conf-dev/create-db.sh
index 5118a6a2..04733542 100644
--- a/conf-dev/create-db.sh
+++ b/conf-dev/create-db.sh
@@ -43,6 +43,9 @@ curl -d '{"id":56,"name":"spec1d","label":"spec1d","form_label":"spec1d","descri
 curl -d '{"id":57,"name":"spec1dnoise","label":"spec1dnoise","form_label":"spec1dnoise","description":null,"primary_key":false,"output_display":570,"criteria_display":570,"search_type":null,"type":"text","operator":null,"dynamic_operator":true,"min":null,"max":null,"placeholder_min":null,"placeholder_max":null,"renderer":"download","renderer_config":{"display":"icon-button","text":"DOWNLOAD","icon":"fas fa-download"},"detail_display":570,"selected":true,"order_by":false,"archive":false,"options":null,"vo_utype":null,"vo_ucd":null,"vo_unit":null,"vo_description":null,"vo_datatype":null,"vo_size":null,"id_criteria_family":null,"id_output_category":1,"id_detail_output_category":null}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/vipers_dr2_w1/attribute
 curl -d '{"id":58,"name":"spec1dsky","label":"spec1dsky","form_label":"spec1dsky","description":null,"primary_key":false,"output_display":580,"criteria_display":580,"search_type":null,"type":"text","operator":null,"dynamic_operator":true,"min":null,"max":null,"placeholder_min":null,"placeholder_max":null,"renderer":"download","renderer_config":{"display":"icon-button","text":"DOWNLOAD","icon":"fas fa-download"},"detail_display":580,"selected":true,"order_by":false,"archive":false,"options":null,"vo_utype":null,"vo_ucd":null,"vo_unit":null,"vo_description":null,"vo_datatype":null,"vo_size":null,"id_criteria_family":null,"id_output_category":1,"id_detail_output_category":null}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/vipers_dr2_w1/attribute
 
+# Add vipers_dr2_w1 detail config
+curl -d '{"enabled":true,"content":"<div class=\"row\">\n    <div class=\"col col-md-8 col-sm-12\">\n        <app-display-spectra\n            [object]=\"context.object\"\n            [datasetName]=\"context.datasetName\"\n            [attributeList]=\"context.attributeList\"\n            [attributeSpectraId]=\"56\"\n            [attributeZId]=\"8\">\n        </app-display-spectra>\n    </div>\n    <div class=\"col col-md-4 col-sm-12\">\n        <app-display-ra-dec\n            [object]=\"context.object\"\n            [attributeList]=\"context.attributeList\"\n            [attributeRaId]=\"2\"\n            [attributeDecId]=\"3\">\n        </app-display-ra-dec>\n        <app-display-object \n            [object]=\"context.object\"\n            [attributeList]=\"context.attributeList\"\n            [outputFamilyList]=\"context.outputFamilyList\"\n            [outputCategoryList]=\"context.outputCategoryList\">\n        </app-display-object>\n    </div>\n</div>"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/vipers_dr2_w1/detail-config
+
 # Add sp_cards attributes
 curl -d '{"label":"Card","display":10,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/sp_cards/criteria-family
 curl -d '{"label":"Default","display":10,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/sp_cards/output-family
@@ -74,6 +77,9 @@ curl -d '{"id":7,"name":"object_name","label":"object_name","form_label":"Object
 curl -d '{"id":8,"name":"fits_file","label":"fits_file","form_label":"fits_file","description":null,"primary_key":false,"output_display":80,"criteria_display":80,"search_type":null,"type":"string","operator":null,"dynamic_operator":true,"min":null,"max":null,"placeholder_min":null,"placeholder_max":null,"renderer":"download","renderer_config":{"display":"icon-button","text":"DOWNLOAD","icon":"fas fa-download"},"detail_display":80,"selected":true,"order_by":false,"archive":false,"detail":false,"options":null,"vo_utype":null,"vo_ucd":null,"vo_unit":null,"vo_description":null,"vo_datatype":null,"vo_size":null,"id_criteria_family":null,"id_output_category":4,"id_detail_output_category":null}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/observations/attribute
 curl -d '{"id":9,"name":"fits_png","label":"fits_png","form_label":"fits_png","description":null,"primary_key":false,"output_display":90,"criteria_display":90,"search_type":null,"type":"string","operator":null,"dynamic_operator":true,"min":null,"max":null,"placeholder_min":null,"placeholder_max":null,"renderer":"image","renderer_config":{"type":"fits","display":"modal","width":"50","height":"50"},"detail_display":90,"selected":true,"order_by":false,"archive":false,"detail":false,"options":null,"vo_utype":null,"vo_ucd":null,"vo_unit":null,"vo_description":null,"vo_datatype":null,"vo_size":null,"id_criteria_family":null,"id_output_category":4,"id_detail_output_category":null}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/observations/attribute
 
+# Add observations detail config
+curl -d '{"enabled":true,"content":"<div class=\"row justify-content-center\">\n    <div class=\"col col-lg-10 col-xl-8 mt-4\">\n        <div class=\"row\">\n            <div class=\"col-12\">\n                <app-display-ra-dec\n                    [object]=\"context.object\"\n                    [attributeList]=\"context.attributeList\"\n                    [attributeRaId]=\"2\"\n                    [attributeDecId]=\"3\">\n                </app-display-ra-dec>\n            </div>\n        </div>\n\n        <app-display-object \n            [object]=\"context.object\"\n            [attributeList]=\"context.attributeList\"\n            [outputFamilyList]=\"context.outputFamilyList\"\n            [outputCategoryList]=\"context.outputCategoryList\">\n        </app-display-object>\n    </div>\n</div>"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/observations/detail-config
+
 # Add vvds_f02_udeep attributes
 curl -d '{"label":"Default","display":10,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/vvds_f02_udeep/criteria-family
 curl -d '{"label":"Default","display":10,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/vvds_f02_udeep/output-family
@@ -112,4 +118,4 @@ curl -d '{"id":15,"name":"src_id","label":"src_id","form_label":"SRC ID","descri
 
 # Add webpages
 curl -d '{"label":"Default","icon":null,"display":10}' --header 'Content-Type: application/json' -X POST http://localhost/instance/default/webpage-family
-curl -d '{"label":"Home","icon":"fas fa-home","display":10,"title":"Home","content":"<div class=\"row align-items-center jumbotron\"><div class=\"col-6 col-md-4 order-md-2 mx-auto text-center\"><img class=\"img-fluid mb-3 mb-md-0\" src=\"http://localhost:8080/instance/default/file-explorer/home_component_logo.png\" alt=\"Instance logo\"></div><div class=\"col-md-8 order-md-1 text-justify pr-md-5\"><h2 class=\"mb-3\">Welcome to the ANIS default instance</h2><p class=\"lead\">This service provides several sub-services to interact with the database.<br>Here is a brief presentation of these sub-services:</p><ul class=\"lead\"><li><a href=\"https://drf-gitlab.cea.fr/svom/sdb/api-import/-/wikis/home\">/import/</a> =&gt; This sub-service allows you to import data L0, L1 or SR3/SR4</li><li>/export-rest =&gt; =&gt; This sub-service allows you to request and export data from the database in json format with a specific URL. To build this URL you could <a class=\"btn btn-warning\" href=\"../../search\">Go to search form</a></li></ul></div></div>"}' --header 'Content-Type: application/json' -X POST http://localhost/webpage-family/1/webpage
+curl -d '{"label":"Home","icon":"fas fa-home","display":10,"title":"Home","content":"<div class=\"row align-items-center jumbotron\">\n    <div class=\"col-6 col-md-4 order-md-2 mx-auto text-center\">\n        <img class=\"img-fluid mb-3 mb-md-0\" src=\"http://localhost:8080/instance/default/file-explorer/home_component_logo.png\" alt=\"Instance logo\">\n   </div>\n   <div class=\"col-md-8 order-md-1 text-justify pr-md-5\">\n       <h2 class=\"mb-3\">Welcome to the ANIS default instance</h2>\n       <p class=\"lead\">\n           This service provides several sub-services to interact with the database.<br>\n           Here is a brief presentation of these sub-services:\n       </p>\n       <ul class=\"lead\">\n           <li>\n               <a href=\"https://drf-gitlab.cea.fr/svom/sdb/api-import/-/wikis/home\">\n                   /import/\n               </a> =&gt; This sub-service allows you to import data L0, L1 or SR3/SR4\n           </li>\n           <li>\n               /export-rest =&gt; =&gt; This sub-service allows you to request and export data \n               from the database in json format with a specific URL. To build this URL you could \n               <a class=\"btn btn-warning\" href=\"../../search\">\n                   Go to search form\n               </a>\n           </li>\n       </ul>\n   </div>\n</div>\n<app-dataset-sample\n    [datasetName]=\"'\''observations'\''\"\n    [sortingColumn]=\"1\"\n    [sortingDirection]=\"'\''d'\''\"\n    [nbItems]=\"5\">\n</app-dataset-sample>"}' --header 'Content-Type: application/json' -X POST http://localhost/webpage-family/1/webpage
-- 
GitLab