Autocompletion in event selection

This commit is contained in:
Raphael Michel
2017-06-05 19:28:52 +02:00
parent 6f7281b0f5
commit 0cecc168b6
7 changed files with 172 additions and 23 deletions

View File

@@ -59,8 +59,6 @@ class PermissionMiddleware(MiddlewareMixin):
return redirect_to_login(
path, resolved_login_url, REDIRECT_FIELD_NAME)
events = request.user.get_events_with_any_permission()
request.user.events_cache = events.order_by("organizer", "date_from").prefetch_related("organizer")
if 'event' in url.kwargs and 'organizer' in url.kwargs:
request.event = Event.objects.filter(
slug=url.kwargs['event'],

View File

@@ -34,6 +34,7 @@
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/quota.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/question.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/mail.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/typeahead.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asyncdownload.js" %}"></script>
<script type="text/javascript" src="{% static "colorpicker/bootstrap-colorpicker.js" %}"></script>
@@ -69,15 +70,15 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-calendar"></i>
<span class="event-name">{{ request.event }}</span>
<span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="{% url "control:events" %}">{% trans "Events overview" %}</a></li>
{% regroup request.user.events_cache by organizer as event_list %}
{% for g in event_list %}
<li class="dropdown-header">{{ g.grouper }}</li>
{% for e in g.list %}
<li><a href="{% url "control:event.index" organizer=g.grouper.slug event=e.slug %}">{{ e.name }}</a></li>
{% endfor %}
{% endfor %}
<ul class="dropdown-menu event-dropdown" role="menu" data-event-typeahead
data-source="{% url "control:events.typeahead" %}">
<li>
<div class="form-box">
<input type="text" class="form-control"
placeholder="{% trans "Search for events" %}"
data-typeahead-query>
</div>
</li>
</ul>
</li>
{% if request.event %}
@@ -147,16 +148,15 @@
</li>
</ul>
<div class="navbar-default sidebar" role="navigation">
<div class="sidebar-nav navbar-events-collapse navbar-collapse hidden-sm hidden-md hidden-lg">
<ul class="nav">
<li><a href="{% url "control:events" %}">{% trans "Event overview" %}</a></li>
{% for e in request.user.events_cache %}
<li>
<a href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">
{{ e.name }}
</a>
</li>
{% endfor %}
<div class="sidebar-nav navbar-events-collapse navbar-collapse hidden-sm hidden-md hidden-lg mobile-event-dropdown">
<ul class="nav" data-event-typeahead data-source="{% url "control:events.typeahead" %}">
<li>
<div class="form-box">
<input type="text" class="form-control"
placeholder="{% trans "Search for events" %}"
data-typeahead-query>
</div>
</li>
</ul>
</div>
<div class="sidebar-nav navbar-nav-collapse navbar-collapse">

View File

@@ -2,7 +2,7 @@ from django.conf.urls import include, url
from pretix.control.views import (
auth, checkin, dashboards, event, global_settings, item, main, orders,
organizer, user, vouchers, waitinglist,
organizer, typeahead, user, vouchers, waitinglist,
)
urlpatterns = [
@@ -45,6 +45,7 @@ urlpatterns = [
name='organizer.team.delete'),
url(r'^events/$', main.EventList.as_view(), name='events'),
url(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
url(r'^events/typeahead/$', typeahead.event_list, name='events.typeahead'),
url(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
url(r'^$', dashboards.event_index, name='event.index'),
url(r'^live/$', event.EventLive.as_view(), name='event.live'),

View File

@@ -252,7 +252,7 @@ def user_event_widgets(**kwargs):
user = kwargs.pop('user')
widgets = []
events = user.get_events_with_any_permission().order_by('-date_from', 'name').select_related('organizer')
events = user.get_events_with_any_permission().order_by('-date_from', 'name').select_related('organizer')[:100]
for event in events:
widgets.append({
'content': '<div class="event">{event}<span class="from">{df}</span><span class="to">{dt}</span></div>'.format(

View File

@@ -0,0 +1,32 @@
import json
from django.db.models import Q
from django.http import JsonResponse
from django.urls import reverse
def i18ncomp(query):
return json.dumps(str(query))[1:-1]
def event_list(request):
query = request.GET.get('query', '')
qs = request.user.get_events_with_any_permission().filter(
Q(name__icontains=i18ncomp(query)) | Q(slug__icontains=query) |
Q(organizer__name__icontains=i18ncomp(query)) | Q(organizer__slug__icontains=query)
).order_by('-date_from')
doc = {
'results': [
{
'slug': e.slug,
'organizer': str(e.organizer.name),
'name': str(e.name),
'date_range': e.get_date_range_display(),
'url': reverse('control:event.index', kwargs={
'event': e.slug,
'organizer': e.organizer.slug
})
} for e in qs.select_related('organizer')[:10]
]
}
return JsonResponse(doc)

View File

@@ -0,0 +1,93 @@
/*global $,u2f */
$(function () {
$('ul.navbar-nav .dropdown, .navbar-events-collapse').on('shown.bs.collapse shown.bs.dropdown', function () {
$(this).parent().find("input").val("").change().focus();
});
$('.dropdown-menu .form-box input').click(function (e) {
e.stopPropagation();
});
$("[data-event-typeahead]").each(function () {
var $container = $(this);
var $query = $(this).find('[data-typeahead-query]');
$query.closest("li").nextAll().remove();
$query.on("change", function () {
$.getJSON(
$container.attr("data-source") + "?query=" + encodeURIComponent($query.val()),
function (data) {
$query.closest("li").nextAll().remove();
$.each(data.results, function (i, res) {
$container.append(
$("<li>").append(
$("<a>").attr("href", res.url).append(
$("<div>").append(
$("<span>").addClass("event-name").append(res.name)
).append(
$("<span>").addClass("event-organizer").append(
$("<span>").addClass("fa fa-users fa-fw")
).append(" ").append(res.organizer)
).append(
$("<span>").addClass("event-daterange").append(
$("<span>").addClass("fa fa-calendar fa-fw")
).append(" ").append(res.date_range)
)
)
)
);
});
}
);
});
$query.on("keydown", function (event) {
var $selected = $container.find(".active");
if (event.which === 13) { // enter
var $link = $selected.find("a");
if ($link.length) {
location.href = $link.attr("href");
}
event.preventDefault();
event.stopPropagation();
}
});
$query.on("keyup", function (event) {
var $first = $query.closest("li").next();
var $last = $query.closest("li").nextAll().last();
var $selected = $container.find(".active");
if (event.which === 13) { // enter
event.preventDefault();
event.stopPropagation();
return true;
} else if (event.which === 40) { // down
if ($selected.length === 0) {
$selected = $query.closest("li");
}
var $next = $selected.next();
if ($next.length === 0) {
$next = $first;
}
$selected.removeClass("active");
$next.addClass("active");
event.preventDefault();
event.stopPropagation();
return true;
} else if (event.which === 38) { // up
if ($selected.length === 0) {
$selected = $container.first();
}
var $prev = $selected.prev();
if ($prev.length === 0 || $prev.find("input").length > 0) {
$prev = $last;
}
$selected.removeClass("active");
$prev.addClass("active");
event.preventDefault();
event.stopPropagation();
return true;
} else {
$(this).change();
}
});
});
});

View File

@@ -297,3 +297,28 @@ body.loading #wrapper {
text-overflow: ellipsis;
vertical-align: bottom;
}
.dropdown-menu .form-box {
padding: 3px 10px;
}
.event-dropdown {
width: 300px;
}
.event-dropdown, .mobile-event-dropdown {
.event-name {
display: block;
}
.event-daterange, .event-organizer {
display: block;
font-size: $font-size-small;
color: $text-muted;
}
.active .event-daterange, .active .event-organizer, .active a {
color: white;
}
.active {
background: $brand-primary;
color: white;
}
}