<template> <div v-frag> <div class="fourteen wide column"> <h1 v-if="feature && $router.history.current.name === 'editer-signalement'" > Mise à jour du signalement "{{ feature.title || feature.feature_id }}" </h1> <h1 v-else-if=" feature_type && $router.history.current.name === 'ajouter-signalement' " > Création d'un signalement <small>[{{ feature_type.title }}]</small> </h1> <form id="form-feature-edit" action="" method="post" enctype="multipart/form-data" class="ui form" > <!-- Feature Fields --> <div class="two fields"> <div class="required field"> <label :for="form.title.id_for_label">{{ form.title.label }}</label> <input type="text" required :maxlength="form.title.field.max_length" :name="form.title.html_name" :id="form.title.id_for_label" v-model="form.title.value" @blur="updateStore" /> {{ form.title.errors }} </div> <div class="required field"> <label :for="form.status.id_for_label">{{ form.status.label }}</label> <Dropdown :options="form.status.field.choices" :selected="selected_status" :selection.sync="selected_status" /> {{ form.status.errors }} </div> </div> <div class="field"> <label :for="form.description.id_for_label">{{ form.description.label }}</label> <textarea :name="form.description.html_name" rows="5" v-model="form.description.value" @blur="updateStore" ></textarea> {{ form.description.errors }} </div> <!-- Geom Field --> <div class="field"> <label :for="form.geom.id_for_label">{{ form.geom.label }}</label> <!-- Import GeoImage --> <div v-frag v-if="feature_type && feature_type.geom_type === 'point'"> <p> <button id="add-geo-image" type="button" class="ui compact button" > <i class="file image icon"></i>Importer une image géoréférencée </button> Vous pouvez utiliser une image géoréférencée pour localiser le signalement. </p> <p> <button id="create-point-geoposition" type="button" class="ui compact button" > <i class="ui map marker alternate icon"></i>Positionner le signalement à partir de votre géolocalisation </button> </p> <span id="erreur-geolocalisation" style="display: none"> <div class="ui negative message"> <div class="header"> Une erreur est survenue avec la fonctionnalité de géolocalisation </div> <p id="erreur-geolocalisation-message"></p> </div> <br /> </span> </div> {{ form.geom.errors }} <!-- Map --> <input type="hidden" :name="form.geom.html_name" :id="form.geom.id_for_label" v-model="form.geom.value" @blur="updateStore" /> <div class="ui tab active map-container" data-tab="map"> <div id="map"></div> <!-- // todo: ajouter v-if --> <!-- {% if serialized_base_maps|length > 0 %} {% include "geocontrib/map-layers/sidebar-layers.html" with basemaps=serialized_base_maps layers=serialized_layers project=project.slug%} {% endif %} --> <SidebarLayers /> </div> </div> <!-- Extra Fields --> <div class="ui horizontal divider">DONNÉES MÉTIER</div> <!-- // Todo: Récupérer les "extra_form" de l'API --> <div v-for="(field, index) in extra_form_with_values" :key="field.field_type + index" class="field" > <div v-frag v-if="field.field_type === 'char'"> <label for="field.name">{{ field.label }}</label> <input type="text" :name="field.name" :id="field.name" v-model="field.value" @blur="updateStore_extra_form" /> </div> <div v-frag v-else-if="field.field_type === 'list'"> <label for="field.name">{{ field.label }}</label> <Dropdown :options="field.choices" :selected="selected_extra_form_list" :selection.sync="selected_extra_form_list" /> </div> <div v-frag v-else-if="field.field_type === 'integer'"> <label for="field.name">{{ field.label }}</label> <div class="ui input"> <!-- //* si click sur fléche dans champ input, pas de focus, donc pas de blur, donc utilisation de @change --> <input type="number" :name="field.name" :id="field.name" v-model.number="field.value" @change="updateStore_extra_form" /> </div> </div> <div v-frag v-else-if="field.field_type === 'boolean'"> <div class="ui checkbox"> <input type="checkbox" :checked="field.value" :name="field.name" :id="field.name" @change="updateStore_extra_form" /> <label for="field.name">{{ field.label }}</label> </div> </div> <div v-frag v-else-if="field.field_type === 'date'"> <label for="field.name">{{ field.label }}</label> <input type="date" :name="field.name" :id="field.name" v-model="field.value" @blur="updateStore_extra_form" /> </div> <div v-frag v-else-if="field.field_type === 'decimal'"> <label for="field.name">{{ field.label }}</label> <div class="ui input"> <input type="number" step=".01" :name="field.name" :id="field.name" v-model.number="field.value" @change="updateStore_extra_form" /> </div> </div> <div v-frag v-else-if="field.field_type === 'text'"> <label :for="field.name">{{ field.label }}</label> <textarea :name="field.name" rows="3" v-model="field.value" @blur="updateStore_extra_form" ></textarea> </div> {{ field.errors }} </div> <!-- Pièces jointes --> <div class="ui horizontal divider">PIÈCES JOINTES</div> <!-- {{ attachment_formset.non_form_errors }} --> <div id="formsets-attachment"> <!-- {{ attachment_formset.management_form }} --> <FeatureAttachmentForm v-for="form in attachmentFormset" :key="form.dataKey" :attachmentForm="form" /> </div> <button @click="add_attachement_formset" id="add-attachment" type="button" class="ui compact basic button button-hover-green" > <i class="ui plus icon"></i>Ajouter une pièce jointe </button> <!-- Signalements liés --> <div class="ui horizontal divider">SIGNALEMENTS LIÉS</div> <!-- {{ linked_formset.non_form_errors }} --> <div id="formsets-link"> <!-- {{ linked_formset.management_form }} --> <FeatureLinkedForm v-for="form in linkedFormset" :key="form.dataKey" :linkedForm="form" :features="features" /> </div> <button @click="add_linked_formset" id="add-link" type="button" class="ui compact basic button button-hover-green" > <i class="ui plus icon"></i>Ajouter une liaison </button> <div class="ui divider"></div> <button @click="postForm" type="button" class="ui teal icon button"> <i class="white save icon"></i> Enregistrer les changements </button> </form> </div> </div> </template> <script> import frag from "vue-frag"; import { mapGetters, mapState } from "vuex"; import FeatureAttachmentForm from "@/components/feature/FeatureAttachmentForm"; import FeatureLinkedForm from "@/components/feature/FeatureLinkedForm"; import Dropdown from "@/components/Dropdown.vue"; import SidebarLayers from "@/components/map-layers/SidebarLayers"; import { configuration } from "@/assets/config/config.js"; import L from "leaflet"; import "leaflet-draw"; import { mapUtil } from "@/assets/js/map-util.js"; const axios = require("axios"); export default { name: "Feature_edit", directives: { frag, }, components: { FeatureAttachmentForm, FeatureLinkedForm, Dropdown, SidebarLayers, }, computed: { ...mapState(["project"]), ...mapState("feature", [ "attachmentFormset", "linkedFormset", "features", "extra_form", ]), ...mapGetters("feature_type", ["feature_type"]), feature: function () { return this.$store.state.feature.features.find( (el) => el.feature_id === this.$route.params.slug_signal ); }, selected_status: { get() { return this.form.status.value; }, set(newValue) { this.form.status.value = newValue; this.updateStore(); }, }, extra_form_with_values: function () { return this.extra_form.map((el) => { return { ...el, value: el.value ? el.value : null }; }); }, selected_extra_form_list: { get() { return this.extra_form_with_values.find( (el) => el.field_type === "list" ).value; }, set(newValue) { const index = this.extra_form_with_values.findIndex( (el) => el.field_type === "list" ); this.extra_form_with_values[index].value = newValue; this.updateStore_extra_form(); }, }, }, data() { return { attachmentDataKey: 0, linkedDataKey: 0, form: { title: { errors: null, id_for_label: "name", field: { max_length: 30, }, html_name: "name", label: "Nom", value: "", }, status: { errors: null, id_for_label: "status", field: { choices: ["Brouillon", "Publié", "Archivé"], }, html_name: "status", label: "Statut", value: "Brouillon", }, description: { errors: null, id_for_label: "description", html_name: "description", label: "Description", value: "", }, geom: { label: "Localisation", }, }, }; }, methods: { add_attachement_formset() { this.$store.commit("feature/ADD_ATTACHMENT_FORM", this.attachmentDataKey); // * create an object with the counter in store this.attachmentDataKey += 1; // * increment counter for key in v-for }, add_linked_formset() { this.$store.commit("feature/ADD_LINKED_FORM", this.linkedDataKey); // * create an object with the counter in store this.linkedDataKey += 1; // * increment counter for key in v-for }, updateStore() { this.$store.commit("feature/UPDATE_FORM", { title: this.form.title.value, status: this.form.status.value, description: this.form.description, // ? geom ? }); }, updateStore_extra_form() { this.$store.commit( "feature/UPDATE_EXTRA_FORM", this.extra_form_with_values ); }, postForm() { if (this.form.title.value) { this.form.title.errors = null; this.$store.dispatch("feature/POST_FEATURE"); } else { this.form.title.errors = "Veuillez compléter ce champ."; } }, initMap(){ var geomLeaflet = { 'point': 'circlemarker', 'linestring': 'polyline', 'polygon': 'polygon' } var geomType = "{{ feature_type.geom_type }}" var drawConfig = { polygon: false, marker: false, polyline: false, rectangle: false, circle: false, circlemarker: false, } drawConfig[geomLeaflet[geomType]] = true L.drawLocal = { draw: { toolbar: { actions: { title: 'Annuler le dessin', text: 'Annuler' }, finish: { title: 'Terminer le dessin', text: 'Terminer' }, undo: { title: 'Supprimer le dernier point dessiné', text: 'Supprimer le dernier point' }, buttons: { polyline: 'Dessiner une polyligne', polygon: 'Dessiner un polygone', rectangle: 'Dessiner un rectangle', circle: 'Dessiner un cercle', marker: 'Dessiner une balise', circlemarker: 'Dessiner un point' } }, handlers: { circle: { tooltip: { start: 'Cliquer et glisser pour dessiner le cercle.' }, radius: 'Rayon' }, circlemarker: { tooltip: { start: 'Cliquer sur la carte pour placer le point.' } }, marker: { tooltip: { start: 'Cliquer sur la carte pour placer la balise.' } }, polygon: { tooltip: { start: 'Cliquer pour commencer à dessiner.', cont: 'Cliquer pour continuer à dessiner.', end: 'Cliquer sur le premier point pour terminer le dessin.' } }, polyline: { error: '<strong>Error:</strong> shape edges cannot cross!', tooltip: { start: 'Cliquer pour commencer à dessiner.', cont: 'Cliquer pour continuer à dessiner.', end: 'Cliquer sur le dernier point pour terminer le dessin.' } }, rectangle: { tooltip: { start: 'Cliquer et glisser pour dessiner le rectangle.' } }, simpleshape: { tooltip: { end: 'Relâcher la souris pour terminer de dessiner.' } } } }, edit: { toolbar: { actions: { save: { title: 'Sauver les modifications', text: 'Sauver' }, cancel: { title: 'Annuler la modification, annule toutes les modifications', text: 'Annuler' }, clearAll: { title: 'Effacer l\'objet', text: 'Effacer' } }, buttons: { edit: 'Modifier l\'objet', editDisabled: 'Aucun objet à modifier', remove: 'Supprimer l\'objet', removeDisabled: 'Aucun objet à supprimer' } }, handlers: { edit: { tooltip: { text: 'Faites glisser les marqueurs ou les balises pour modifier l\'élément.', subtext: 'Cliquez sur Annuler pour annuler les modifications..' } }, remove: { tooltip: { text: 'Cliquez sur un élément pour le supprimer.' } } } } }; var drawnItems = new L.FeatureGroup(); //console.log(drawnItems); //console.log(configuration); var mapDefaultViewCenter = configuration.DEFAULT_MAP_VIEW.center; var mapDefaultViewZoom = configuration.DEFAULT_MAP_VIEW.zoom; // Create the map, then init the layers and features this.map = mapUtil.createMap({ mapDefaultViewCenter, mapDefaultViewZoom }); // mapUtil.addLayers(layers, serviceMap, optionsMap); const currentFeatureId="12"; const url=configuration.BASE_URL+"/test_data/features.json"; axios.get(url) .then((response) => { const features = response.data.features; if (features) { const allFeaturesExceptCurrent = features.filter(feat => feat.id !== currentFeatureId); mapUtil.addFeatures(allFeaturesExceptCurrent); } }) .catch((error) => { throw error; }); let self=this; // ------ Listen Sidebar events ---------- // // Listen custom events triggered by the sidebar-layers document.addEventListener('add-layers', (event) => { mapUtil.removeLayers(self.map); // Reverse is done because the first layer in order has to be added in the map in last. // Slice is done because reverse() changes the original array, so we make a copy first mapUtil.addLayers(event.detail.slice().reverse(), configuration.DEFAULT_BASE_MAP.SERVICE, configuration.DEFAULT_BASE_MAP.OPTIONS); }); document.addEventListener('update-opacity', (event) => { mapUtil.updateOpacity(event.detail.layerId, event.detail.opacity); }); document.addEventListener('change-layers-order', (event) => { // Reverse is done because the first layer in order has to be added in the map in last. // Slice is done because reverse() changes the original array, so we make a copy first mapUtil.updateOrder(event.detail.layers.slice().reverse()); }); // --------- End sidebar events ---------- // Check if at least one basemap exist. If not, use the default application const basemaps = undefined;//JSON.parse(document.getElementById('basemaps').textContent); if (!basemaps || basemaps && basemaps.length === 0) { mapUtil.addLayers(null, configuration.DEFAULT_BASE_MAP.SERVICE, configuration.DEFAULT_BASE_MAP.OPTIONS); } this.map.addLayer(drawnItems); var drawControlFull = new L.Control.Draw({ position: 'topright', edit: { featureGroup: drawnItems }, draw: drawConfig, }) /*var drawControlEditOnly = new L.Control.Draw({ position: 'topright', edit: { featureGroup: drawnItems }, draw: false })*/ this.map.addControl(drawControlFull); } }, created() { if (!this.project) { this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug); } this.$store.commit( "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG", this.$route.params.slug_type_signal ); }, mounted() { if (this.$router.history.current.name === "editer-signalement") { for (let el in this.feature) { if (el && this.form[el]) this.form[el].value = this.feature[el]; } } this.initMap(); }, }; // TODO : add script from django and convert: </script> <style> #map { height: 70vh; width: 100%; border: 1px solid grey; } @media only screen and (max-width: 767px) { #map { height: 80vh; } } /* // ! missing style in semantic.min.css, je ne comprends pas comment... */ .ui.right.floated.button { float: right; margin-right: 0; margin-left: 0.25em; } /* // ! margin écrasé par class last-child first-child, pas normal ... */ .ui.segment { margin: 1rem 0 !important; } </style>