Generators - Map Listing Generator

Edit only a few settings to generate a modern looking visualization that uses ODS components and widgets.

Listing with map view

Page that shows some KPIs and a list of results alongside a map.

  <!-- V1.2 :
    - Add fieldImage and imagePosition for image datasets
    - Add mapPictoColor and mapPicto to set up map picto
  -->

  <!-- V1.1 :
    - Add ods-select and multiple choice option for filters
    - Add clear all filter button when one filter is applied
    - responsive display for filters and date button
    - KPI default value to 0 when no data/results to display
  -->

<!-- IMPORTANT ******** MUST READ !

    In the following settings declaration :

    A common error is forget to escape (protect) apostrophe with a leading backslash
    As apostrophes are used to declare values of variable it will break the settings

    Ex:
    wrongVariable = 'I'll be freed from apostrophes'
    correctVariable = 'I\'ll be freed from apostrophes'
-->

<!-- SETTINGS START HERE -->
<div class="container"


     ng-init="domain = 'mesr.opendatasoft.com';
                datasetid = 'fr-esr-principaux-etablissements-enseignement-superieur';

                filters = [
                {'id':'type_d_etablissement','multiple':true},
                {'id':'secteur_d_etablissement','multiple':false},
                {'id':'dep_nom','multiple':true}
                ];
                resetFiltersButton = true;
                resetFiltersButtonLabel = 'Supprimer tous les filtres';

                fieldsList = ['adresse_uai', 'dep_nom', 'numero_telephone_uai'];
                fieldLink = 'url';
                fieldLinkLabel = 'Visiter le site web';

                fieldImage = '';
                imagePosition = 'left';

                resultTitle = 'uo_lib';
                itemsPerRow = '2';

                kpis = [
                {
                'title': 'Nombre d\'établissements',
                'function': 'COUNT',
                'faicon': 'graduation-cap'
                }
                ];

                headerBackgroundImage = 'https://images.unsplash.com/photo-1541890289-b86df5bafd81?ixlib=rb-1.2.1&auto=format&fit=crop&w=2828&q=80';
                subtitle = 'Établissements près de chez vous';

                mapPictoColor = '';
                mapPicto = '';


                DO_NOT_MODIFY_BELOW;

                ctxfields = {};
                values = {};
                activeFilters = {};
                ">

    <!-- ### GENERAL SETTINGS ### -->
    <!-- domain : (Domain URL) : Must contain the ID of the domain where the dataset is published.
    ex: 'discovery.opendatasoft.com'
    -->
        <!-- datasetid (Dataset ID) : Must contain the ID of the dataset
    ex: 'oeuvres-de-johannes-vermeer'
    -->
        <!-- headerBackgroundImage : Use an URL to show a background image in the header.
    -->

    <!-- ### FILTERS SETTINGS ### -->
    <!-- filters (Filters) : List of object that contains the IDs to generate the filters pannel.
          and multiple true or false to allow the user to select multiple values in the filter.
          NB: the field must be a facet in the dataset
          NB: alphanumerical sort is applied in the filter view
           ex: [
                    {'id':'filterid','multiple':true},
                    {'id':'filterid2','multiple':false}
               ]
      -->
    <!-- resetFiltersButton (boolean) : add a reset filters button after filters block -->
    <!-- resetFiltersButtonLabel (Label of the button) : test to display when a filter is selected
         ex: 'Clear all filters'
    -->


    <!-- ### LIST VIEW SETTINGS ### -->
    <!-- fieldsList (List configuration) : Set the list of field IDs
    ex: ['title','category','genre','date']
    -->
        <!-- fieldLink (Link to an external resource) : If available, the field ID of some external resource as a web URL
    ex: 'link'
    -->
        <!-- fieldLinkLabel (The label of that link) : Label of the link button
    ex: 'Read more here'
    -->
        <!-- fieldImage (Field id of the image field if any)
         ex: 'image'
    -->
        <!-- imagePosition (Image position) : Image position in the card, can be 'top' or 'left'
         ex: 'left'
    -->


    <!-- ### KPIS SETTINGS ### -->
    <!-- KPIS settings is a list of object that describes each KPI
    List of available keys are :
    - title (Name of the KPI) ex: 'Average # of citizens'
    - function (function of the aggregation) ex: 'SUM'
    - expression (field id that contains numerical values to aggregate) ex: 'population'
    - precision (Decimal precision of the KPI) ex: 2
    - unit (KPI unit) ex: 'citizens'
    - faicon (FontAwesome icon id) ex: 'square-o'

    title, function, expression are MANDATORY
    the others are optionnal

    Available functions are SUM, AVG, COUNT, STD, MAX, MIN.
    Please see the documentation for more information
    https://help.opendatasoft.com/widgets/#/api/ods-widgets.directive:odsAggregation

    Please see all available icons here
    https://fontawesome.com/v4.7.0/icons/

    ex:
    kpis = [
        {
        'title': 'Taille moyenne',
        'function': 'AVG',
        'expression': 'surface',
        'precision': 2,
        'unit': 'm2',
        'faicon': 'square-o'
        },
        {
        'title': 'Nombre d\'oeuvre référencées',
        'function': 'COUNT'
        }
    ];
    -->

    <!-- ### Subtitle ### -->
    <!-- subtitle : "Some texte" -->


    <!-- mapPictoColor = '#EC643C'
         mapPicto = 'ods-photo'
    Map picto settings to set the color and the picto from this list : https://help.opendatasoft.com/platform/en/other_resources/pictograms_reference/pictograms_reference.html
    -->

    <!-- DO NOT MODIFY -->
    <!-- Technical fields, do not modify please -->

    <ods-dataset-context context="ctx"
                         ctx-domain="{{ domain }}"
                         ctx-dataset="{{ datasetid }}">
      <!-- Private datasets can be accessed by adding an API Key.
        Add this param to the <ods-dataset-context above,
        where XXX is your apikey :
        ctx-apikey="XXX"
      -->

      <div class="page-header" style="background-image: url({{ headerBackgroundImage }})">
        <h1 class="page-title">
          {{ ctx.dataset.metas.title }}
        </h1>
      </div>

      <!-- Page subtitle -->
      <h2 class="page-subtitle" ng-bind-html="ctx.dataset.metas.description | shortSummary"></h2>

      <span ng-repeat="field in ctx.dataset.fields">
        {{ ctxfields[field.name] = field.label; '' }}
      </span>

      <span ng-repeat="filter in filters">
        {{ ctx.parameters['refine.' + filter.id] = activeFilters[filter.id] ; '' }}
      </span>

      <!-- KPIs -->
      <section class="kpis-container row row-equal-height">
        <div class="{{ 'col-md-' + (12/itemsPerRow) }} margin-bottom-20"
             ng-repeat="kpi in kpis">
          <!-- KPI box component -->
          <div class="kpi-card"
               ods-aggregation="agg"
               ods-aggregation-context="ctx"
               ods-aggregation-function="{{ kpi.function }}"
               ods-aggregation-expression="{{ kpi.expression }}">
            <i class="kpi-icon fa fa-{{ kpi.faicon || 'gitlab' }}" aria-hidden="true"></i>
            <h2 class="kpi-title">
              {{ (agg || 0) | number : (kpi.precision || 0) }}
              <span ng-if="kpi.unit" class="kpi-unit">{{ kpi.unit }}</span>
            </h2>
            <p class="kpi-description">
              {{ kpi.title }}
            </p>
          </div>
        </div>
      </section>



      <!-- Section 1 -->
      <section class="page-section">
        <h2 class="section-title">
          {{ subtitle }}
        </h2>

        <div class="content-card">
          <p class="content-card-description">
            Utilisez les filtres ci-dessus pour affiner votre recherche.
          </p>

          <!-- SEARCH -->
          <div class="search-module">
            <i class="fa fa-search search-module-icon" aria-hidden="true"></i>
            <input placeholder="Rechercher"
                   ng-model="ctx.parameters['q']"
                   ng-model-options="{ updateOn: 'keyup', debounce: { 'default': 300, 'blur': 0 }}"
                   class="search-module-input"
                   type="text"/>
            <button class="search-module-clear"
                    ng-if="ctx.parameters['q']"
                    ng-click="ctx.parameters['q'] = undefined">
              <i class="fa fa-times-circle" aria-hidden="true"></i>
            </button>
          </div>


          <!-- FILTERS -->
          <div class="filter-list"
               ng-init="dropdown.open = '';
                        select = {}">
            <div ng-repeat="filter in filters">
              {{ ctx.parameters['disjunctive.' + filter.id] = true; '' }}
              <div ods-facet-results="categories"
                   ods-facet-results-facet-name="{{ filter.id }}"
                   ods-facet-results-context="ctx"
                   ods-facet-results-sort="alphanum">
                <ods-select ng-if="ctxfields[filter.id]"
                            selected-values="activeFilters[filter.id]"
                            multiple="filter.multiple"
                            options="categories"
                            label-modifier="name"
                            value-modifier="name"
                            placeholder="{{ ctxfields[filter.id] }}"></ods-select>
              </div>
            </div>

            <div class="clear-filters"
                 ng-show="(activeFilters | values).join('')">
              <div class="clear-filters-button"
                   role="button"
                   ng-click="activeFilters = {}">
                {{ resetFiltersButtonLabel }}
                <i class="fa fa-times-circle" aria-hidden="true"></i>
              </div>
            </div>
          </div>

          <div class="row">
            <div class="col-md-5 col-xs-12">
              <ul class="result-list">
                <li class="result"
                    ng-repeat="item in items"
                    ods-results="items"
                    ods-results-context="ctx"
                    ods-results-max="1000"
                    ng-click="ctx.parameters['refine.' + resultTitle] = (ctx.parameters['refine.' + resultTitle]?undefined:item.fields[resultTitle])"
                    ng-class="{'result-img-horizontal': imagePosition === 'left' }">
                  <div class="result-img"
                       ng-if="item.fields[fieldImage]"
                       style="{{ 'background-image: url(https://' + domain + '/explore/dataset/' + datasetid + '/files/' + item.fields[fieldImage].id + '/300/);' }}">
                  </div>
                  <div class="result-content">
                    <h2 class="result-title">
                      {{ item.fields[resultTitle] }}
                    </h2>
                    <dl class="result-info">
                      <dt ng-repeat-start="field in fieldsList">
                        {{ ctxfields[field] }}
                      </dt>
                      <dd ng-repeat-end>{{ item.fields[field] }}</dd>
                    </dl>
                    <div ng-if="fieldLink">
                      <!-- fieldLink is used here
       For very simple usage, just set the href to the field value :
       - href="{{ item.fields[fieldLink] }}"

       For more advanced scenario, you can send the user to :
       - the dataset table filtered with the fieldLink value, through a text query
       href="/explore/dataset/{{ datasetid }}/table?q={{ item.fields[fieldLink] }}"
       - the dataset table filtered with the fieldLink value, through a refine on the field
       href="/explore/dataset/{{ datasetid }}/table?refine.{{ fieldLink }}={{ item.fields[fieldLink] }}"
       - a page using url-sync=true setting :
       href="/pages/yourpage/?refine.{{ fieldLink }}={{ item.fields[fieldLink] }}"
       -->

                      <a href="{{ 'https://' + domain + '/explore/dataset/' + datasetid + '/files/' + item.fields[fieldLink].id + '/300/' }}"
                         target="_blank"
                         class="content-card-button"
                         ng-if="item.fields[fieldLink]">
                        {{ fieldLinkLabel }}
                      </a>
                    </div>
                  </div>
                </li>
              </ul>
            </div>

            <div class="col-md-7 col-xs-12">
              <ods-map no-refit="false"
                       scroll-wheel-zoom="false">
                <ods-map-layer-group>
                  <ods-map-layer context="ctx"
                                 color="{{ mapPictoColor }}"
                                 picto="{{ mapPicto }}"
                                 show-marker="true"
                                 display="auto"
                                 shape-opacity="0.5"
                                 point-opacity="1"
                                 border-color="#FFFFFF"
                                 border-opacity="1"
                                 border-size="1"
                                 border-pattern="solid"
                                 size="4"
                                 size-min="3"
                                 size-max="5"
                                 size-function="linear"></ods-map-layer>
                </ods-map-layer-group>
              </ods-map>
            </div>
          </div>
        </div>
        <a href="{{ ctx.domainUrl }}/explore/dataset/{{ datasetid }}" target="_blank">Accéder aux données source</a>
      </section>
    </ods-dataset-context>
  </div>
</div>
/* General Layout
========================================================================== */

main.main--page {
  margin: 0;
}

/* Body text */
p {
  text-align: justify;
  margin-top: 0;
  margin-bottom: 1.5rem;
}

ul {
  list-style: none;
  padding-left: 0;
}

@media screen and (min-width: 992px) {
  .row-equal-height {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: wrap;
    flex-wrap: wrap;
    margin-bottom: 20px;
  }
  /* Fix for early content wrapping in Safari*/
  .row-equal-height:before,
  .row-equal-height:after {
    content: normal;
  }
}

.margin-bottom-20 {
  margin-bottom: 20px;
}

.page-section {
  margin-top: 2rem;
  margin-bottom: 6rem;
}

.section-title {
  font-size: 2.2rem;
  font-weight: 400;
  color: #FFF;
  background-color: var(--highlight);
  text-align: center;
  padding: 10px;
  margin-top: 1.5rem;
  margin-bottom: 1.5rem;
}

/* Header
========================================================================== */

.page-header {
  background-color: var(--highlight);
  min-height: 180px; /* Change the height of the image here by increasing or decreasing the pixels. */
  /* Properties of the title located within the image banner */
  display: flex;
  justify-content: center;
  align-items: flex-end;
  margin-bottom: 40px;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center center;
  background-image: url('');
}

.page-title {
  color: var(--highlight);
  text-align: center;
  background-color: var(--page-background);
  position: relative;
  vertical-align: bottom;
  margin: 0;
  font-weight: 400;
  font-size: 2.5rem;
  padding: 15px 20px 10px 20px;
  border-radius: 7px 7px 0px 0px;
}

.page-subtitle {
  font-weight: 400;
  margin-top: 0;
  margin-bottom: 20px;
  font-size: 1.14rem;
}


/* Search Module
========================================================================== */

.search-module {
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid #dee5ef;
  margin-bottom: 13px;
  transition: all .2s;
}

.search-module:hover,
.search-module:focus-within {
  border-bottom-color: var(--links);
}

.search-module-icon {
  color: #898d92;
  margin-right: 8px;
  align-self: center;
}

.search-module-input {
  background-color: transparent;
  width: 100%;
  outline: none;
  border: none;
  padding: 12px 0;
  transition: all .2s;
  color: var(--text);
}

.search-module-input::placeholder {
  transition: all .2s;
}

.search-module-clear {
  color: #898d92;
  font-size: 1rem;
  background: transparent;
  border: none;
  margin: 0;
  outline: none;
  padding: 0 0 0 12px;
  transition: all .2s;
}

.search-module-clear:hover {
  opacity: .65;
}

.search-module:hover .search-module-icon,
.search-module:focus-within .search-module-icon,
.search-module:hover .search-module-input::placeholder,
.search-module:focus-within .search-module-input::placeholder {
  color: var(--links)
}


/* Filters
========================================================================== */

.filter-list {
  display: flex;
  flex-wrap: wrap;
  position: relative;
}
.filter-list > * {
  margin: 0 0 10px;
  width: 100%;
}
.odswidget-select .odswidget-select-dropdown.open .odswidget-select-dropdown-menu {
  width: 100%
}
.clear-filters {
  display: flex;
  align-items: center;
  justify-content: center;
}
.clear-filters-button:hover {
  opacity: 0.65;
}
.odswidget-select,
.odswidget-select .odswidget-select-dropdown {
  width: 100%;
}
@media screen and (min-width: 500px) {
  .filter-list > * {
    margin: 0 10px 10px 0;
    width: inherit;
  }
  .odswidget-select .odswidget-select-dropdown.open .odswidget-select-dropdown-menu {
    width: max-content;
    min-width: 240px;
  }
}


/* Content Card
========================================================================== */

.content-card {
  background-color: var(--boxes-background);
  border-radius: 4px;
  height: 100%;
  padding: 26px;
  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.13);
  margin-bottom: 10px;
}

.content-card-description {
  color: var(--text);
  font-size: 1rem;
  line-height: 1.5;
  font-weight: normal;
  margin-top: 0;
  margin-bottom: 26px;
  max-width: 100%;
}

.content-card-button {
  color: var(--highlight);
  border: 1px solid var(--highlight);
  background: transparent;
  display: inline-block;
  text-align: center;
  font-size: .867rem;
  border-radius: 4px;
  padding: .5rem 1.15rem;
  text-decoration: none;
  transition: all .2s;
}

.content-card-button:hover {
  background-color: var(--highlight);
  color: #FFFFFF;
  text-decoration: none;
}


/* KPI Card
========================================================================== */

@media screen and (min-width: 992px) {
  .kpis-container {
    display: flex;
    justify-content: center;
  }
}

.kpi-card {
  background-color: var(--boxes-background);
  height: 100%;
  padding: 39px;
  border-radius: 4px;
  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.13);
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -ms-flex-direction: column;
  flex-direction: column;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  text-align: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  text-align: center;
}

.kpi-icon {
  color: var(--highlight);
  color: var(--secondary-color);
  font-size: 4rem;
  margin-top: 0;
  margin-bottom: 13px;
  max-width: 100%;
}

.kpi-title {
  font-weight: normal;
  color: var(--highlight);
  font-size: 3.2rem;
  margin-top: 0;
  margin-bottom: 13px;
  max-width: 100%;
}

.kpi-unit {
  font-size: 0.8em;
  color: var(--secondary-color);
}

.kpi-description {
  color: var(--text);
  font-size: 1rem;
  line-height: 1.5;
  font-weight: normal;
  margin-top: 0;
  margin-bottom: 0;
  max-width: 100%;
}


/* Results and Map
========================================================================== */

.odswidget-map__map {
  min-height: 550px;
}

@media screen and (min-width: 991px) {
  .col-md-5 {
    padding-right: 0;
  }

  .col-md-7 {
    padding-left: 0;
  }
}


.result-list {
  list-style: none;
  padding-left: 0;
  margin-top: 0;
  height: 550px;
  overflow-y: auto;
}

.result {
  margin-bottom: 10px;
  padding: 0 1rem 1rem 0;
  border-bottom: 1px solid #DEE5EF;
}

.result.result-img-horizontal {
  display: flex;
}

.result-img-horizontal .result-img {
  flex: 0 0 35%;
  width: 35%;
  height: auto;
}

.result-img {
  disaply: block;
  height: 118px;

  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.result-img-horizontal .result-content {
  margin-left: 0.7rem;
}
:not(.result-img-horizontal) .result-content {
  margin-top: 0.7rem;
}

.result-title {
  color: var(--titles);
  font-size: 1rem;
  font-weight: bold;
  margin-top: 0;
  margin-bottom: 5px;
}

.result-info {
  list-style: none;
  padding-left: 0;
  margin-top: 0;
  margin-bottom: 10px;
  overflow-wrap: break-word;
}

.result-info dt {
  font-size: .9rem;
  opacity: .9;
}

.result-info dd {
  margin-left: 0;
  margin-bottom: 5px;
}

Listing with map view - with image placeholder New

Optionally, images can be displayed in the list view.

  <!-- V1.2 :
    - Add fieldImage and imagePosition for image datasets
    - Add mapPictoColor and mapPicto to set up map picto
  -->

  <!-- V1.1 :
    - Add ods-select and multiple choice option for filters
    - Add clear all filter button when one filter is applied
    - responsive display for filters and date button
    - KPI default value to 0 when no data/results to display
  -->

<!-- IMPORTANT ******** MUST READ !

    In the following settings declaration :

    A common error is forget to escape (protect) apostrophe with a leading backslash
    As apostrophes are used to declare values of variable it will break the settings

    Ex:
    wrongVariable = 'I'll be freed from apostrophes'
    correctVariable = 'I\'ll be freed from apostrophes'
-->

<!-- SETTINGS START HERE -->
<div class="container"


        ng-init="domain = 'opendata.hauts-de-seine.fr';
                datasetid = 'archives-de-la-planete-paris';

                filters = [
                {'id':'operateur','multiple':true},
                {'id':'themes','multiple':true},
                {'id':'lieu_retraite','multiple':true}
                ];
                resetFiltersButton = true;
                resetFiltersButtonLabel = 'Supprimer tous les filtres';

                fieldsList = ['sujets', 'procede_technique', 'date_de_prise_de_vue'];
                fieldLink = 'photo';
                fieldLinkLabel = 'Télécharger l\'image';

                fieldImage = 'photo';
                imagePosition = 'left';

                resultTitle = 'legende_revisee';
                itemsPerRow = '2';

                kpis = [
                {
                'title': 'Nombre d\'œuvres référencées',
                'function': 'COUNT',
                'faicon': 'camera-retro'
                }
                ];

                headerBackgroundImage = 'https://opendata.hauts-de-seine.fr/explore/dataset/archives-de-la-planete/files/ef4d61b0016cb909b5081622a0abecfe/download/';
                subtitle = 'Autochromes dans la capitale, par les opérateurs d\'Albert Kahn';

                mapPictoColor = '#EC643C';
                mapPicto = 'ods-photo';


                DO_NOT_MODIFY_BELOW;

                ctxfields = {};
                values = {};
                activeFilters = {};
                ">

    <!-- ### GENERAL SETTINGS ### -->
    <!-- domain : (Domain URL) : Must contain the ID of the domain where the dataset is published.
    ex: 'discovery.opendatasoft.com'
    -->
        <!-- datasetid (Dataset ID) : Must contain the ID of the dataset
    ex: 'oeuvres-de-johannes-vermeer'
    -->
        <!-- headerBackgroundImage : Use an URL to show a background image in the header.
    -->

    <!-- ### FILTERS SETTINGS ### -->
    <!-- filters (Filters) : List of object that contains the IDs to generate the filters pannel.
          and multiple true or false to allow the user to select multiple values in the filter.
          NB: the field must be a facet in the dataset
          NB: alphanumerical sort is applied in the filter view
           ex: [
                    {'id':'filterid','multiple':true},
                    {'id':'filterid2','multiple':false}
               ]
      -->
    <!-- resetFiltersButton (boolean) : add a reset filters button after filters block -->
    <!-- resetFiltersButtonLabel (Label of the button) : test to display when a filter is selected
         ex: 'Clear all filters'
    -->


    <!-- ### LIST VIEW SETTINGS ### -->
    <!-- fieldsList (List configuration) : Set the list of field IDs
    ex: ['title','category','genre','date']
    -->
        <!-- fieldLink (Link to an external resource) : If available, the field ID of some external resource as a web URL
    ex: 'link'
    -->
        <!-- fieldLinkLabel (The label of that link) : Label of the link button
    ex: 'Read more here'
    -->
        <!-- fieldImage (Field id of the image field if any)
         ex: 'image'
    -->
        <!-- imagePosition (Image position) : Image position in the card, can be 'top' or 'left'
         ex: 'left'
    -->


    <!-- ### KPIS SETTINGS ### -->
    <!-- KPIS settings is a list of object that describes each KPI
    List of available keys are :
    - title (Name of the KPI) ex: 'Average # of citizens'
    - function (function of the aggregation) ex: 'SUM'
    - expression (field id that contains numerical values to aggregate) ex: 'population'
    - precision (Decimal precision of the KPI) ex: 2
    - unit (KPI unit) ex: 'citizens'
    - faicon (FontAwesome icon id) ex: 'square-o'

    title, function, expression are MANDATORY
    the others are optionnal

    Available functions are SUM, AVG, COUNT, STD, MAX, MIN.
    Please see the documentation for more information
    https://help.opendatasoft.com/widgets/#/api/ods-widgets.directive:odsAggregation

    Please see all available icons here
    https://fontawesome.com/v4.7.0/icons/

    ex:
    kpis = [
        {
        'title': 'Taille moyenne',
        'function': 'AVG',
        'expression': 'surface',
        'precision': 2,
        'unit': 'm2',
        'faicon': 'square-o'
        },
        {
        'title': 'Nombre d\'oeuvre référencées',
        'function': 'COUNT'
        }
    ];
    -->

    <!-- ### Subtitle ### -->
    <!-- subtitle : "Some texte" -->


    <!-- mapPictoColor = '#EC643C'
         mapPicto = 'ods-photo'
    Map picto settings to set the color and the picto from this list : https://help.opendatasoft.com/platform/en/other_resources/pictograms_reference/pictograms_reference.html
    -->

    <!-- DO NOT MODIFY -->
    <!-- Technical fields, do not modify please -->

    <ods-dataset-context context="ctx"
                         ctx-domain="{{ domain }}"
                         ctx-dataset="{{ datasetid }}">
      <!-- Private datasets can be accessed by adding an API Key.
        Add this param to the <ods-dataset-context above,
        where XXX is your apikey :
        ctx-apikey="XXX"
      -->

      <div class="page-header" style="background-image: url({{ headerBackgroundImage }})">
        <h1 class="page-title">
          {{ ctx.dataset.metas.title }}
        </h1>
      </div>

      <!-- Page subtitle -->
      <h2 class="page-subtitle" ng-bind-html="ctx.dataset.metas.description | shortSummary"></h2>

      <span ng-repeat="field in ctx.dataset.fields">
        {{ ctxfields[field.name] = field.label; '' }}
      </span>

      <span ng-repeat="filter in filters">
        {{ ctx.parameters['refine.' + filter.id] = activeFilters[filter.id] ; '' }}
      </span>

      <!-- KPIs -->
      <section class="kpis-container row row-equal-height">
        <div class="{{ 'col-md-' + (12/itemsPerRow) }} margin-bottom-20"
             ng-repeat="kpi in kpis">
          <!-- KPI box component -->
          <div class="kpi-card"
               ods-aggregation="agg"
               ods-aggregation-context="ctx"
               ods-aggregation-function="{{ kpi.function }}"
               ods-aggregation-expression="{{ kpi.expression }}">
            <i class="kpi-icon fa fa-{{ kpi.faicon || 'gitlab' }}" aria-hidden="true"></i>
            <h2 class="kpi-title">
              {{ (agg || 0) | number : (kpi.precision || 0) }}
              <span ng-if="kpi.unit" class="kpi-unit">{{ kpi.unit }}</span>
            </h2>
            <p class="kpi-description">
              {{ kpi.title }}
            </p>
          </div>
        </div>
      </section>



      <!-- Section 1 -->
      <section class="page-section">
        <h2 class="section-title">
          {{ subtitle }}
        </h2>

        <div class="content-card">
          <p class="content-card-description">
            Utilisez les filtres ci-dessus pour affiner votre recherche.
          </p>

          <!-- SEARCH -->
          <div class="search-module">
            <i class="fa fa-search search-module-icon" aria-hidden="true"></i>
            <input placeholder="Rechercher"
                   ng-model="ctx.parameters['q']"
                   ng-model-options="{ updateOn: 'keyup', debounce: { 'default': 300, 'blur': 0 }}"
                   class="search-module-input"
                   type="text"/>
            <button class="search-module-clear"
                    ng-if="ctx.parameters['q']"
                    ng-click="ctx.parameters['q'] = undefined">
              <i class="fa fa-times-circle" aria-hidden="true"></i>
            </button>
          </div>


          <!-- FILTERS -->
          <div class="filter-list"
               ng-init="dropdown.open = '';
                        select = {}">
            <div ng-repeat="filter in filters">
              {{ ctx.parameters['disjunctive.' + filter.id] = true; '' }}
              <div ods-facet-results="categories"
                   ods-facet-results-facet-name="{{ filter.id }}"
                   ods-facet-results-context="ctx"
                   ods-facet-results-sort="alphanum">
                <ods-select ng-if="ctxfields[filter.id]"
                            selected-values="activeFilters[filter.id]"
                            multiple="filter.multiple"
                            options="categories"
                            label-modifier="name"
                            value-modifier="name"
                            placeholder="{{ ctxfields[filter.id] }}"></ods-select>
              </div>
            </div>

            <div class="clear-filters"
                 ng-show="(activeFilters | values).join('')">
              <div class="clear-filters-button"
                   role="button"
                   ng-click="activeFilters = {}">
                {{ resetFiltersButtonLabel }}
                <i class="fa fa-times-circle" aria-hidden="true"></i>
              </div>
            </div>
          </div>

          <div class="row">
            <div class="col-md-5 col-xs-12">
              <ul class="result-list">
                <li class="result"
                    ng-repeat="item in items"
                    ods-results="items"
                    ods-results-context="ctx"
                    ods-results-max="1000"
                    ng-click="ctx.parameters['refine.' + resultTitle] = (ctx.parameters['refine.' + resultTitle]?undefined:item.fields[resultTitle])"
                    ng-class="{'result-img-horizontal': imagePosition === 'left' }">
                  <div class="result-img"
                       ng-if="item.fields[fieldImage]"
                       style="{{ 'background-image: url(https://' + domain + '/explore/dataset/' + datasetid + '/files/' + item.fields[fieldImage].id + '/300/);' }}">
                  </div>
                  <div class="result-content">
                    <h2 class="result-title">
                      {{ item.fields[resultTitle] }}
                    </h2>
                    <dl class="result-info">
                      <dt ng-repeat-start="field in fieldsList">
                        {{ ctxfields[field] }}
                      </dt>
                      <dd ng-repeat-end>{{ item.fields[field] }}</dd>
                    </dl>
                    <div ng-if="fieldLink">
                      <!-- fieldLink is used here
       For very simple usage, just set the href to the field value :
       - href="{{ item.fields[fieldLink] }}"

       For more advanced scenario, you can send the user to :
       - the dataset table filtered with the fieldLink value, through a text query
       href="/explore/dataset/{{ datasetid }}/table?q={{ item.fields[fieldLink] }}"
       - the dataset table filtered with the fieldLink value, through a refine on the field
       href="/explore/dataset/{{ datasetid }}/table?refine.{{ fieldLink }}={{ item.fields[fieldLink] }}"
       - a page using url-sync=true setting :
       href="/pages/yourpage/?refine.{{ fieldLink }}={{ item.fields[fieldLink] }}"
       -->

                      <a href="{{ 'https://' + domain + '/explore/dataset/' + datasetid + '/files/' + item.fields[fieldLink].id + '/300/' }}"
                         target="_blank"
                         class="content-card-button"
                         ng-if="item.fields[fieldLink]">
                        {{ fieldLinkLabel }}
                      </a>
                    </div>
                  </div>
                </li>
              </ul>
            </div>

            <div class="col-md-7 col-xs-12">
              <ods-map no-refit="false"
                       scroll-wheel-zoom="false">
                <ods-map-layer-group>
                  <ods-map-layer context="ctx"
                                 color="{{ mapPictoColor }}"
                                 picto="{{ mapPicto }}"
                                 show-marker="true"
                                 display="auto"
                                 shape-opacity="0.5"
                                 point-opacity="1"
                                 border-color="#FFFFFF"
                                 border-opacity="1"
                                 border-size="1"
                                 border-pattern="solid"
                                 size="4"
                                 size-min="3"
                                 size-max="5"
                                 size-function="linear"></ods-map-layer>
                </ods-map-layer-group>
              </ods-map>
            </div>
          </div>
        </div>
        <a href="{{ ctx.domainUrl }}/explore/dataset/{{ datasetid }}" target="_blank">Accéder aux données source</a>
      </section>
    </ods-dataset-context>
  </div>
</div>
/* General Layout
========================================================================== */

main.main--page {
  margin: 0;
}

/* Body text */
p {
  text-align: justify;
  margin-top: 0;
  margin-bottom: 1.5rem;
}

ul {
  list-style: none;
  padding-left: 0;
}

@media screen and (min-width: 992px) {
  .row-equal-height {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: wrap;
    flex-wrap: wrap;
    margin-bottom: 20px;
  }
  /* Fix for early content wrapping in Safari*/
  .row-equal-height:before,
  .row-equal-height:after {
    content: normal;
  }
}

.margin-bottom-20 {
  margin-bottom: 20px;
}

.page-section {
  margin-top: 2rem;
  margin-bottom: 6rem;
}

.section-title {
  font-size: 2.2rem;
  font-weight: 400;
  color: #FFF;
  background-color: var(--highlight);
  text-align: center;
  padding: 10px;
  margin-top: 1.5rem;
  margin-bottom: 1.5rem;
}

/* Header
========================================================================== */

.page-header {
  background-color: var(--highlight);
  min-height: 180px; /* Change the height of the image here by increasing or decreasing the pixels. */
  /* Properties of the title located within the image banner */
  display: flex;
  justify-content: center;
  align-items: flex-end;
  margin-bottom: 40px;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center center;
  background-image: url('');
}

.page-title {
  color: var(--highlight);
  text-align: center;
  background-color: var(--page-background);
  position: relative;
  vertical-align: bottom;
  margin: 0;
  font-weight: 400;
  font-size: 2.5rem;
  padding: 15px 20px 10px 20px;
  border-radius: 7px 7px 0px 0px;
}

.page-subtitle {
  font-weight: 400;
  margin-top: 0;
  margin-bottom: 20px;
  font-size: 1.14rem;
}


/* Search Module
========================================================================== */

.search-module {
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid #dee5ef;
  margin-bottom: 13px;
  transition: all .2s;
}

.search-module:hover,
.search-module:focus-within {
  border-bottom-color: var(--links);
}

.search-module-icon {
  color: #898d92;
  margin-right: 8px;
  align-self: center;
}

.search-module-input {
  background-color: transparent;
  width: 100%;
  outline: none;
  border: none;
  padding: 12px 0;
  transition: all .2s;
  color: var(--text);
}

.search-module-input::placeholder {
  transition: all .2s;
}

.search-module-clear {
  color: #898d92;
  font-size: 1rem;
  background: transparent;
  border: none;
  margin: 0;
  outline: none;
  padding: 0 0 0 12px;
  transition: all .2s;
}

.search-module-clear:hover {
  opacity: .65;
}

.search-module:hover .search-module-icon,
.search-module:focus-within .search-module-icon,
.search-module:hover .search-module-input::placeholder,
.search-module:focus-within .search-module-input::placeholder {
  color: var(--links)
}


/* Filters
========================================================================== */

.filter-list {
  display: flex;
  flex-wrap: wrap;
  position: relative;
}
.filter-list > * {
  margin: 0 0 10px;
  width: 100%;
}
.odswidget-select .odswidget-select-dropdown.open .odswidget-select-dropdown-menu {
  width: 100%
}
.clear-filters {
  display: flex;
  align-items: center;
  justify-content: center;
}
.clear-filters-button:hover {
  opacity: 0.65;
}
.odswidget-select,
.odswidget-select .odswidget-select-dropdown {
  width: 100%;
}
@media screen and (min-width: 500px) {
  .filter-list > * {
    margin: 0 10px 10px 0;
    width: inherit;
  }
  .odswidget-select .odswidget-select-dropdown.open .odswidget-select-dropdown-menu {
    width: max-content;
    min-width: 240px;
  }
}


/* Content Card
========================================================================== */

.content-card {
  background-color: var(--boxes-background);
  border-radius: 4px;
  height: 100%;
  padding: 26px;
  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.13);
  margin-bottom: 10px;
}

.content-card-description {
  color: var(--text);
  font-size: 1rem;
  line-height: 1.5;
  font-weight: normal;
  margin-top: 0;
  margin-bottom: 26px;
  max-width: 100%;
}

.content-card-button {
  color: var(--highlight);
  border: 1px solid var(--highlight);
  background: transparent;
  display: inline-block;
  text-align: center;
  font-size: .867rem;
  border-radius: 4px;
  padding: .5rem 1.15rem;
  text-decoration: none;
  transition: all .2s;
}

.content-card-button:hover {
  background-color: var(--highlight);
  color: #FFFFFF;
  text-decoration: none;
}


/* KPI Card
========================================================================== */

@media screen and (min-width: 992px) {
  .kpis-container {
    display: flex;
    justify-content: center;
  }
}

.kpi-card {
  background-color: var(--boxes-background);
  height: 100%;
  padding: 39px;
  border-radius: 4px;
  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.13);
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -ms-flex-direction: column;
  flex-direction: column;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  text-align: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  text-align: center;
}

.kpi-icon {
  color: var(--highlight);
  color: var(--secondary-color);
  font-size: 4rem;
  margin-top: 0;
  margin-bottom: 13px;
  max-width: 100%;
}

.kpi-title {
  font-weight: normal;
  color: var(--highlight);
  font-size: 3.2rem;
  margin-top: 0;
  margin-bottom: 13px;
  max-width: 100%;
}

.kpi-unit {
  font-size: 0.8em;
  color: var(--secondary-color);
}

.kpi-description {
  color: var(--text);
  font-size: 1rem;
  line-height: 1.5;
  font-weight: normal;
  margin-top: 0;
  margin-bottom: 0;
  max-width: 100%;
}


/* Results and Map
========================================================================== */

.odswidget-map__map {
  min-height: 550px;
}

@media screen and (min-width: 991px) {
  .col-md-5 {
    padding-right: 0;
  }

  .col-md-7 {
    padding-left: 0;
  }
}


.result-list {
  list-style: none;
  padding-left: 0;
  margin-top: 0;
  height: 550px;
  overflow-y: auto;
}

.result {
  margin-bottom: 10px;
  padding: 0 1rem 1rem 0;
  border-bottom: 1px solid #DEE5EF;
}

.result.result-img-horizontal {
  display: flex;
}

.result-img-horizontal .result-img {
  flex: 0 0 35%;
  width: 35%;
  height: auto;
}

.result-img {
  disaply: block;
  height: 118px;

  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.result-img-horizontal .result-content {
  margin-left: 0.7rem;
}
:not(.result-img-horizontal) .result-content {
  margin-top: 0.7rem;
}

.result-title {
  color: var(--titles);
  font-size: 1rem;
  font-weight: bold;
  margin-top: 0;
  margin-bottom: 5px;
}

.result-info {
  list-style: none;
  padding-left: 0;
  margin-top: 0;
  margin-bottom: 10px;
  overflow-wrap: break-word;
}

.result-info dt {
  font-size: .9rem;
  opacity: .9;
}

.result-info dd {
  margin-left: 0;
  margin-bottom: 5px;
}