grails 怎么使用
我是Grails的忠实粉丝。 当然,我主要是一个数据人员,喜欢使用命令行工具来探索和分析数据。 但是,即使是数据人,有时也需要查看数据,有时使用数据意味着拥有出色的数据浏览器。 使用Grails, jQuery和DataTables jQuery插件 ,我们可以制作出非常漂亮的表格数据浏览器。
DataTables网站提供了许多不错的“食谱样式”文档,这些文档显示了如何将一些优秀的示例应用程序组合在一起,并且其中包括必要JavaScript,HTML和偶尔的PHP,以完成一些漂亮的工作。 但是对于那些更愿意将Grails用作后端的人来说,需要一些解释。 此外,使用的示例应用程序数据是虚构公司的单个员工平表,因此处理表关系的复杂性是读者的练习。
Groovy定义的Java Hibernate标准。 我已将应用程序的代码放在GitHub上 ,因此本文旨在说明代码的细微差别。对于先决条件,您将需要设置Java,Groovy和Grails环境。 对于Grails,我倾向于使用终端窗口和Vim ,所以这里使用的就是这种方式。 为了获得现代Java,我建议下载并安装由Linux发行版(应该是Java 8、9、10或11)提供的Open Java Development Kit (OpenJDK);在撰写本文时,我正在使用Java 8。 )。 从我的角度来看,获取最新Groovy和Grails的最佳方法是使用SDKMAN! 。
从未尝试过Grails的读者可能需要做一些背景阅读。 首先,我建议创建您的First Grails应用程序 。
获取员工浏览器应用程序
如上所述,我已将此示例员工浏览器应用程序的源代码放在GitHub上 。 为了进一步说明,应用程序嵌入是在Linux终端窗口中使用以下命令构建的:
cd Projects
grails create
- app com.
nuevaconsulting .
embrow
域类和单元测试创建如下:
cd embrow
grails create
- domain
-
class com.
nuevaconsulting .
embrow .
Position
grails create
- domain
-
class com.
nuevaconsulting .
embrow .
Office
grails create
- domain
-
class com.
nuevaconsulting .
embrow .
Employee
用这种方法构建的域类没有属性,因此必须按如下方式进行编辑:
Position域类:
package com.
nuevaconsulting .
embrow
class
Position
{
String
name
int starting
static constraints
=
{
name nullable
:
false
, blank
:
false
starting nullable
:
false
}
}
Office域类:
package com.
nuevaconsulting .
embrow
class Office
{
String
name
String address
String city
String country
static constraints
=
{
name nullable
:
false
, blank
:
false
address nullable
:
false
, blank
:
false
city nullable
:
false
, blank
:
false
country nullable
:
false
, blank
:
false
}
}
和Employee域类:
package com.
nuevaconsulting .
embrow
class Employee
{
String surname
String givenNames
Position
position
Office office
int extension
Date hired
int salary
static constraints
=
{
surname nullable
:
false
, blank
:
false
givenNames nullable
:
false
, blank
:
false
position nullable
:
false
office nullable
:
false
extension nullable
:
false
hired nullable
:
false
salary nullable
:
false
}
}
请注意,虽然Position和Office域类使用预定义的Groovy类型String和int,但是Employee域类定义的类型是Position和Office(以及预定义的Date)字段。 这将导致创建用于存储Employee实例的数据库表,以包含对存储Position和Office实例的表的引用或外键。
现在,您可以生成控制器,视图和其他各种测试组件:
grails generate
- all com.
nuevaconsulting .
embrow .
Position
grails generate
- all com.
nuevaconsulting .
embrow .
Office
grails generate
- all com.
nuevaconsulting .
embrow .
Employee
至此,您已经可以使用基本的创建-读取-更新-删除(CRUD)应用程序。 我已经在grails-app / init / com / nuevaconsulting / BootStrap.groovy中添加了一些基本数据来填充表格。
如果使用以下命令运行应用程序:
grails run - app
您将在浏览器的http:// localhost:8080 /上看到以下屏幕:
单击OfficeController的链接,将显示一个类似于以下内容的屏幕:
请注意,此列表由OfficeController索引方法生成,并由视图office/index.gsp
。
类似地,单击EmployeeController会显示一个屏幕,如下所示:
好的,这很丑陋-Position和Office链接有什么用?
好了,上面的generate-all
命令generate-all
的视图将创建一个索引。 使用Grails <f:table />标记的gsp文件,默认情况下显示类名称( com.nuevaconsulting.embrow.Position )和持久性实例标识符( 30 )。 可以自定义此行为以产生更好看的外观,并且具有一些自动生成的链接,自动生成的分页和自动生成的可排序列的漂亮东西。
但是,即使完全清理完毕,此员工浏览器也提供了有限的功能。 例如,如果要查找其职位包括文本“ dev”的所有员工该怎么办? 如果要合并列进行排序,以便主排序键是姓氏,辅助排序键是办公室名怎么办? 或者,如果您要将已排序的子集导出到电子表格或PDF中,以电子邮件发送给无法访问浏览器的人,该怎么办?
jQuery DataTables插件提供了这种额外的功能,并允许您创建完整的表格数据浏览器。
创建员工浏览器视图和控制器方法
为了创建基于jQuery DataTables的员工浏览器,您必须完成两个任务:
创建一个Grails视图,其中包含启用DataTables所需HTML和JavaScript
向Grails控制器添加方法以处理新视图
员工浏览器视图
在目录embrow / grails-app / views / employee中 ,首先制作索引的副本。 gsp文件,将其命名为browser.gsp :
此时,您要自定义新的浏览器。 gsp文件添加相关的jQuery DataTables代码。
通常,我希望在可行的情况下从内容提供者那里获取JavaScript和CSS。 在这种情况下,在以下行之后执行此操作:
< 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
>
接下来,删除在index.gsp中提供数据分页的代码:
< div id
=
"list-employee"
class
=
"content scaffold-list" role
=
"main"
>
< h1
>< g
: message code
=
"default.list.label" args
=
"[entityName]"
/></ h1
>
< g
:
if test
=
"${flash.message}"
>
< div
class
=
"message" role
=
"status"
>
$
{ flash.
message
}
</ div
>
</ g
: if
>
< f
: table collection
=
"${employeeList}"
/>
< div
class
=
"pagination"
>
< g
: paginate total
=
"${employeeCount ?: 0}"
/>
</ div
>
</ div
>
并插入实现jQuery DataTables的代码。
插入的第一部分是HTML,它创建浏览器的基本表格结构。 对于DataTables与数据库后端通信的应用程序,仅提供表的页眉和页脚; DataTables JavaScript负责表的内容。
< div id
=
"employee-browser"
class
=
"content" role
=
"main"
>
< h1
> Employee Browser
</ h1
>
< table id
=
"employee_dt"
class
=
"display compact" style
=
"width:99%;"
>
< thead
>
< tr
>
< th
> Surname
</ th
>
< th
> Given
name
( s
)
</ th
>
< th
> Position
</ th
>
< th
> Office
</ th
>
< th
> Extension
</ th
>
< th
> Hired
</ th
>
< th
> Salary
</ th
>
</ tr
>
</ thead
>
< tfoot
>
< tr
>
< th
> Surname
</ th
>
< th
> Given
name
( s
)
</ th
>
< th
> Position
</ th
>
< th
> Office
</ th
>
< th
> Extension
</ th
>
< th
> Hired
</ th
>
< th
> Salary
</ th
>
</ tr
>
</ tfoot
>
</ table
>
</ div
>
接下来,插入一个JavaScript块,该块具有三个主要功能:设置页脚中显示的文本框的大小以进行列过滤,建立DataTables表模型,并创建处理程序以进行列过滤。
下面的代码用于调整表格列底部的过滤器框的大小:
接下来,定义表模型。 这是提供所有表选项的地方,包括滚动而不是分页的界面性质,根据dom字符串提供的隐秘修饰,将数据导出为CSV和其他格式的能力以及与服务器的Ajax连接已建立。 请注意,该URL是通过对Grails createLink()方法的Groovy GString调用创建的,引用了EmployeeController中的browserLister操作。 还需要注意的是表的列的定义。 该信息被发送到后端,后端查询数据库并返回适当的记录。
var table
=
$
(
'#employee_dt'
) .
DataTable
(
{
"scrollY"
:
500
,
"deferRender"
:
true
,
"scroller"
:
true
,
"dom"
:
"Brtip"
,
"buttons"
:
[
'copy'
,
'csv'
,
'excel'
,
'pdf'
,
'print'
]
,
"processing"
:
true
,
"serverSide"
:
true
,
"ajax"
:
{
"url"
:
"${createLink(controller: 'employee', action: 'browserLister')}"
,
"type"
:
"POST"
,
}
,
"columns"
:
[
{
"data"
:
"surname"
}
,
{
"data"
:
"givenNames"
}
,
{
"data"
:
"position"
}
,
{
"data"
:
"office"
}
,
{
"data"
:
"extension"
}
,
{
"data"
:
"hired"
}
,
{
"data"
:
"salary"
}
]
}
)
;
最后,监视过滤器列中的更改并使用它们来应用过滤器。
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
(
)
;
}
)
;
JavaScript就是这样。 这样就完成了对视图代码的更改。
}
)
;
</ g
: javascript
>
这是此视图创建的UI的屏幕截图:
这是另一个截图,显示了工作中的过滤和多列排序(查找职位中包含字符“ dev”的员工,首先按办公室,然后按姓氏排序):
这是另一个屏幕截图,显示了单击CSV按钮时发生的情况:
最后,这是一个截图,显示了在LibreOffice中打开的CSV数据:
好的,因此视图部分看起来非常简单。 因此,控制器动作必须完成所有繁重的工作,对吗? 让我们来看看…
员工控制器browserLister操作
回想一下,我们看到了这个字符串
"${createLink(controller: 'employee', action: 'browserLister')}"
作为用于DataTables表模型的Ajax调用的URL。 createLink()是 Grails标记后面的方法 ,该标记用于在Grails服务器上对HTML进行预处理时动态生成链接。 最终生成到EmployeeController的链接,该链接位于
embrow / grails - app / controllers / com / nuevaconsulting / embrow / EmployeeController. groovy
特别是控制器方法browserLister() 。 我在代码中保留了一些打印语句,以便可以在运行应用程序的终端窗口中看到中间结果。
def browserLister
(
)
{
// Applies filters and sorting to return a list of desired employees
首先,打印出传递给browserLister()的参数。 我通常使用此代码开始构建控制器方法,以便完全清楚控制器接收到的内容。
println
"employee browserLister params $params"
println
(
)
接下来,处理这些参数以使其更易使用。 首先,jQuery DataTables参数,一个名为jqdtParams的Groovy映射:
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
}
println
"employee dataTableParams $jqdtParams"
println
(
)
接下来,列数据是一个名为columnMap的Groovy映射:
def columnMap
= jqdtParams.
columns .
collectEntries
{ k
, v
->
def whereTerm
=
null
switch
( v.
data
)
{
case
'extension'
:
case
'hired'
:
case
'salary'
:
if
( v.
search .
value
== ~
/\d+(,\d+)*/
)
whereTerm
= v.
search .
value .
split
(
','
) .
collect
{ it
as Integer
}
break
default
:
if
( v.
search .
value
== ~
/[A-Za-z0-9 ]+/
)
whereTerm
=
"%${v.search.value}%"
as String
break
}
[
( v.
data
)
:
[ where
: whereTerm
]
]
}
println
"employee columnMap $columnMap"
println
(
)
接下来,是从columnMap检索的所有列名称的列表,以及在视图中应如何对这些列进行排序的对应列表,分别称为allColumnList和orderList的 Groovy列表:
def allColumnList
= columnMap.
keySet
(
)
as List
println
"employee allColumnList $allColumnList"
def orderList
= jqdtParams.
order .
collect
{ k
, v
->
[ allColumnList
[ v.
column
as Integer
]
, v.
dir
]
}
println
"employee orderList $orderList"
我们将使用Grails对Hibernate标准的实现来实际执行要显示元素的选择以及它们的排序和分页。 标准要求过滤器关闭; 在大多数示例中,这是在创建条件实例本身的过程中给出的,但是在此我们预先定义过滤器关闭。 请注意,在这种情况下,“租用日期”过滤器的解释相对复杂,该过滤器被视为一年并用于建立日期范围,并使用createAlias来使我们进入相关的类职位和职位:
def filterer
=
{
createAlias
'position'
,
'p'
createAlias
'office'
,
'o'
if
( columnMap.
surname .
where
) ilike
'surname'
, columnMap.
surname .
where
if
( columnMap.
givenNames .
where
) ilike
'givenNames'
, columnMap.
givenNames .
where
if
( columnMap.
position .
where
) ilike
'p.name'
, columnMap.
position .
where
if
( columnMap.
office .
where
) ilike
'o.name'
, columnMap.
office .
where
if
( columnMap.
extension .
where
) inList
'extension'
, columnMap.
extension .
where
if
( columnMap.
salary .
where
) inList
'salary'
, columnMap.
salary .
where
if
( columnMap.
hired .
where
)
{
if
( columnMap.
hired .
where .
size
(
)
>
1
)
{
or
{
columnMap.
hired .
where .
each
{
between
'hired'
, Date.
parse
(
'yyyy/MM/dd'
,
"${it}/01/01"
as String
)
,
Date.
parse
(
'yyyy/MM/dd'
,
"${it}/12/31"
as String
)
}
}
}
else
{
between
'hired'
, Date.
parse
(
'yyyy/MM/dd'
,
"${columnMap.hired.where[0]}/01/01"
as String
)
,
Date.
parse
(
'yyyy/MM/dd'
,
"${columnMap.hired.where[0]}/12/31"
as String
)
}
}
}
此时,是时候应用前面的内容了。 第一步是获取所有Employee实例的总数,这是分页代码所要求的:
def recordsTotal
= Employee.
count
(
)
println
"employee recordsTotal $recordsTotal"
接下来,将过滤器应用于Employee实例以获取过滤结果的计数,该结果始终小于或等于总数(同样,这是针对分页代码的):
def c
= Employee.
createCriteria
(
)
def recordsFiltered
= c.
count
{
filterer.
delegate
= delegate
filterer
(
)
}
println
"employee recordsFiltered $recordsFiltered"
一旦获得了这两个计数,就可以使用分页和排序信息来获取实际的过滤实例。
def orderer
= Employee.
withCriteria
{
filterer.
delegate
= delegate
filterer
(
)
orderList.
each
{ oi
->
switch
( oi
[
0
]
)
{
case
'surname'
: order
'surname'
, oi
[
1
]
;
break
case
'givenNames'
: order
'givenNames'
, oi
[
1
]
;
break
case
'position'
: order
'p.name'
, oi
[
1
]
;
break
case
'office'
: order
'o.name'
, oi
[
1
]
;
break
case
'extension'
: order
'extension'
, oi
[
1
]
;
break
case
'hired'
: order
'hired'
, oi
[
1
]
;
break
case
'salary'
: order
'salary'
, oi
[
1
]
;
break
}
}
maxResults
( jqdtParams.
length
as Integer
)
firstResult
( jqdtParams.
start
as Integer
)
}
完全清楚地说,JTables中的分页代码管理三个计数:数据集中的记录总数,应用过滤器后的结果数量以及要在页面上显示的数量(显示是滚动还是分页) )。 该排序应用于所有过滤的记录,而分页应用于这些过滤的记录的块,以用于显示。
接下来,处理订购者返回的结果,在每一行中创建指向Employee,Position和Office实例的链接,以便用户可以单击以下链接以获取有关实例的所有详细信息:
def dollarFormatter
=
new DecimalFormat
(
'$##,###.##'
)
def employees
= orderer.
collect
{ employee
->
[
'surname'
:
"<a href='${createLink(controller: 'employee', action: 'show', id: employee.id)}'>${employee.surname}</a>"
,
'givenNames'
: employee.
givenNames
,
'position'
:
"<a href='${createLink(controller: 'position', action: 'show', id: employee.position?.id)}'>${employee.position?.name}</a>"
,
'office'
:
"<a href='${createLink(controller: 'office', action: 'show', id: employee.office?.id)}'>${employee.office?.name}</a>"
,
'extension'
: employee.
extension
,
'hired'
: employee.
hired .
format
(
'yyyy/MM/dd'
)
,
'salary'
: dollarFormatter.
format
( employee.
salary
)
]
}
最后,创建要返回的结果并将其作为JSON DataTables要求的JSON送回。
def result
=
[ draw
: jqdtParams.
draw
, recordsTotal
: recordsTotal
, recordsFiltered
: recordsFiltered
,
data
: employees
]
render
( result
as JSON
)
}
而已。
如果您熟悉Grails,这似乎比您原先想像的要多,但是这里没有火箭科学,只有很多活动部件。 但是,如果您没有太多接触Grails(或Groovy)的知识,那么有很多新的东西需要理解-闭包,委托和构建器,等等。
在这种情况下,从哪里开始? 最好的地方是了解Groovy本身,尤其是Groovy闭包以及Groovy委托和构建器 。 然后回到上面建议的有关Grails和Hibernate条件查询的内容。
结论
jQuery DataTables为Grails制作了很棒的表格数据浏览器。 对视图进行编码并不是很棘手,但是DataTables文档中提供PHP示例仅帮助您了解到这一点。 特别是,它们并不是在考虑Grails程序员的情况下编写的,也不是在探索使用引用其他类的元素(本质上是查找表)的更详细的信息。
我已经使用这种方法制作了两个数据浏览器,使用户可以选择要查看和累积记录计数的列,或者只是浏览数据。 即使在相对适中的VPS上的百万行表中,性能也很好。
一个警告:我偶然发现了Grails中公开的各种Hibernate标准机制的一些问题(请参阅我的其他GitHub存储库),因此需要谨慎和试验。 如果所有其他方法均失败,则另一种方法是动态构建SQL字符串并改为执行它们。 在撰写本文时,我更喜欢使用Grails准则,除非我陷入混乱的子查询,但这可能只是反映出我相对缺乏在Hibernate中使用子查询的经验。
希望您那里的Grails程序员觉得这很有趣。 请随时在下面留下评论或建议。
翻译自: https://opensource.com/article/18/9/using-grails-jquery-and-datatables
grails 怎么使用