Use bootstrap-table for fancy ajax tables.

This commit is contained in:
Phillip Kühne 2019-08-18 23:55:59 +02:00
parent 41b2aa2ed1
commit 3ed8146b6f
11 changed files with 161 additions and 117 deletions

34
.vscode/launch.json vendored
View File

@ -6,6 +6,7 @@
"configurations": [
{"name":"Python: Flask","type":"python","request":"launch","module":"flask","env":{"FLASK_APP":"app/main.py","FLASK_ENV":"development","FLASK_DEBUG":"1"},"args":["run","--no-debugger","--no-reload"],"jinja":true},
{"name":"Python: Flask (with reload)","type":"python","request":"launch","module":"flask","env":{"FLASK_APP":"app/main.py","FLASK_ENV":"development","FLASK_DEBUG":"1"},"args":["run","--no-debugger"],"jinja":true},
{
@ -25,6 +26,24 @@
],
"jinja": true
},
{
"name": "Python: Flask (externally reachable)",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app/main.py",
"FLASK_ENV": "development",
"FLASK_DEBUG": "1"
},
"args": [
"run",
"--no-debugger",
"--no-reload",
"--host='0.0.0.0'"
],
"jinja": true
},
{
"name": "Python: Current File (Integrated Terminal)",
"type": "python",
@ -65,21 +84,6 @@
],
"django": true
},
{
"name": "Python: Flask",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py"
},
"args": [
"run",
"--no-debugger",
"--no-reload"
],
"jinja": true
},
{
"name": "Python: Current File (External Terminal)",
"type": "python",

8
app/data_adapters.py Normal file
View File

@ -0,0 +1,8 @@
def dict_from_row(row):
return dict(zip(row.keys(), row))
def dict_from_rows(rows):
outlist=[]
for row in rows:
outlist.append(dict_from_row(row))
return outlist

View File

@ -74,6 +74,7 @@ def create_done_song_view():
def get_list():
conn = open_db()
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute("SELECT * FROM Liste")
return cur.fetchall()
@ -141,6 +142,20 @@ def delete_entry(id):
return True
def delete_entries(ids):
idlist = []
for x in ids:
idlist.append( (x,) )
try:
conn = open_db()
cur = conn.cursor()
cur.executemany("DELETE FROM entries WHERE id=?", idlist)
conn.commit()
conn.close()
return cur.rowcount
except sqlite3.Error as error:
return -1
def delete_all_entries():
conn = open_db()
cur = conn.cursor()

View File

@ -1,6 +1,7 @@
from flask import Flask, render_template, Response, abort, request, redirect
import helpers
import database
import app.helpers as helpers
import app.database as database
import app.data_adapters as data_adapters
import os, errno
import json
from flask_basicauth import BasicAuth
@ -29,6 +30,11 @@ def enqueue():
def songlist():
return render_template('songlist.html', list=database.get_song_list(), auth=basic_auth.authenticate())
@app.route("/api/queue")
def queue_json():
list = data_adapters.dict_from_rows(database.get_list())
return Response(json.dumps(list, ensure_ascii=False).encode('utf-8'), mimetype='text/json')
@app.route("/plays")
@basic_auth.required
def played_list():
@ -69,6 +75,20 @@ def delete_entry(entry_id):
return Response('{"status": "FAIL"}', mimetype='text/json')
@app.route("/api/entries/delete", methods=['POST'])
@basic_auth.required
def delete_entries():
if not request.json:
print(request.data)
abort(400)
return
updates = database.delete_entries(request.json)
if updates >= 0:
return Response('{"status": "OK", "updates": '+str(updates)+'}', mimetype='text/json')
else:
return Response('{"status": "FAIL"}', mimetype='text/json', status=400)
@app.route("/api/entries/mark_sung/<entry_id>")
@basic_auth.required
def mark_sung(entry_id):

View File

@ -26,15 +26,11 @@ main {
.topbutton {
width: 100%;
margin-right: auto;
margin-bottom: 1rem;
}
@media (min-width: 768px) {
.topbutton {
width: auto;
margin-right: 1rem;
margin-bottom: auto;
}
}

View File

@ -11,6 +11,9 @@
<title>{% block title %}{% endblock %} - KaraoQueue</title>
<!-- Bootstrap-Tables -->
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.css">
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
@ -18,9 +21,10 @@
<!-- Custom styles for this template -->
<link href="static/css/style.css" rel="stylesheet">
<!-- Fontawesome Icons-->
<!-- Fontawesome Icons -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
</head>
<body>
@ -88,6 +92,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"
integrity="sha256-4F7e4JsAJyLUdpP7Q8Sah866jCOhv72zU5E8lIRER4w=" crossorigin="anonymous">
</script>
<script src="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.js"></script>
{% block extrajs %}{% endblock %}
<script>
$(document).ready(function () {

View File

@ -3,28 +3,19 @@
{% extends 'base.html' %}
{% block title %}Warteliste{% endblock %}
{% block content %}
<a role="button" class="btn btn-primary btn-lg btn-block" href="/list">Eintragen</a>
<table class="table">
<a role="button" class="btn btn-primary btn-lg btn-block mb-2" href="/list">Eintragen</a>
<table class="table"
data-toggle="table"
data-url="/api/queue"
data-pagination="true"
data-classes="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Song</th>
<th scope="col">Künstler</th>
<th scope="col" data-field="Name">Name</th>
<th scope="col" data-field="Title">Song</th>
<th scope="col" data-field="Artist">Künstler</th>
</tr>
</thead>
{% for entry in list: %}
<tr>
<td>
{{ entry[0] }}
</td>
<td>
{{ entry[1] }}
</td>
<td>
{{ entry[2] }}
</td>
</tr>
{% endfor %}
</table>
<a name="end"></a>
{% endblock %}

View File

@ -4,53 +4,37 @@
{% block title %}Warteliste-Admin{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="card" style="width: 100%">
<div class="card-body">
<button type="button" class="topbutton btn btn-danger"
onclick="confirmDeleteAllEntries()"><i class="fas fa-trash mr-2"></i>Alle Einträge löschen</button>
<button type="button" class="topbutton btn btn-danger"
onclick="confirmUpdateSongDatabase()"><i class="fas fa-file-import mr-2"></i>Song-Datenbank
aktualisieren</button>
</div>
</div>
<div id="toolbar">
<button type="button" class="topbutton btn btn-danger" onclick="confirmDeleteSelectedEntries()"><i
class="fas fa-trash mr-2"></i>Gewählte Einträge löschen</button>
<button type="button" class="topbutton btn btn-danger" onclick="confirmUpdateSongDatabase()"><i
class="fas fa-file-import mr-2"></i>Song-Datenbank
aktualisieren</button>
</div>
<div class="row">
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Song</th>
<th scope="col">Künstler</th>
<th scope="col">Aktionen</th>
</tr>
</thead>
{% for entry in list: %}
<table class="table"
id="entrytable"
data-toggle="table"
data-search="true"
data-show-columns="true"
data-show-toggle="true"
data-multiple-select-row="true"
data-click-to-select="true"
data-toolbar="#toolbar"
data-pagination="true"
data-show-extended-pagination="true"
data-classes="table table-hover"
data-url="/api/queue">
<thead>
<tr>
<td>
{{ entry[0] }}
</td>
<td>
{{ entry[1] }}
</td>
<td>
{{ entry[2] }}
</td>
<td>
<button type='button' class='btn btn-success'
data-toggle="tooltip" data-placement="top" title="Als gesungen markieren"
onclick='markEntryAsSung({{ entry[3] }})'><i
class="fas fa-check"></i></button>
<button type='button' class='btn btn-danger'
data-toggle="tooltip" data-placement="top" title="Eintrag löschen"
onclick='confirmDeleteEntry("{{ entry[0] }}",{{ entry[3] }})'><i
class="fas fa-trash"></i></button>
</td>
<th data-field="state" data-checkbox="true"></th>
<th scope="col" data-field="Name">Name</th>
<th scope="col" data-field="Title">Song</th>
<th scope="col" data-field="Artist">Künstler</th>
<th scope="col" data-formatter="TableActions">Aktionen</th>
</tr>
{% endfor %}
</table>
<a name="end"></a>
</div>
</thead>
</table>
<a name="end"></a>
</div>
{% endblock %}
{% block extrajs %}
@ -65,9 +49,9 @@
}
})
}
function confirmDeleteAllEntries() {
function confirmDeleteSelectedEntries() {
bootbox.confirm({
message: "Wirklich alle Eintragungen löschen?",
message: "Wirklich gewählte Eintragungen löschen?",
buttons: {
confirm: {
label: 'Ja',
@ -80,7 +64,7 @@
},
callback: function(result){
if (result) {
deleteAllEntries()
DeleteSelectedEntries(getIdSelections())
}
}
})
@ -118,7 +102,7 @@
dataType: 'json',
async: false
});
location.reload();
$("#entrytable").bootstrapTable('refresh')
}
function markEntryAsSung(entry_id) {
$.ajax({
@ -128,17 +112,24 @@
dataType: 'json',
async: false
});
location.reload();
$("#entrytable").bootstrapTable('refresh')
}
function deleteAllEntries() {
function DeleteSelectedEntries(ids) {
$.ajax({
type: 'GET',
url: '/api/entries/delete_all',
type: 'POST',
url: '/api/entries/delete',
data: JSON.stringify(ids), // or JSON.stringify ({name: 'jonas'}),
error: function() {
bootbox.alert({
message: "Fehler beim Löschen der Eintragungen.",
})
},
success: function() {
$("#entrytable").bootstrapTable('refresh')
},
contentType: "application/json",
dataType: 'json',
async: false
dataType: 'json'
});
location.reload();
}
function updateSongDatabase(wait_dialog) {
$.ajax({
@ -150,10 +141,18 @@
wait_dialog.modal('hide')
bootbox.alert({
message: data["status"],
callback: function() {location.reload()}
callback: function() {$("#entrytable").bootstrapTable('refresh')}
})
}
});
}
function TableActions (value, row, index) {
return "<button type='button' class='btn btn-success' data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick='markEntryAsSung("+row.ID+")'><i class=\"fas fa-check\"></i></button>&nbsp;<button type='button' class='btn btn-danger' data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick='confirmDeleteEntry(\""+row.Name+"\","+row.ID+")'><i class=\"fas fa-trash\"></i></button>";
}
function getIdSelections() {
return $.map($("#entrytable").bootstrapTable('getSelections'), function (row) {
return row.ID
})
}
</script>
{% endblock %}

View File

@ -1,15 +1,23 @@
{% extends 'base.html' %}
{% block title %}Abspielliste{% endblock %}
{% block content %}
<div class="card admincontrols" style="width: 100%">
<div class="card-body">
<button type="button" class="topbutton btn btn-danger" onclick="confirmDeleteAllEntries()"><i
class="fas fa-trash mr-2"></i>Abspielliste löschen</button>
<button type="button" class="topbutton btn btn-primary" onclick="exportPDF()"><i class="fas fa-file-pdf mr-2"></i>Als PDF herunterladen</button>
<button type="button" class="topbutton btn btn-secondary" onclick="printPDF()"><i class="fas fa-print mr-2"></i>Drucken</button>
</div>
<div id="toolbar">
<button type="button" class="topbutton btn btn-danger" onclick="confirmDeleteAllEntries()"><i
class="fas fa-trash mr-2"></i>Abspielliste löschen</button>
<button type="button" class="topbutton btn btn-primary" onclick="exportPDF()"><i
class="fas fa-file-pdf mr-2"></i>Als PDF herunterladen</button>
<button type="button" class="topbutton btn btn-secondary" onclick="printPDF()"><i
class="fas fa-print mr-2"></i>Drucken</button>
</div>
<table class="table" id="table">
<table class="table"
id="table"
data-toggle="table"
data-search="true"
data-show-columns="true"
data-toolbar="#toolbar"
data-pagination="true"
data-classes="table table-bordered table-striped"
data-show-extended-pagination="true">
<thead>
<tr>
<th scope="col">Song</th>
@ -67,7 +75,8 @@
function exportPDF() {
var doc = new jsPDF();
doc.autoTable({
html: '#table',
head: [["Song","Wiedergaben"]],
body: createTableArray(),
theme: 'grid'
});
doc.save('Abspielliste.pdf');
@ -76,11 +85,18 @@
function printPDF() {
var doc = new jsPDF();
doc.autoTable({
html: '#table',
head: [["Song","Wiedergaben"]],
body: createTableArray(),
theme: 'grid'
});
doc.autoPrint();
doc.output('dataurlnewwindow');
}
function createTableArray() {
var data = $("#table").bootstrapTable('getData')
out = data.map(x => [x["0"],x["1"]])
return out;
}
</script>
{% endblock %}

View File

@ -4,16 +4,6 @@
<input class="form-control" id="filter" type="text" placeholder="Suchen...">
<table class="table">
<tbody id="songtable">
<!--{% for entry in (): %}
<tr>
<td>
{{ entry[0] }}
</td>
<td>
<button type="button" class="btn btn-primary"><i class="material-icons">queue_music</i></button>
</td>
</tr>
{% endfor %}-->
</tbody>
</table>
<div class="modal fade" id="enqueueModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"