Generators - Full screen Map Generator

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

Fullscreen Map

View a fullscreen map with a side panel where the listing results can be filtered, searched and clicked on.

<!-- V2.1 :
    - Add ods-select and multiple choice option for filters
    - Add clear all filter button when one filter is applied
    - Add documentation
  -->

<!-- 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 ng-init="domain = 'mesr.opendatasoft.com';
                datasetid = 'fr-esr-principaux-etablissements-enseignement-superieur';

                title = 'Établissements près de chez vous';

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

                resultTitle = 'uo_lib';
                fieldsList = ['adresse_uai', 'dep_nom', 'numero_telephone_uai'];
                fieldLink = 'url';
                fieldLinkLabel = 'Visiter le site web';
                mapLinkLabel = 'Voir sur la carte';

                viewListButtonText = 'Voir la liste';
                viewDetailsButtonText = 'Voir les détails';
                backToResultsButtonText = 'Retour aux résultats';

                mapNoRefit = false;


                DO_NOT_MODIFY_BELOW;

                ctxfields = {};
                isVisible = false;
                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'
    -->

    <!-- title (Title of the page) :
    ex: 'List of interest around you'
    -->

    <!-- ### 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 ### -->
    <!-- resultTitle (Title of the item) : Set the item title from a field ID
      ex: 'title';
    -->
    <!-- fieldsList (List configuration) : Set the list of field IDs
      ex: ['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'
    -->
    <!-- mapLinkLabel (The label of the item button) : used to select the item and see it on the map
      ex: 'See on the map'
    -->

    <!-- ### MOBILE VIEW SETTINGS ### -->
    <!-- viewListButtonText : when displaying the map, the bottom button to open the list view
      ex: 'See the list'
    -->
    <!-- viewDetailsButtonText : when an item is selected on the map, the bottom button to open the list view with the selected item
      ex: 'See the item details'
    -->
    <!-- backToResultsButtonText : when the list view is opened, to close button to go back to the map
      ex: "Back to the list'
    -->

    <!-- ### MAP SETTINGS ### -->
    <!-- mapNoRefit : true / false refit the map when filtering -->


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

    <ods-dataset-context context="ctx"
                         ctx-domain="{{ domain }}"
                         ctx-dataset="{{ datasetid }}">

      <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>

        <aside class="cl-modal"
               ng-class="{ 'is-visible' : modal.isVisible}">
            <div class="cl-modal-header">
                <button class="cl-modal-close-mobile"
                        title="Close modal"
                        ng-click="modal.isVisible = false">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
                         stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
                         class="feather feather-x">
                        <line x1="18" y1="6" x2="6" y2="18"></line>
                        <line x1="6" y1="6" x2="18" y2="18"></line>
                    </svg>
                </button>
                <h1 class="cl-modal-title">
                    {{ title }}
                </h1>
                <div class="search-module">
                    <i class="fa fa-search search-module-icon" aria-hidden="true"></i>
                    <input placeholder="Search"
                           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>

            <div class="cl-modal-content">
                <ul class="result-list">
                    <li class="result"
                        ng-repeat="item in items"
                        ods-results="items"
                        ods-results-context="ctx"
                        ods-results-max="1000">

                        <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 class="result-footer">
                            <a href="{{ item.fields[fieldLink] }}"
                               ng-if="fieldLink && item.fields[fieldLink]"
                               target="_blank">
                                {{ fieldLinkLabel }}
                            </a>
                            <button class="result-button"
                                    ng-if="!ctx.parameters['refine.' + resultTitle]"
                                    ng-click="modal.isVisible = false;
                                  ctx.parameters['refine.' + resultTitle] = item.fields[resultTitle];">
                                {{ mapLinkLabel }}
                            </button>
                            <button class="result-button"
                                    ng-if="ctx.parameters['refine.' + resultTitle]"
                                    ng-click="ctx.parameters['refine.' + resultTitle] = undefined">
                                {{ backToResultsButtonText }}
                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                                     fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                     stroke-linejoin="round" class="feather feather-x">
                                    <line x1="18" y1="6" x2="6" y2="18"></line>
                                    <line x1="6" y1="6" x2="18" y2="18"></line>
                                </svg>
                            </button>
                        </div>
                    </li>
                </ul>
            </div>
        </aside>


        <ods-map no-refit="{{ mapNoRefit }}"
                 scroll-wheel-zoom="true"
                 display-control="false"
                 display-control-single-layer="false"
                 toolbar-fullscreen="false"
                 toolbar-drawing="false">
            <ods-map-layer-group>
                <ods-map-layer context="ctx"
                               refine-on-click-context="ctx"
                               refine-on-click-map-field="{{ resultTitle }}"
                               refine-on-click-context-field="{{ resultTitle }}"
                               color="#0098c3"
                               picto="college"
                               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 class="cl-modal-open-mobile"
             ng-click="modal.isVisible = true">
            <svg xmlns="http://www.w3.org/2000/svg"
                 width="24"
                 height="24"
                 viewBox="0 0 24 24"
                 fill="none"
                 stroke="currentColor"
                 stroke-width="2"
                 stroke-linecap="round"
                 stroke-linejoin="round"
                 class="feather feather-chevron-up">
                <polyline points="18 15 12 9 6 15"></polyline>
            </svg>
            <h3 class="cl-modal-open-mobile-title"
                ng-if="!ctx.parameters['refine.' + resultTitle]">
                {{ viewListButtonText }}
            </h3>
            <h3 class="cl-modal-open-mobile-title"
                ng-if="ctx.parameters['refine.' + resultTitle]">
                {{ viewDetailsButtonText }}
            </h3>
        </div>
    </ods-dataset-context>
</div>
/* General Layout
========================================================================== */

body, .main--page {
    padding: 0;
    margin: 0;
}

.ods-front-header, .ods-front-footer, .ods-content:after, footer {
    display: none;
}

ods-dataset-context {
    display: block;
    position: relative;
}

.odswidget.odswidget-map {
    width: 100%;
    height: 100vh;
}

/* Modal
   ========================================================================== */

.cl-modal {
    display: none;
    flex-direction: column;
    background-color: #FFFFFF;
    box-shadow: 0 1px 5px rgba(0,0,0,0.65);
    width: 100vw;
    height: 100vh;
    position: absolute;
    top: 35%;
    left: 0;
    border-radius: 6px 6px 0 0;
    z-index: 30;
    overflow: hidden; /* Disable horizontal scroll */
}

@media screen and (min-width: 768px) {
    .cl-modal {
        display: flex;
        width: 400px;
        height: calc(100vh - 55px);
        top: 10px;
        left: 47px;
        border-radius: 6px;
    }
}

.cl-modal.is-visible {
    display: flex;
}

.cl-modal-open-mobile {
    position: absolute;
    bottom: 0;
    text-align: center;
    width: 100vw;
    background-color: #FFFFFF;
    padding: 13px;
    z-index: 20;
    border-radius: 6px 6px 0 0;
    box-shadow: 0 0 5px rgba(0,0,0,0.65);
    transition: all .2s;
}

.cl-modal-open-mobile:hover {
    background-color: #f2f2f2;
}

@media screen and (min-width: 768px) {
    .cl-modal-open-mobile {
        display: none;
    }
}

.cl-modal-open-mobile-title {
    margin: 0;
    font-weight: bold;
}

.cl-modal-close-mobile {
    position: absolute;
    top: 5px;
    right: 5px;
    background-color: transparent;
    border-radius: 10000px;
    border: none;
    font-size: 2.5rem;
    opacity: .6;
    transition: all .2s;
}

.cl-modal-close-mobile:hover {
    background-color: #f2f2f2;
    opacity: 1;
}

@media screen and (min-width: 768px) {
    .cl-modal-close-mobile {
        display: none;
    }
}

.cl-modal-header {
    padding: 26px 26px 0 26px;
    box-shadow: 0 1px 2px rgba(0,0,0,.13);
}

.cl-modal-title {
    margin-top: 0;
    margin-bottom: 13px;
    text-align: center;
    font-weight: 400;
    font-size: 1.5rem;
}

.cl-modal-content {
    height: 100%;
    overflow-y: auto;
}


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

.search-module {
    display: flex;
    align-items: stretch;
    border-bottom: 1px solid #dee5ef;
    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)
}

/* Dropdown menu with pill styling
========================================================================== */

.filter-list {
    display: flex;
    flex-wrap: wrap;
    margin-bottom: 20px;
}

.filter-list > div {
    width: 100%;
    margin-top: 13px;
}

.clear-filters {
    display: flex;
    align-items: center;
    justify-content: center;
}
.clear-filters-button:hover {
    opacity: 0.65;
}

/* Results
   ========================================================================== */

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

.result {
    padding: 1rem 26px;
    border-top: 1px solid #DEE5EF;
}

.result:hover {
    background-color: #f5f7fa;
}

.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;
}

.result-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
}

.result-button {
    display: inline-flex;
    align-items: center;
    background-color: transparent;
    border: 1px solid var(--links);
    color: var(--links);
    line-height: 1.5;
    border-radius: 20px;
    padding: .1rem .8rem;
    margin-right: 4px;
    margin-bottom: 2px;
    transition: all .2s;
}

.result-button .feather-x {
    height: 15px;
    width: 15px;
    margin-left: 4px;
}

.result-button:hover {
    opacity: .65;
    text-decoration: none;
}