ootstrap是Twitter推出的一个开源的用于前端开发的工具包。它由Twitter的设计师Mark Otto和Jacob Thornton合作开发,是一个CSS/HTML框架。Bootstrap提供了优雅的HTML和CSS规范,它即是由动态CSS语言Less写成。Bootstrap一经推出后颇受欢迎,一直是GitHub上的热门开源项目,包括NASA的MSNBC的Breaking News都使用了该项目。
Bootstrap是基于HTML5和CSS3开发的,它在jQuery的基础上进行了更为个性化和人性化的完善,形成一套自己独有的网站风格,并兼容大部分jQuery插件。
Bootstrap中包含了丰富的Web组件,根据这些组件,可以快速的搭建一个漂亮、功能完备的网站。其中包括以下组件:下拉菜单、按钮组、按钮下拉菜单、导航、导航条、面包屑、分页、排版、缩略图、警告对话框、进度条、媒体对象等。
Bootstrap自带了13个jQuery插件,这些插件为Bootstrap中的组件赋予了“生命”。其中包括:模式对话框、标签页、滚动条、弹出框等。
Bootstrap Less是一个 CSS 预处理器,让 CSS 具有动态性。另一方面,Bootstrap 是一个快速开发 Web App 和站点的工具包。这样,您可以在 CSS 中使用 Bootstrap 的 Less 变量、mixins(混合
)和 nesting(嵌套)。
更多内容和资讯或者问题,更多干货分享,尽在我的个人微信公众号,微信名:非著名程序员,微信号:smart_android(←长按复制)。个人微信号:loonggg微博:涩郎
源:《JavaScript设计模式与开发实践》
模式和重构之间有着一种与生俱来的关系。从某种角度来看,设计模式的目的就是为许多重构行为提供目标。
在JavaScript开发中,我们大部分时间都在与函数打交道,所以我们希望这些函数有着良好的命名,函数体内包含的逻辑清晰明了。如果一个函数过长,不得不加上若干注释才能让这个函数显得易读一些,那这些函数就很有必要进行重构。
如果在函数中有一段代码可以被独立出来,那我们最好把这些代码放进另外一个独立的函数中。这是一种很常见的优化工作,这样做的好处主要有以下几点。
比如在一个负责取得用户信息的函数里面,我们还需要打印跟用户信息有关的log,那么打印log的语句就可以被封装在一个独立的函数里:
var getUserInfo = function(){
ajax( 'http:// xxx.com/userInfo', function( data ){
console.log( 'userId: ' + data.userId );
console.log( 'userName: ' + data.userName );
console.log( 'nickName: ' + data.nickName );
});
};
改成:
var getUserInfo = function(){
ajax( 'http:// xxx.com/userInfo', function( data ){
printDetails( data );
});
};
var printDetails = function( data ){
console.log( 'userId: ' + data.userId );
console.log( 'userName: ' + data.userName );
console.log( 'nickName: ' + data.nickName );
};
如果一个函数体内有一些条件分支语句,而这些条件分支语句内部散布了一些重复的代码,那么就有必要进行合并去重工作。假如我们有一个分页函数paging,该函数接收一个参数currPage,currPage表示即将跳转的页码。在跳转之前,为防止currPage传入过小或者过大的数字,我们要手动对它的值进行修正,详见如下伪代码:
var paging = function( currPage ){
if ( currPage <= 0 ){
currPage = 0;
jump( currPage ); // 跳转
}else if ( currPage >= totalPage ){
currPage = totalPage;
jump( currPage ); // 跳转
}else{
jump( currPage ); // 跳转
}
};
可以看到,负责跳转的代码jump( currPage )在每个条件分支内都出现了,所以完全可以把这句代码独立出来:
var paging = function( currPage ){
if ( currPage <= 0 ){
currPage = 0;
}else if ( currPage >= totalPage ){
currPage = totalPage;
}
jump( currPage ); // 把jump函数独立出来
};
在程序设计中,复杂的条件分支语句是导致程序难以阅读和理解的重要原因,而且容易导致一个庞大的函数。假设现在有一个需求是编写一个计算商品价格的getPrice函数,商品的计算只有一个规则:如果当前正处于夏季,那么全部商品将以8折出售。代码如下:
var getPrice = function( price ){
var date = new Date();
if ( date.getMonth() >= 6 && date.getMonth() <= 9 ){ // 夏天
return price * 0.8;
}
return price;
};
观察这句代码:
if ( date.getMonth() >= 6 && date.getMonth() <= 9 ){
// ...
}
这句代码要表达的意思很简单,就是判断当前是否正处于夏天(7~10月)。尽管这句代码很短小,但代码表达的意图和代码自身还存在一些距离,阅读代码的人必须要多花一些精力才能明白它传达的意图。其实可以把这句代码提炼成一个单独的函数,既能更准确地表达代码的意思,函数名本身又能起到注释的作用。代码如下:
var isSummer = function(){
var date = new Date();
return date.getMonth() >= 6 && date.getMonth() <= 9;
};
var getPrice = function( price ){
if ( isSummer() ){ // 夏天
return price * 0.8;
}
return price;
};
在函数体内,如果有些代码实际上负责的是一些重复性的工作,那么合理利用循环不仅可以完成同样的功能,还可以使代码量更少。下面有一段创建XHR对象的代码,为了简化示例,我们只考虑版本9以下的IE浏览器,代码如下:
var createXHR = function(){
var xhr;
try{
xhr = new ActiveXObject( 'MSXML2.XMLHttp.6.0' );
}catch(e){
try{
xhr = new ActiveXObject( 'MSXML2.XMLHttp.3.0' );
}catch(e){
xhr = new ActiveXObject( 'MSXML2.XMLHttp' );
}
}
return xhr;
};
var xhr = createXHR();
下面我们灵活地运用循环,可以得到跟上面代码一样的效果:
var createXHR = function(){
var versions= [ 'MSXML2.XMLHttp.6.0ddd', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp' ];
for ( var i = 0, version; version = versions[ i++ ]; ){
try{
return new ActiveXObject( version );
}catch(e){
}
}
};
var xhr = createXHR();
许多程序员都有这样一种观念:“每个函数只能有一个入口和一个出口。”现代编程语言都会限制函数只有一个入口。但关于“函数只有一个出口”,往往会有一些不同的看法。
下面这段伪代码是遵守“函数只有一个出口的”的典型代码:
var del = function( obj ){
var ret;
if ( !obj.isReadOnly ){ // 不为只读的才能被删除
if ( obj.isFolder ){ // 如果是文件夹
ret = deleteFolder( obj );
}else if ( obj.isFile ){ // 如果是文件
ret = deleteFile( obj );
}
}
return ret;
};
嵌套的条件分支语句绝对是代码维护者的噩梦,对于阅读代码的人来说,嵌套的if、else语句相比平铺的if、else,在阅读和理解上更加困难,有时候一个外层if分支的左括号和右括号之间相隔500米之远。用《重构》里的话说,嵌套的条件分支往往是由一些深信“每个函数只能有一个出口的”程序员写出的。但实际上,如果对函数的剩余部分不感兴趣,那就应该立即退出。引导阅读者去看一些没有用的else片段,只会妨碍他们对程序的理解。
于是我们可以挑选一些条件分支,在进入这些条件分支之后,就立即让这个函数退出。要做到这一点,有一个常见的技巧,即在面对一个嵌套的if分支时,我们可以把外层if表达式进行反转。重构后的del函数如下:
var del = function( obj ){
if ( obj.isReadOnly ){ // 反转if表达式
return;
}
if ( obj.isFolder ){
return deleteFolder( obj );
}
if ( obj.isFile ){
return deleteFile( obj );
}
};
有时候一个函数有可能接收多个参数,而参数的数量越多,函数就越难理解和使用。使用该函数的人首先得搞明白全部参数的含义,在使用的时候,还要小心翼翼,以免少传了某个参数或者把两个参数搞反了位置。如果我们想在第3个参数和第4个参数之中增加一个新的参数,就会涉及许多代码的修改,代码如下:
var setUserInfo = function( id, name, address, sex, mobile, qq ){
console.log( 'id= ' + id );
console.log( 'name= ' +name );
console.log( 'address= ' + address );
console.log( 'sex= ' + sex );
console.log( 'mobile= ' + mobile );
console.log( 'qq= ' + qq );
};
setUserInfo( 1314, 'sven', 'shenzhen', 'male', '137********', 377876679 )
;
这时我们可以把参数都放入一个对象内,然后把该对象传入setUserInfo 函数,setUserInfo函数需要的数据可以自行从该对象里获取。现在不用再关心参数的数量和顺序,只要保证参数对应的key值不变就可以了:
var setUserInfo = function( obj ){
console.log( 'id= ' + obj.id );
console.log( 'name= ' + obj.name );
console.log( 'address= ' + obj.address );
console.log( 'sex= ' + obj.sex );
console.log( 'mobile= ' + obj.mobile );
console.log( 'qq= ' + obj.qq );
};
setUserInfo({
id: 1314,
name: 'sven',
address: 'shenzhen',
sex: 'male',
mobile: '137********',
qq: 377876679
});
如果调用一个函数时需要传入多个参数,那这个函数是让人望而生畏的,我们必须搞清楚这些参数代表的含义,必须小心翼翼地把它们按照顺序传入该函数。而如果一个函数不需要传入任何参数就可以使用,这种函数是深受人们喜爱的。在实际开发中,向函数传递参数不可避免,但我们应该尽量减少函数接收的参数数量。下面举个非常简单的示例。 有一个画图函数draw,它现在只能绘制正方形,接收了3个参数,分别是图形的width、heigth以及square:
var draw = function( width, height, square ){};
但实际上正方形的面积是可以通过width和height计算出来的,于是我们可以把参数square从draw函数中去掉:
var draw = function( width, height ){
var square = width * height;
};
假设以后这个draw函数开始支持绘制圆形,我们需要把参数width和height换成半径radius, 但图形的面积square始终不应该由客户传入,而是应该在draw函数内部,由传入的参数加上一定的规则计算得来。此时,我们可以使用策略模式,让draw函数成为一个支持绘制多种图形的函数。
有一些程序员喜欢大规模地使用三目运算符,来代替传统的if、else。理由是三目运算符性能高,代码量少。不过,这两个理由其实都很难站得住脚。
即使我们假设三目运算符的效率真的比if、else高,这点差距也是完全可以忽略不计的。在实际的开发中,即使把一段代码循环一百万次,使用三目运算符和使用if、else的时间开销处在同一个级别里。
同样,相比损失的代码可读性和可维护性,三目运算符节省的代码量也可以忽略不计。让JS文件加载更快的办法有很多种,如压缩、缓存、使用CDN和分域名等。把注意力只放在使用三目运算符节省的字符数量上,无异于一个300斤重的人把超重的原因归罪于头皮屑。
如果条件分支逻辑简单且清晰,这无碍我们使用三目运算符:
var global = typeof window !== "undefined" ? window : this;
但如果条件分支逻辑非常复杂,如下段代码所示,那我们最好的选择还是按部就班地编写if、else。if、else语句的好处很多,一是阅读相对容易,二是修改的时候比修改三目运算符周围的代码更加方便:
if ( !aup || !bup ) {
return a === doc ? -1 :
b === doc ? 1 :
aup ? -1 :
bup ? 1 :
sortInput ?
( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
0;
}
经常使用jQuery的程序员相当习惯链式调用方法,在JavaScript中,可以很容易地实现方法的链式调用,即让方法调用结束后返回对象自身,如下代码所示:
var User = function(){
this.id = null;
this.name = null;
};
User.prototype.setId = function( id ){
this.id = id;
return this;
};
User.prototype.setName = function( name ){
this.name = name;
return this;
};
console.log( new User().setId( 1314 ).setName( 'sven' ) );
或者:
var User = {
id: null,
name: null,
setId: function( id ){
this.id = id;
return this;
},
setName: function( name ){
this.name = name;
return this;
}
};
console.log( User.setId( 1314 ).setName( 'sven' ) );
使用链式调用的方式并不会造成太多阅读上的困难,也确实能省下一些字符和中间变量,但节省下来的字符数量同样是微不足道的。链式调用带来的坏处就是在调试的时候非常不方便,如果我们知道一条链中有错误出现,必须得先把这条链拆开才能加上一些调试log或者增加断点,这样才能定位错误出现的地方。
如果该链条的结构相对稳定,后期不易发生修改,那么使用链式调用无可厚非。但如果该链条很容易发生变化,导致调试和维护困难,那么还是建议使用普通调用的形式:
var user = new User();
user.setId( 1314 );
user.setName( 'sven' );
在HTML5版“街头霸王”的第一版代码中,负责创建游戏人物的Spirit 类非常庞大,不仅要负责创建人物精灵,还包括了人物的攻击、防御等动作方法,代码如下:
var Spirit = function( name ){
this.name = name;
};
Spirit.prototype.attack = function( type ){ // 攻击
if ( type === 'waveBoxing' ){
console.log( this.name + ': 使用波动拳' );
}else if( type === 'whirlKick' ){
console.log( this.name + ': 使用旋风腿' );
}
};
var spirit = new Spirit( 'RYU' );
spirit.attack( 'waveBoxing' ); // 输出:RYU: 使用波动拳
spirit.attack( 'whirlKick' ); // 输出:RYU: 使用旋风腿
后来发现,Spirit.prototype.attack这个方法实现是太庞大了,实际上它完全有必要作为一个单独的类存在。面向对象设计鼓励将行为分布在合理数量的更小对象之中:
var Attack = function( spirit ){
this.spirit = spirit;
};
Attack.prototype.start = function( type ){
return this.list[ type ].call( this );
};
Attack.prototype.list = {
waveBoxing: function(){
console.log( this.spirit.name + ': 使用波动拳' );
},
whirlKick: function(){
console.log( this.spirit.name + ': 使用旋风腿' );
}
};
现在的Spirit类变得精简了很多,不再包括各种各样的攻击方法,而是把攻击动作委托给Attack类的对象来执行,这段代码也是策略模式的运用之一:
var Spirit = function( name ){
this.name = name;
this.attackObj = new Attack( this );
};
Spirit.prototype.attack = function( type ){ // 攻击
this.attackObj.start( type );
};
var spirit = new Spirit( 'RYU' );
spirit.attack( 'waveBoxing' ); // 输出:RYU: 使用波动拳
spirit.attack( 'whirlKick' ); // 输出:RYU: 使用旋风
假设在函数体内有一个两重循环语句,我们需要在内层循环中判断,当达到某个临界条件时退出外层的循环。我们大多数时候会引入一个控制标记变量:
var func = function(){
var flag = false;
for ( var i = 0; i < 10; i++ ){
for ( var j = 0; j < 10; j++ ){
if ( i * j >30 ){
flag = true;
break;
}
}
if ( flag === true ){
break;
}
}
};
第二种做法是设置循环标记:
var func = function(){
outerloop:
for ( var i = 0; i < 10; i++ ){
innerloop:
for ( var j = 0; j < 10; j++ ){
if ( i * j >30 ){
break outerloop;
}
}
}
};
这两种做法无疑都让人头晕目眩,更简单的做法是在需要中止循环的时候直接退出整个方法:
var func = function(){
for ( var i = 0; i < 10; i++ ){
for ( var j = 0; j < 10; j++ ){
if ( i * j >30 ){
return;
}
}
}
};
当然用return直接退出方法会带来一个问题,如果在循环之后还有一些将被执行的代码呢?如果我们提前退出了整个方法,这些代码就得不到被执行的机会:
var func = function(){
for ( var i = 0; i < 10; i++ ){
for ( var j = 0; j < 10; j++ ){
if ( i * j >30 ){
return;
}
}
}
console.log( i ); // 这句代码没有机会被执行
};
为了解决这个问题,我们可以把循环后面的代码放到return后面,如果代码比较多,就应该把它们提炼成一个单独的函数:
var print = function( i ){
console.log( i );
};
var func = function(){
for ( var i = 0; i < 10; i++ ){
for ( var j = 0; j < 10; j++ ){
if ( i * j >30 ){
return print( i );
}
}
}
};
func();- END -
译自: https://opensource.com/article/18/9/using-grails-jquery-and-datatables
作者: Chris Hermansen
译者: jrg
本文介绍如何构建一个基于 Grails 的数据浏览器来可视化复杂的表格数据。
我是 Grails 的忠实粉丝。当然,我主要是热衷于利用命令行工具来探索和分析数据的数据从业人员。数据从业人员经常需要查看数据,这也意味着他们通常拥有优秀的数据浏览器。利用 Grails、 jQuery ,以及 DataTables jQuery 插件 ,我们可以制作出非常友好的表格数据浏览器。
DataTables 网站 提供了许多“食谱式”的教程文档,展示了如何组合一些优秀的示例应用程序,这些程序包含了完成一些非常漂亮的东西所必要的 JavaScript、HTML,以及偶尔出现的 PHP 。但对于那些宁愿使用 Grails 作为后端的人来说,有必要进行一些说明示教。此外,样本程序中使用的数据是一个虚构公司的员工的单个平面表格数据,因此处理这些复杂的表关系可以作为读者的一个练习项目。
本文中,我们将创建具有略微复杂的数据结构和 DataTables 浏览器的 Grails 应用程序。我们将介绍 Grails 标准,它是 Groovy 式的 Java Hibernate 标准。我已将代码托管在 GitHub 上方便大家访问,因此本文主要是对代码细节的解读。
首先,你需要配置 Java、Groovy、Grails 的使用环境。对于 Grails,我倾向于使用终端窗口和 Vim ,本文也使用它们。为获得现代的 Java 环境,建议下载并安装 Linux 发行版提供的 Open Java Development Kit (OpenJDK)(应该是 Java 8、9、10 或 11 之一,撰写本文时,我正在使用 Java 8)。从我的角度来看,获取最新的 Groovy 和 Grails 的最佳方法是使用 SDKMAN! 。
从未尝试过 Grails 的读者可能需要做一些背景资料阅读。作为初学者,推荐文章 创建你的第一个 Grails 应用程序 。
正如上文所提,我将本文中员工信息浏览器的源代码托管在 GitHub 上。进一步讲,应用程序 embrow 是在 Linux 终端中用如下命令构建的:
cd Projects
grails create-app com.nuevaconsulting.embrow
域类和单元测试创建如下:
grails create-domain-class com.nuevaconsulting.embrow.Position
grails create-domain-class com.nuevaconsulting.embrow.Office
grails create-domain-class com.nuevaconsulting.embrow.Employeecd embrowgrails createdomaincom.grails createdomaincom.grails createdomaincom.
这种方式构建的域类没有属性,因此必须按如下方式编辑它们:
Position 域类:
package com.nuevaconsulting.embrow
class Position {
String name
int starting
static constraints = {
name nullable: false, blank: false
starting nullable: false
}
}com.Stringint startingstatic constraintsnullableblankstarting nullable
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
}
}
Enployee 域类:
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
: 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 实例表的引用或者外键。
现在你可以生成控制器,视图,以及其他各种测试组件:
-all com.nuevaconsulting.embrow.Position
grails generate-all com.nuevaconsulting.embrow.Office
grails generate-all com.nuevaconsulting.embrow.Employeegrails generateall com.grails generateall com.grails generateall com.
此时,你已经准备好了一个基本的增删改查(CRUD)应用程序。我在 grails-app/init/com/nuevaconsulting/BootStrap.groovy 中包含了一些基础数据来填充表格。
如果你用如下命令来启动应用程序:
grails run-app
在浏览器输入 http://localhost:8080/,你将会看到如下界面:
Embrow 应用程序主界面。
单击 “OfficeController” 链接,会跳转到如下界面:
Office 列表
注意,此表由 OfficeController 的 index 方式生成,并由视图 office/index.gsp 显示。
同样,单击 “EmployeeController” 链接 跳转到如下界面:
employee 控制器
好吧,这很丑陋: Position 和 Office 链接是什么?
上面的命令 generate-all 生成的视图创建了一个叫 index.gsp 的文件,它使用 Grails <f:table/> 标签,该标签默认会显示类名(com.nuevaconsulting.embrow.Position)和持久化示例标识符(30)。这个操作可以自定义用来产生更好看的东西,并且自动生成链接,自动生成分页以及自动生成可排序列的一些非常简洁直观的东西。
但该员工信息浏览器功能也是有限的。例如,如果想查找 “position” 信息中包含 “dev” 的员工该怎么办?如果要组合排序,以姓氏为主排序关键字,“office” 为辅助排序关键字,该怎么办?或者,你需要将已排序的数据导出到电子表格或 PDF 文档以便通过电子邮件发送给无法访问浏览器的人,该怎么办?
jQuery DataTables 插件提供了这些所需的功能。允许你创建一个完成的表格数据浏览器。
要基于 jQuery DataTables 创建员工信息浏览器,你必须先完成以下两个任务:
在目录 embrow/grails-app/views/employee 中,首先复制 index.gsp 文件,重命名为 browser.gsp:
cd Projects
cd embrow/grails-app/views/employee
cp gsp browser.gsp
此刻,你自定义新的 browser.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 表模型,并创建一个处理程序来进行列过滤。
<g:javascript>
$('#employee_dt tfoot th').each( function() {javascript
下面的代码处理表格列底部的过滤器框的大小:
var title = $(this).text();
if (title == 'Extension' || title == 'Hired')
$(this).html('<input type="text" size="5" placeholder="' + title + '?" />');
else
$(this).html('<input type="text" size="15" placeholder="' + title + '?" />');
});titletitletitletitletitle
接下来,定义表模型。这是提供所有表选项的地方,包括界面的滚动,而不是分页,根据 DOM 字符串提供的装饰,将数据导出为 CSV 和其他格式的能力,以及建立与服务器的 AJAX 连接。 请注意,使用 Groovy GString 调用 Grails createLink() 的方法创建 URL,在 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的屏幕截图:
这是另一个屏幕截图,显示了过滤和多列排序(寻找 “position” 包括字符 “dev” 的员工,先按 “office” 排序,然后按姓氏排序):
这是另一个屏幕截图,显示单击 CSV 按钮时会发生什么:
最后,这是一个截图,显示在 LibreOffice 中打开的 CSV 数据:
好的,视图部分看起来非常简单;因此,控制器必须做所有繁重的工作,对吧? 让我们来看看……
回想一下,我们看到过这个字符串:
"${createLink(controller: 'employee', action: 'browserLister')}"
对于从 DataTables 模型中调用 AJAX 的 URL,是在 Grails 服务器上动态创建 HTML 链接,其 Grails 标记背后通过调用 createLink() 的方法实现的。这会最终产生一个指向 EmployeeController 的链接,位于:
embrow/grails-app/controllers/com/nuevaconsulting/embrow/EmployeeController.groovy
特别是控制器方法 browserLister()。我在代码中留了一些 print 语句,以便在运行时能够在终端看到中间结果。
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 中检索的所有列表,以及在视图中应如何排序这些列表,Groovy 列表分别称为 allColumnList 和 orderList :
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 标准实现来实际选择要显示的元素以及它们的排序和分页。标准要求过滤器关闭;在大多数示例中,这是作为标准实例本身的创建的一部分给出的,但是在这里我们预先定义过滤器闭包。请注意,在这种情况下,“date hired” 过滤器的相对复杂的解释被视为一年并应用于建立日期范围,并使用 createAlias 以允许我们进入相关类别 Position 和 Office:
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)
}
要完全清楚,JTable 中的分页代码管理三个计数:数据集中的记录总数,应用过滤器后得到的数字,以及要在页面上显示的数字(显示是滚动还是分页)。 排序应用于所有过滤的记录,并且分页应用于那些过滤的记录的块以用于显示目的。
接下来,处理命令返回的结果,在每行中创建指向 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 返回,这是 jQuery DataTables 所需要的。
def result = [draw: jqdtParams.draw, recordsTotal: recordsTotal, recordsFiltered: recordsFiltered, data: employees]
render(result as JSON)
}
大功告成。
如果你熟悉 Grails,这可能看起来比你原先想象的要多,但这里没有火箭式的一步到位方法,只是很多分散的操作步骤。但是,如果你没有太多接触 Grails(或 Groovy),那么需要了解很多新东西 - 闭包,代理和构建器等等。
在那种情况下,从哪里开始? 最好的地方是了解 Groovy 本身,尤其是 Groovy closures 和 Groovy delegates and builders 。然后再去阅读上面关于 Grails 和 Hibernate 条件查询的建议阅读文章。
jQuery DataTables 为 Grails 制作了很棒的表格数据浏览器。对视图进行编码并不是太棘手,但 DataTables 文档中提供的 PHP 示例提供的功能仅到此位置。特别是,它们不是用 Grails 程序员编写的,也不包含探索使用引用其他类(实质上是查找表)的元素的更精细的细节。
我使用这种方法制作了几个数据浏览器,允许用户选择要查看和累积记录计数的列,或者只是浏览数据。即使在相对适度的 VPS 上的百万行表中,性能也很好。
一个警告:我偶然发现了 Grails 中暴露的各种 Hibernate 标准机制的一些问题(请参阅我的其他 GitHub 代码库),因此需要谨慎和实验。如果所有其他方法都失败了,另一种方法是动态构建 SQL 字符串并执行它们。在撰写本文时,我更喜欢使用 Grails 标准,除非我遇到杂乱的子查询,但这可能只反映了我在 Hibernate 中对子查询的相对缺乏经验。
我希望 Grails 程序员发现本文的有趣性。请随时在下面留下评论或建议。
via: https://opensource.com/article/18/9/using-grails-jquery-and-datatables
作者: Chris Hermansen 选题: lujun9972 译者: jrg 校对: wxy
本文由 LCTT 原创编译, Linux中国 荣誉推出
*请认真填写需求信息,我们会在24小时内与您取得联系。