Linux中國

在 Grails 中使用 jQuery 和 DataTables

我是 Grails 的忠實粉絲。當然,我主要是熱衷於利用命令行工具來探索和分析數據的數據從業人員。數據從業人員經常需要查看數據,這也意味著他們通常擁有優秀的數據瀏覽器。利用 GrailsjQuery,以及 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
    }
}

請注意,雖然 PositionOffice 域類使用了預定義的 Groovy 類型 String 以及 int,但 Employee 域類定義了 PositionOffice 欄位(以及預定義的 Date)。這會導致創建資料庫表,其中存儲的 Employee 實例中包含了指向存儲 PositionOffice 實例表的引用或者外鍵。

現在你可以生成控制器,視圖,以及其他各種測試組件:

-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 home screen

Embrow 應用程序主界面。

單擊 「OfficeController」 鏈接,會跳轉到如下界面:

Office list

Office 列表

注意,此表由 OfficeControllerindex 方式生成,並由視圖 office/index.gsp 顯示。

同樣,單擊 「EmployeeController」 鏈接 跳轉到如下界面:

Employee controller

employee 控制器

好吧,這很醜陋: Position 和 Office 鏈接是什麼?

上面的命令 generate-all 生成的視圖創建了一個叫 index.gsp 的文件,它使用 Grails <f:table/> 標籤,該標籤默認會顯示類名(com.nuevaconsulting.embrow.Position)和持久化示例標識符(30)。這個操作可以自定義用來產生更好看的東西,並且自動生成鏈接,自動生成分頁以及自動生成可排序列的一些非常簡潔直觀的東西。

但該員工信息瀏覽器功能也是有限的。例如,如果想查找 「position」 信息中包含 「dev」 的員工該怎麼辦?如果要組合排序,以姓氏為主排序關鍵字,「office」 為輔助排序關鍵字,該怎麼辦?或者,你需要將已排序的數據導出到電子表格或 PDF 文檔以便通過電子郵件發送給無法訪問瀏覽器的人,該怎麼辦?

jQuery DataTables 插件提供了這些所需的功能。允許你創建一個完成的表格數據瀏覽器。

創建員工信息瀏覽器視圖和控制器的方法

要基於 jQuery DataTables 創建員工信息瀏覽器,你必須先完成以下兩個任務:

  1. 創建 Grails 視圖,其中包含啟用 DataTable 所需的 HTML 和 JavaScript
  2. 給 Grails 控制器增加一個方法來控制新視圖。

員工信息瀏覽器視圖

在目錄 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>
$(&apos;#employee_dt tfoot th&apos;).each( function() {javascript

下面的代碼處理表格列底部的過濾器框的大小:

var title = $(this).text();
if (title == &apos;Extension&apos; || title == &apos;Hired&apos;)
$(this).html(&apos;<input type="text" size="5" placeholder="&apos; + title + &apos;?" />&apos;);
else
$(this).html(&apos;<input type="text" size="15" placeholder="&apos; + title + &apos;?" />&apos;);
});titletitletitletitletitle

接下來,定義表模型。這是提供所有表選項的地方,包括界面的滾動,而不是分頁,根據 DOM 字元串提供的裝飾,將數據導出為 CSV 和其他格式的能力,以及建立與伺服器的 AJAX 連接。 請注意,使用 Groovy GString 調用 Grails createLink() 的方法創建 URL,在 EmployeeController 中指向 browserLister 操作。同樣有趣的是表格列的定義。此信息將發送到後端,後端查詢資料庫並返回相應的記錄。

var table = $(&apos;#employee_dt&apos;).DataTable( {
"scrollY": 500,
"deferRender": true,
"scroller": true,
"dom": "Brtip",
"buttons": [ &apos;copy&apos;, &apos;csv&apos;, &apos;excel&apos;, &apos;pdf&apos;, &apos;print&apos; ],
"processing": true,
"serverSide": true,
"ajax": {
"url": "${createLink(controller: &apos;employee&apos;, action: &apos;browserLister&apos;)}",
"type": "POST",
},
"columns": [
{ "data": "surname" },
{ "data": "givenNames" },
{ "data": "position" },
{ "data": "office" },
{ "data": "extension" },
{ "data": "hired" },
{ "data": "salary" }
]
});

最後,監視過濾器列以進行更改,並使用它們來應用過濾器。

table.columns().every(function() {
var that = this;
$(&apos;input&apos;, this.footer()).on(&apos;keyup change&apos;, 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 數據:

好的,視圖部分看起來非常簡單;因此,控制器必須做所有繁重的工作,對吧? 讓我們來看看……

控制器 browserLister 操作

回想一下,我們看到過這個字元串:

"${createLink(controller: &apos;employee&apos;, action: &apos;browserLister&apos;)}"

對於從 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(&apos;]&apos;,&apos;&apos;).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 &apos;extension&apos;:
    case &apos;hired&apos;:
    case &apos;salary&apos;:
        if (v.search.value ==~ /d+(,d+)*/)
            whereTerm = v.search.value.split(&apos;,&apos;).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 列表分別稱為 allColumnListorderList

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 以允許我們進入相關類別 PositionOffice

def filterer = {
    createAlias &apos;position&apos;,        &apos;p&apos;
    createAlias &apos;office&apos;,          &apos;o&apos;

    if (columnMap.surname.where)    ilike  &apos;surname&apos;,     columnMap.surname.where
    if (columnMap.givenNames.where) ilike  &apos;givenNames&apos;,  columnMap.givenNames.where
    if (columnMap.position.where)   ilike  &apos;p.name&apos;,      columnMap.position.where
    if (columnMap.office.where)     ilike  &apos;o.name&apos;,      columnMap.office.where
    if (columnMap.extension.where)  inList &apos;extension&apos;,   columnMap.extension.where
    if (columnMap.salary.where)     inList &apos;salary&apos;,      columnMap.salary.where
    if (columnMap.hired.where) {
        if (columnMap.hired.where.size() > 1) {
            or {
                columnMap.hired.where.each {
                    between &apos;hired&apos;, Date.parse(&apos;yyyy/MM/dd&apos;,"${it}/01/01" as String),
                        Date.parse(&apos;yyyy/MM/dd&apos;,"${it}/12/31" as String)
                }
            }
        } else {
            between &apos;hired&apos;, Date.parse(&apos;yyyy/MM/dd&apos;,"${columnMap.hired.where[0]}/01/01" as String),
                Date.parse(&apos;yyyy/MM/dd&apos;,"${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 &apos;surname&apos;:    order &apos;surname&apos;,    oi[1]; break
                case &apos;givenNames&apos;: order &apos;givenNames&apos;, oi[1]; break
                case &apos;position&apos;:   order &apos;p.name&apos;,     oi[1]; break
                case &apos;office&apos;:     order &apos;o.name&apos;,     oi[1]; break
                case &apos;extension&apos;:  order &apos;extension&apos;,  oi[1]; break
                case &apos;hired&apos;:      order &apos;hired&apos;,      oi[1]; break
                case &apos;salary&apos;:     order &apos;salary&apos;,     oi[1]; break
                }
            }
            maxResults (jqdtParams.length as Integer)
            firstResult (jqdtParams.start as Integer)
        }

要完全清楚,JTable 中的分頁代碼管理三個計數:數據集中的記錄總數,應用過濾器後得到的數字,以及要在頁面上顯示的數字(顯示是滾動還是分頁)。 排序應用於所有過濾的記錄,並且分頁應用於那些過濾的記錄的塊以用於顯示目的。

接下來,處理命令返回的結果,在每行中創建指向 EmployeePositionOffice 實例的鏈接,以便用戶可以單擊這些鏈接以獲取相關實例的所有詳細信息:

        def dollarFormatter = new DecimalFormat(&apos;$##,###.##&apos;)
        def employees = orderer.collect { employee ->
            [&apos;surname&apos;: "<a href=&apos;${createLink(controller: &apos;employee&apos;, action: &apos;show&apos;, id: employee.id)}&apos;>${employee.surname}</a>",
                &apos;givenNames&apos;: employee.givenNames,
                &apos;position&apos;: "<a href=&apos;${createLink(controller: &apos;position&apos;, action: &apos;show&apos;, id: employee.position?.id)}&apos;>${employee.position?.name}</a>",
                &apos;office&apos;: "<a href=&apos;${createLink(controller: &apos;office&apos;, action: &apos;show&apos;, id: employee.office?.id)}&apos;>${employee.office?.name}</a>",
                &apos;extension&apos;: employee.extension,
                &apos;hired&apos;: employee.hired.format(&apos;yyyy/MM/dd&apos;),
                &apos;salary&apos;: 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 closuresGroovy 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中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國