Widget pour choisir ou créer un lieu fonctionnel

This commit is contained in:
Evarin 2017-04-18 02:43:05 +02:00
parent 3b030aef70
commit 7e8384f086
9 changed files with 406 additions and 35 deletions

View file

@ -482,3 +482,51 @@ div.as-results {
#map_addlieu { #map_addlieu {
height: 500px; height: 500px;
} }
.window {
display:none;
position:fixed;
width: 100%;
height: 100%;
overflow: hidden;
top: 0;
left: 0;
z-index: 10;
&.visible {
display:block;
}
.window-bg {
background: #000;
opacity: 0.7;
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: -1;
}
.window-content {
position: relative;
margin: 0 auto;
padding: 20px;
margin-top: 50vh;
transform: translateY(-50%);
z-index: 1;
background: #eee;
max-width: 600px;
width: 90%;
max-height: 100%;
overflow: auto;
}
}
.lieu-ui {
.map {
height: 400px;
width: 100%;
}
}

View file

@ -552,3 +552,50 @@ div.as-results ul li.as-message {
#map_addlieu { #map_addlieu {
height: 500px; height: 500px;
} }
/* line 486, ../../sass/screen.scss */
.window {
display: none;
position: fixed;
width: 100%;
height: 100%;
overflow: hidden;
top: 0;
left: 0;
z-index: 10;
}
/* line 496, ../../sass/screen.scss */
.window.visible {
display: block;
}
/* line 500, ../../sass/screen.scss */
.window .window-bg {
background: #000;
opacity: 0.7;
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: -1;
}
/* line 511, ../../sass/screen.scss */
.window .window-content {
position: relative;
margin: 0 auto;
padding: 20px;
margin-top: 50vh;
transform: translateY(-50%);
z-index: 1;
background: #eee;
max-width: 600px;
width: 90%;
max-height: 100%;
overflow: auto;
}
/* line 528, ../../sass/screen.scss */
.lieu-ui .map {
height: 400px;
width: 100%;
}

View file

@ -0,0 +1,194 @@
function SelectLieuWidget(STATIC_ROOT, target, callback) {
var map, input, autocomplete;
var $el = $(target);
var ui_el = $el.find('.lieu-ui');
var form_el = $el.find('form');
var content_el = $el.find('.window-content');
var ui_ready = false;
var lieux_db = {};
form_el.detach();
form_el.on("submit", nouveauLieu);
function initUI(){
$.each(ui_el.children(), function(i, item){$(item).remove();});
var map_el = $("<div>", {class: "map"});
input = $("<input>",
{type:"text",
placeholder:"Chercher un établissement..."});
ui_el.append(input);
ui_el.append(map_el);
// Affiche la carte
map = L.map(map_el[0]).setView([48.8422411,2.3430553], 13);
var layer = new L.StamenTileLayer("terrain");
map.addLayer(layer);
// Autocomplete
autocomplete = new google.maps.places.Autocomplete(input[0]);
autocomplete.setTypes(["geocode", "establishment"]);
autocomplete.addListener('place_changed', handlePlaceSearch);
}
this.showWidget = function() {
$el.addClass("visible").removeClass("ajout");
if(!ui_ready)
initUI();
form_el.detach();
}
this.closeWidget = function() {
$el.removeClass("visible");
}
// Icones
function makeIcon(couleur){
return L.icon({
iconUrl: STATIC_ROOT + 'images/marker-'+couleur+'.png',
iconSize: [36, 46],
iconAnchor: [18, 45],
popupAnchor: [0, -48]
})
}
var greenIcon = makeIcon('green');
var redIcon = makeIcon('red');
var blueIcon = makeIcon('blue');
// Callback de l'autocomplete
function handlePlaceSearch() {
var place = autocomplete.getPlace();
if (!place.geometry) {
return;
}
console.log(place);
if (lieux_db.suggestion !== undefined) {
lieux_db.suggestion.marqueur.remove();
lieux_db.suggestion = undefined;
}
// Processing du lieu
var data = {};
$.each(place.address_components, function(i, obj) {
for (var j=0; j<obj.types.length; j++) {
switch(obj.types[j]) {
case "locality":
data.ville = obj.long_name;
break;
case "country":
data.pays = obj.short_name;
break;
}
}
});
data.nom = place.name;
data.coord = {lng: place.geometry.location.lng(),
lat: place.geometry.location.lat()};
data.orig_coord = data.coord;
data.fromSuggestion = true;
lieux_db.suggestion = data;
map.panTo(data.coord);
lieuSurCarte(data);
// Affichage des suggestions
$.getJSON("/api/v1/lieu/", {"format":"json",
"lat":location.lat,
"lng":location.lng}, showPropositions);
}
// Callback suggestions
function showPropositions(data) {
// TODO gérer les appels concurrents
$.each(data.objects, function(i, item) {
lieuSurCarte(item);
});
}
function lieuSurCarte(data) {
// data : des données sur un lieu, sérialisé comme par tastypie
if(data.marqueur !== undefined)
data.marqueur.remove();
var icone = blueIcon;
var fromSuggestion = false;
// Si c'est un résultat d'autocomplete
if(data.fromSuggestion === true) {
icone = redIcon;
fromSuggestion = true;
}
var marqueur = L.marker(data.coord,
{icon: icone, draggable: fromSuggestion});
data.marqueur = marqueur;
var desc = $("<div>").append($("<h3>").text(data.nom))
.append($("<p>").text(data.ville+", "+data.pays));
var activeBtn = $("<a>", {href:"javascript:void(0);"})
.prop("_lieustage_data", data)
.on("click", choixLieuStage);
if(!fromSuggestion) {
activeBtn.text("Choisir ce lieu");
} else {
var resetBtn = $("<a>", {href:"javascript:void(0);"})
.text("Réinitialiser la position")
.prop("_lieustage_data", data)
.on("click", resetOrigLieu);
desc.append($("<p>").append(resetBtn))
activeBtn.text("Créer un nouveau lieu ici");
}
desc.append($("<p>").append(activeBtn));
marqueur.bindPopup(desc[0]).addTo(map);
}
function resetOrigLieu() {
var data = this._lieustage_data;
data.marqueur.setLatLng(data.orig_coord);
map.panTo(data.orig_coord);
}
function choixLieuStage() {
var choix = this._lieustage_data;
if(!choix.fromSuggestion)
callback(choix);
else
showForm(choix);
}
function showForm(choix) {
$el.addClass("ajout");
content_el.append(form_el);
form_el.find("#id_nom").val(choix.nom);
form_el.find("#id_ville").val(choix.ville);
form_el.find("#id_pays").val(choix.pays);
form_el.find("#id_coord_0").val(choix.coord.lat);
form_el.find("#id_coord_1").val(choix.coord.lng);
}
function nouveauLieu() {
var coord = lieux_db.suggestion.marqueur.getLatLng();
form_el.find("#id_coord_0").val(coord.lat);
form_el.find("#id_coord_1").val(coord.lng);
$.post(form_el.attr("action")+"?format=json",
form_el.serialize(),
onLieuCreated);
form_el.detach();
content_el.append($("<p>").text("Envoi en cours..."));
return false;
}
function onLieuCreated(data) {
console.log(data);
if(data.success = false)
content_el.append(form_el);
else {
lieux_db.suggestion.id = data.id;
callback(lieux_db.suggestion);
}
}
}

View file

@ -1,19 +1,18 @@
{% extends "avisstage/base.html" %} {% extends "avisstage/base.html" %}
{% load staticfiles %} {% load staticfiles avisstage_tags %}
{% block extra_head %} {% block extra_head %}
<link href="{% static "jquery-autosuggest/css/autoSuggest-upshot.css" %}" <script type="text/javascript" src="//maps.googleapis.com/maps/api/js?libraries=places&key=AIzaSyDd4innPShfHcW8KDJB833vZHZSsqt-ACw"></script>
type="text/css" media="all" rel="stylesheet" /> <script type="text/javascript" src="{% static "js/leaflet.js" %}"></script>
<script type="text/javascript" <script type="text/javascript" src="{% static "js/leaflet-gplaces-autocomplete.js" %}"></script>
src="{% static "jquery-autosuggest/js/jquery.autoSuggest.minified.js" %}"> </script> <script type="text/javascript" src="//maps.stamen.com/js/tile.stamen.js?v1.3.0"></script>
<script type="text/javascript"> <script type="text/javascript" src="{% static "jquery-autosuggest/js/jquery.autoSuggest.minified.js" %}"> </script>
$( function() { <link rel="stylesheet" type="text/css" href="{% static "css/leaflet.css" %}" />
$( ".datepicker" ).datepicker({ dateFormat: 'dd/mm/yy' });
} );
</script>
<script type="text/javascript" src="{% static "js/tinymce/tinymce.min.js" %}"></script> <script type="text/javascript" src="{% static "js/tinymce/tinymce.min.js" %}"></script>
<script type="text/javascript" src="{% static "js/select_lieu.js" %}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
$(".datepicker").datepicker({ dateFormat: 'dd/mm/yy' });
// Process rich text fields // Process rich text fields
var txtr = $("textarea.tinymce"); var txtr = $("textarea.tinymce");
$.each(txtr, function(i, item) { $.each(txtr, function(i, item) {
@ -31,7 +30,7 @@
language: "fr_FR", language: "fr_FR",
}); });
// process select multiple fields // Process select multiple fields
var slts = $("select[multiple]"); var slts = $("select[multiple]");
var NULL_VAL = " "; var NULL_VAL = " ";
$.each(slts, function(i, item) { $.each(slts, function(i, item) {
@ -66,6 +65,21 @@
$item.remove(); $item.remove();
}); });
// Widget du choix du lieu
var lieu_select = new SelectLieuWidget("{{ STATIC_URL|escapejs }}",
$("#lieu_widget"), lieuChoisi);
$("#stage-addlieu").prop("_lieustage_data", "new")
.on("click", lieu_select.showWidget);
var lieu_focus;
function clickLieu() {
lieu_focus = this;
lieu_select.showWidget();
}
function lieuChoisi(lieu) {
// TODO
lieu_select.closeWidget();
}
// À l'envoi du formulaire // À l'envoi du formulaire
$("#stageform").submit(function() { $("#stageform").submit(function() {
$.each(txtr, function(i, item) { $.each(txtr, function(i, item) {
@ -109,6 +123,7 @@
{% for fform in avis_lieu_formset %} {% for fform in avis_lieu_formset %}
{{ fform.lieu }} {{ fform.lieu }}
{% endfor %} {% endfor %}
<a href="javascript:void(0);" id="stage-addlieu">Ajouter un lieu</a>
</div> </div>
</div> </div>
@ -130,7 +145,9 @@
</div> </div>
{% endfor %} {% endfor %}
{{ avis_lieu_formset.management_form }} {{ avis_lieu_formset.management_form }}
<div id="avis_lieu_container">
{% for fform in avis_lieu_formset %} {% for fform in avis_lieu_formset %}
<div class="avis_lieu">
<h2>Commentaire sur le lieu</h2> <h2>Commentaire sur le lieu</h2>
{{ fform.non_field_errors }} {{ fform.non_field_errors }}
{% for field in fform.hidden_fields %} {% for field in fform.hidden_fields %}
@ -152,7 +169,9 @@
</div> </div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div>
{% endfor %} {% endfor %}
</div>
<div id="avis_lieu_vide"> <div id="avis_lieu_vide">
{% with avis_lieu_formset.empty_form as fform %} {% with avis_lieu_formset.empty_form as fform %}
<h2>Commentaire sur le lieu</h2> <h2>Commentaire sur le lieu</h2>
@ -180,4 +199,7 @@
</div> </div>
<input type="submit" value="Enregistrer" /> <input type="submit" value="Enregistrer" />
</form> </form>
{% lieu_widget %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,33 @@
{% load staticfiles %}
<div id="lieu_widget" class="window">
<div class="window-bg"></div>
<div class="window-content">
<a class="window-closer"></a>
<h2>Choisir un lieu</h2>
<div class="lieu-ui">
</div>
<div class="lieu-form">{% load staticfiles %}
<form action="{% url 'avisstage:lieu_ajout' %}" method="post" id="lieu_ajout">
<h1>Ajouter un lieu</h1>
{% csrf_token %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
{% for field in form.visible_fields %}
{{ field.errors }}
<div class="field">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
<div class="input">
{{ field }}
{% if field.help_text %}
<p class="help_text">{{ field.help_text }}</p>
{% endif %}
</div>
</div>
{% endfor %}
<input type="submit" />
</form>
</div>
</div>
</div>

View file

View file

@ -0,0 +1,10 @@
from django import template
from avisstage.forms import LieuForm
register = template.Library()
@register.inclusion_tag('avisstage/templatetags/widget_lieu.html')
def lieu_widget():
form = LieuForm()
return {"form": form}

View file

@ -8,6 +8,7 @@ from django import forms
from django.urls import reverse from django.urls import reverse
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from braces.views import LoginRequiredMixin from braces.views import LoginRequiredMixin
from django.http import JsonResponse
from avisstage.models import Normalien, Stage, Lieu, AvisLieu, AvisStage from avisstage.models import Normalien, Stage, Lieu, AvisLieu, AvisStage
from avisstage.forms import StageForm, LieuForm, AvisStageForm, AvisLieuForm from avisstage.forms import StageForm, LieuForm, AvisStageForm, AvisLieuForm
@ -94,6 +95,22 @@ class LieuAjout(CreateView, LoginRequiredMixin):
form_class = LieuForm form_class = LieuForm
template_name = 'avisstage/formulaires/lieu.html' template_name = 'avisstage/formulaires/lieu.html'
def form_valid(self, form):
if self.request.GET.get("format", "") == "json":
self.object = form.save()
return JsonResponse({"success": True,
"id": self.object.id})
else:
super(LieuAjout, self).form_valid(form)
def form_invalid(self, form):
if self.request.GET.get("format", "") == "json":
return JsonResponse({"success": False,
"errors": form.errors})
else:
super(LieuAjout, self).form_valid(form)
def recherche(request): def recherche(request):
return render(request, 'avisstage/recherche.html') return render(request, 'avisstage/recherche.html')

View file

@ -7,8 +7,8 @@ class LatLonWidget(forms.MultiWidget):
""" """
def __init__(self, attrs=None, date_format=None, time_format=None): def __init__(self, attrs=None, date_format=None, time_format=None):
widgets = (forms.TextInput(attrs=attrs), widgets = (forms.HiddenInput(attrs=attrs),
forms.TextInput(attrs=attrs)) forms.HiddenInput(attrs=attrs))
super(LatLonWidget, self).__init__(widgets, attrs) super(LatLonWidget, self).__init__(widgets, attrs)
def decompress(self, value): def decompress(self, value):