... gegen Computerkopfschmerzen

Grails: Tutorial 2 – Erweiterung um Suchfunktion

In diesem Tutorial wird gezeigt, wie man ein Grails-Projekt um eine Suchfunktion erweitert.

Voraussetzungen:
In diesem Beitrag wird die erweitert. Im Grails-Standard hat man nicht die Möglichkeit, innerhalb einer Tabelle nach Text zu suchen. Eine Suchfunktion soll jetzt ergänzt werden.
Als Ausgangspunkt dieser Anleitung dienen die in Teil 1 erstellten Dateien. Die in dieser Anleitung erstellten Dateien kann man hier herunterladen.
Das Basisverzeichnis ist ~/Grails/Teddy-DB.

Views anpassen und anlegen

Zuerst wird oberhalb der Teddy-Liste eine Schaltfläche "Suchen" zum Aufruf der Ansicht mit der Suchfunktion eingefügt:
% cd ~/Grails/Teddy-DB

Teddy-DB % vi grails-app/views/teddies/index.gsp

ergänzen:

<li><a class="list" href="${createLink(uri: '/hersteller')}"><g:message code="Hersteller"/></a></li>
<li><g:link class="list" action="search"><g:message code="Suchen"/></g:link></li>
</ul>

Hier ist sie:
Grails Suche Bild 1

Dann wird die neue Seite search.gsp angelegt. Es werden die Felder „Name“, „Beschreibung“, „Schätze“ und „Hersteller“ aufgenommen.
Teddy-DB % vi grails-app/views/teddies/search.gsp

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<g:set var="entityName" value="${message(code: 'teddies.label', default: 'Teddies')}" />
<title><g:message code="default.list.label" args="[entityName]" /></title>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.css">
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/scroller/1.4.4/css/scroller.dataTables.min.css">
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/scroller/1.4.4/js/dataTables.scroller.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.flash.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/pdfmake.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/vfs_fonts.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.html5.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.print.min.js "></script>
</head>
<body>
<a href="#list-teddies" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content…"/></a>
<div class="nav" role="navigation">
<ul>
<li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
</ul>
</div>

<div id="teddies-search" class="content" role="main">
<h1>Teddies Suche</h1>
<table id="teddies_dt" class="display compact" style="width:99%;">
<thead><tr><th>Name</th><th>Beschreibung</th><th>Schätze</th><th>Hersteller</th></tr></thead>
<tfoot><tr><th>Name</th><th>Beschreibung</th><th>Schätze</th><th>Hersteller</th></tr></tfoot>
</table>
</div>
<g:javascript>
$('#teddies_dt tfoot th').each( function() {
var title = $(this).text();
$(this).html('<input type="text" size="15" placeholder="' + title + '?" />');
});
var table = $('#teddies_dt').DataTable( {
"scrollY": 500,
"deferRender": true,
"scroller": true,
"dom": "Brtip",
"buttons": [ 'copy', 'csv', 'excel', 'pdf', 'print' ],
"processing": true,
"serverSide": true,
"ajax": {
"url": "${createLink(controller: 'teddies', action: 'searchLister')}",
"type": "POST",
},
"columns": [
{ "data": "name" },
{ "data": "beschreibung" },
{ "data": "schaetze" },
{ "data": "hersteller" }
]
});
table.columns().every(function() {
var that = this;
$('input', this.footer()).on('keyup change', function(e) {
if (that.search() != this.value && 8 < e.keyCode && e.keyCode < 32)
that.search(this.value).draw();
});
});
</g:javascript>
</body>
</html>

DataTables bietet mehrere Download-Möglichkeiten (csv, pdf, Excel, Kopieren, Drucken) an. Zu weiteren Beispielen siehe DataTables-Optionen.

Controller erweitern

Jetzt wird der Teddy-Controller um die neue Funktionalität erweitert:
Teddy-DB % vi grails-app/controllers/teddies/TeddiesController.groovy

einfügen:

import static org.springframework.http.HttpStatus.*
import grails.converters.JSON

class TeddiesController {


einfügen:

static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]

def search(Integer max) {
respond 'unused response'
}

def searchLister() {

def jqdtParams = [:]
params.each { key, value ->
def keyFields = key.replace(']','').split(/\[/)
def table = jqdtParams
for (int f = 0; f < keyFields.size() - 1; f++) {
def keyField = keyFields[f]
if (!table.containsKey(keyField))
table[keyField] = [:]
table = table[keyField]
}
table[keyFields[-1]] = value
}
def columnMap = jqdtParams.columns.collectEntries { k, v ->
def whereTerm = null
switch (v.data) {
default:
if (v.search.value ==~ /[A-Za-z0-9 ]+/)
whereTerm = "%${v.search.value}%" as String
break
}
[(v.data): [where: whereTerm]]
}
def allColumnList = columnMap.keySet() as List
def orderList = jqdtParams.order.collect { k, v -> [allColumnList[v.column as Integer], v.dir] }
def filterer = {
createAlias 'hersteller', 'h'

if (columnMap.name.where) ilike 'name', columnMap.name.where
if (columnMap.beschreibung.where) ilike 'beschreibung', columnMap.beschreibung.where
if (columnMap.schaetze.where) ilike 'schaetze', columnMap.schaetze.where
if (columnMap.hersteller.where) ilike 'h.name', columnMap.hersteller.where
}
def recordsTotal = Teddies.count()
def c = Teddies.createCriteria()
def recordsFiltered = c.count {
filterer.delegate = delegate
filterer()
}
def orderer = Teddies.withCriteria {
filterer.delegate = delegate
filterer()
orderList.each { oi ->
switch (oi[0]) {
case 'name': order 'name', oi[1]; break
case 'beschreibung': order 'beschreibung', oi[1]; break
case 'schaetze': order 'schaetze', oi[1]; break
case 'hersteller': order 'h.name', oi[1]; break
}
}
maxResults (jqdtParams.length as Integer)
firstResult (jqdtParams.start as Integer)
}

def teddies = orderer.collect { teddies ->
['name': "<a href='${createLink(controller: 'teddies', action: 'show', id: teddies.id)}'>${teddies.name}</a>",
'beschreibung': teddies.beschreibung,
'schaetze': teddies.schaetze,
'hersteller': "<a href='${createLink(controller: 'hersteller', action: 'show', id: teddies.hersteller?.id)}'>${teddies.hersteller?.name}</a>"]
}
def result = [draw: jqdtParams.draw, recordsTotal: recordsTotal, recordsFiltered: recordsFiltered, data: teddies]
render(result as JSON)
}


def index() {
params.max = Math.min( params.max ? params.max.toInteger() : 10, 100)

Das Ergebnis sieht so aus:
Grails-Anwendung mit Suchfunktion

Man hat jetzt die Möglichkeit zur Volltextsuche in den Tabelleneinträgen.

Als Vorlage für dieses Projekt diente diese Anleitung:

https://opensource.com/article/18/9/using-grails-jquery-and-datatables


macOS Catalina
Grails Version: 4.0.8
JVM Version: 11.0.10
MySQL Community Server 8.0.23