22 February 2014

Memulai Grails (Bagian - 5)

Pembaca yang budiman, setelah pada artikel sebelumya kita membahas bagaimana mengintegrasikan Grails Framework dengan Twitter-Bootstrap css framework untuk view "list", maka sekarang kita akan melanjutkan pembahasan integrasi ini pada tahap selanjutnya yaitu membuat view lain seperti form agar dapat juga terintegrasi dengan bootstrap css framework. Tanpa berpanjang-panjang lagi mari kita mulai saja.
Oh ya saya hampir lupa, bukankah pada tulisan lalu kita baru membuat satu controller saja yaitu "CustomAuthorController" kan?, hmm, sekarang bagaimana kalo kita buat "CustomBookController" dengan cara yang sama, hanya saja ada sedikit perlakuan yang berbeda dalam class domain Book, sebab class domain Book ini kan memiliki relasi dengan class domain Author. Silahkan anda buat sendiri controller baru bernama "CustomBookController" dengan cara persis sama dengan yang kita buat sebelumya, saya akan membiarkan anda berkreasi sendiri dalam kode-kodenya, sehingga akan kita skip saja tahap-tahap pembuatan "CustomBookController" tersebut, namun hasilnya paling tidak bisa seperti gambar berikut ini (anggap aja ujian/tes … :P):

Jika ada pertanyaan bagaimana cara mengisi data Book-nya, jawabannya adalah silahkan isi sendiri di file Bootstrap.groovy. Nah pada gambar tersebut kolom nama tertulis dengan benar, kalo para pembaca mengikuti artikel ini dari bagian 1 tentunya ini bukan hal yang sulit bukan?
Baiklah sekarang kita lanjutkan pembahasan dengan membuat form Author, langkah pertama kita tambahkan sebuah action baru bernama “add” pada controller CustomAuthor, adapun kodenya adalah seperti dibawah ini:
def add(){
  [model:new Author()]
}
Nah, perhatikan kode actionadd” tersebut, cukup sederhana bukan?, disana kita membuat sebuah variable yang akan kita lempar ke form gsp bernama model. Kemudian buatlah sebuah file gsp bernama “add.gsp” dan letakkan ke dalam foldergrails-app/views/customAuthor/” dan tuliskan kode-kode berikut ini ke dalam fileadd.gsp” tersebut:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <meta name="layout" content="custom-layout">
    <title></title>
</head>
<body>
<form action="addNew" method="post" class="col-md-4">
    <g:render template="form"/>
    %{--button area here--}%
    <p>
      <input type="submit" class="btn btn-info btn-sm" value="Save">
      <input type="button" class="btn btn-info btn-sm btnCancel" value="Cancel">
    </p>
    %{--end button area--}%
</form>
<script type="text/javascript">
    $(document).ready(function(){
        $(".btnCancel").click(function(){
            window.location = "${request.contextPath}/customAuthor/list"
        });
    })
</script>
</body>
</html>
Jangan dicoba dulu, sekarang buatlah sebuah file baru lagi bernama “_form.gsp” dan letakkan dalam folder yang sama dengan fileadd.gsp” tadi yaitu di dalam foldergrails-app/views/customAuthor”, adapun isi dari file_form.gsp” tersebut adalah seperti dibawah ini:
<table class="table">
    <tr>
        <td><label for="authorName">Name</label> </td>
        <td><input type="text" class="form-control" id="authorName" name="authorName" value="${model?.authorName}"></td>
    </tr>
    <tr>
        <td><label for="address">Address</label> </td>
        <td><input type="text" class="form-control" id="address" name="address" value="${model?.authorName}"></td>
    </tr>
    <tr>
        <td><label for="age">Age</label> </td>
        <td><input type="text" class="form-control" id="age" name="age" value="${model?.age}"></td>
    </tr>
</table>
Ok, begini penjelasannya, file_form.gsp” yang terakhir kita buat adalah sebuah template form yang nantinya akan kita bisa gunakan kembali. File_form.gsp” ini kita render dalam fileadd.gsp”  dengan cara menuliskan “<g:render template=”form.gsp”>”, ingat bahwa jika kita ingin membuat template maka harus diawali dengan karakter underscore ( _ ) sementara pemanggilannya tetap menghilangkan karakter tersebut dalam penulisan kodenya. Sementara integrasi dengan bootstrap css framework dapat terlihat dari cara kita memakai class milik bootstrap framework pada setiap element html pada form tersebut.
Kemudian perhatikan pada fileadd.gsp” di atas, form tersebut terarah pada sebuah action dengan nama “addNew” itu artinya kita akan membuat sebuah action lagi dalam controller CustomAuthor ini dengan nama “addNew”. Secara flow aplikasi, sudah pasti actionaddNew” yang akan kita buat pada controller CustomAuthor ini berfungsi menyimpan data-data yang akan di masukkan oleh form tersebut, berikut ini adalah kode actionaddNew” yang akan kita tambahkan dalam controller CustomAuthor:
def addNew() {
    def author = new Author(params)
    if (!author.save(flush: true)) {
       render(view: "add", model: [author:author])
       return
    }
    redirect(action: "list")
}
Pada kode di atas kita melihat semua paramater yang di-POST dari gsp akan ditampung dalam sebuah variable author dan langsung dibentuk menjadi object author itu sendiri, oleh sebab itulah pada file_form.gsp” kita memberi nama masing-masing element html tersebut dengan nama-nama yang sama persis dengan properti milik class domain Author, seperti authorName, age, dan address. Fungsi penamaan yang sama ini adalah agar tiap value dari element tersebut bisa langsung dikonversi menjadi object Author. Kemudian kita menuliskan pencabangan penyimpanan, jika tidak berhasil disimpan maka flow aplikasi akan me-render fileadd.gsp” kembali, namun jika berhasil menyimpan data maka flow aplikasi akan mengarahkan ke actionlist” sehingga bisa langsung tampil data-data yang sudah disimpan.
Silahkan mencoba sendiri fungsi tersebut dengan cara membuka url berikut ini pada browser anda "http://localhost:8080/grails-book-catalog/customAuthor/add”, kemudian isi form tersebut dan silahkan lihat bagaimana hasilnya. Jika sudah berhasil, kita tinggal menghubungkan tombol “Add New” pada viewlist” dengan cara mengedit kodenya seperti di bawah ini:
<a class="btn btn-default btn-info" href="add">
Dengan demikian pembuatan fungsi insert pada rangkaian CRUD untuk class domain Author kita sudah selesai. 
Langkah selanjutnya adalah kita akan membuat fungsi delete pada controller CustomAuthor, kali ini kita akan sedikit variasikan pemanfaatan fungsi delete ini dengan cara menerapkan mekanisme ajax pada flow aplikasi kita. Sekarang buatlah sebuah action bernama “delete” pada controller CustomAuthor, seperti kode berikut ini:
def delete() {
    def author = Author.get(params.id)
    def map;
    if (author == null) {
        map = [res: "Delete Failed", msg: "Data Not Found"]
    } else {
       try {
          author.delete(flush: true)
          map = [res: "Delete Succeed"]
       } catch (DataIntegrityViolationException dive) {
          map = [res:"Delete Failed", msg: dive.message]
       }
    }
    render map as JSON
}
Ok, mari kita telaah kode tersebut, pada actiondelete” ini object Author akan dicari menggunakan method mixinsAuthor.get” dimana parameternya adalah id yang di POST dari http request. Jika object author tidak ditemukan dalam database (author==null) maka sebuah variable bernama map dengan tipe data Map akan di-construct dengan detil isinya adalah res=”Delete Failed” dan msg=”Data Not Found”. Namun jika object author ditemukan maka data object author tersebut akan dicoba di hapus dalam blok try-catch, kenapa harus di try-catch? Sebab kita sama-sama tahu kalau class domain Author ini memiliki relasi dengan class domain Book, sehingga ketika menghapusnya bisa saja terjadi constraint yang disebabkan karena data yang ingin kita hapus memiliki relasi dengan data Book. Jika data author berhasil dihapus maka variable map akan diisi oleh pesan “Delete Succeed” dan jika tidak berhasil dihapus karena terkena constraint maka diisi oleh pesan “Delete Failed” dan pesan detil berupa message yang ditangkap oleh Exception yang terjadi. 
Grails Framework dilengkapi fitur berupa JSON response sehingga kita tidak perlu repot untuk membuat bagaimana supaya sebuah response dari http request bisa mengeluarkan output berupa format JSON, cukup dengan men-casting object yang ingin dikeluarkan menjadi object JSON milik Grails Framework, pada kode diatas kita melakukan hal tersebut dengan cara me-render object map dan men-casting-nya menjadi object JSON (render map as JSON). Disamping itu dalam kode diatas kita juga memakai class DataIntegrityViolenceException milik Grails sebagai class untuk menangkap error dalam blok try-catch yang kita buat. Oleh karena kita memakai dua class tersebut, maka kita harus meng-import dua class tersebut ke dalam controller CustomAuthor ini, tambahkan dua class tersebut seperti berikut ini pada bagian atas file CustomAuthor dibawah baris package:
package org.learn.grails
import grails.converters.JSON
import org.springframework.dao.DataIntegrityViolationException
Kemudian kita sudah menyiapkan sebuah tombol dalam viewlist”, [oops, saya salah menuliskan label tombol tersebut ternyata, harusnya tertulis “Del” malah tertulis “Edit”], ok edit kode untuk tombol delete pada viewlist” yang ada dalam filelist.gsp” yang terletak pada foldergrails-app/views/customAuthor” menjadi seperti dibawah ini: 
<a href="#" id="${author.id}" class="btn btn-default btn-danger delBtn"><span class="glyphicon glyphicon-trash"></span> Delete</a> 
Kemudian masih pada filelist.gsp” tersebut tambahkan kode-kode javascript yang berfungsi untuk meng-implementasi mekanisme ajax, tepat dibawah tag “pagination” seperti berikut ini:
<div class="pagination">
    <g:paginate total="${totalData}"/>
</div>

<script type="text/javascript">
    $(document).ready(function () {
        $(".delBtn").click(function () {
            $.ajax({
                url: "delete",
                data: "id=" + $(this).attr("id"),
                type: 'POST',
                dataType: "json",
                success: function (data) {
                    alert("Result : " + data.res);
                    window.location.reload();
                }
            });
        })
    })
</script>
Kalo para pembaca akrab dengan implementasi ajax menggunakan jQuery tentu saja kode di atas pasti akan gampang dimengerti. Intinya adalah ketika actiondelete” dipanggil, maka JSON yang dihasilkan oleh controller akan ditampung ke dalam sebuah javascrtipt alert sebagai informasi kepada user yang memberitahukan apakah data tersebut berhasil dihapus dari database atau tidak, kemudian halaman ini akan me-refresh dengan sendirinya sebagai cara untuk me-display kembali daftar Author pada actionlist” tersebut.
Oh ya, karena kita menggunakan jQuery dalam impelementasi ajax ini, ada sedikit kode yang dimodifikasi pada filecustom-layout.gsp” yang terletak pada foldergrails-app/views/layout”, yaitu memindahkan referensi javascript yang tadinya ada di barisan bawah menjadi dibarisan atas seperti contoh berikut ini:
<link rel="stylesheet" href="${resource(dir: 'css', file: 'custom-layout.css')}" type="text/css">
<g:layoutHead/>
<script type="text/javascript" src="${resource(plugin:'jquery',  dir: 'js/jquery',file: 'jquery-1.8.3.min.js' )}"></script>
<r:layoutResources/>

Nah, para pembaca silahkan anda coba sendiri hasil pembahasan ini diatas, dan silahkan buktikan sendiri. Mudah-mudahan hasilnya sama dengan yang saya lakukan, hingga kita bisa menyimpulkan bahwa “semuanya berfungsi”.
Kita sudah membuat fungsi addNew dan fungsi delete, dengan menggunakan cara-cara diatas, selanjutnya adalah kita akan membuat fungsi edit pada controller CustomAuthor. Mari kita lanjutkan. Untuk membuat fungsi edit ini flow aplikasi tersebut mungkin secara deskripsi bisa dikatakan seperti ini, ketika user mengklik tombol edit pada viewlist” maka user akan di hantarkan pada actionedit” yang secara views adalah berupa halaman html berbentuk form sama dengan halaman html untuk actionadd” tadi, hanya saja kali ini html form tersebut berisi data Author yang kita pilih untuk di-edit. Untuk membuat fungsi edit tersebut silahkan tambahkan action yang kita beri nama “edit” pada controller CustomAuthor seperti contoh kode dibawah ini:
def edit(Long id) {
    def author = Author.get(id)
    if (author == null) {
        redirect(action: "list")
        return
    }
    [model: author]
}
Ok, sepertinya tidak ada yang spesial dari kode untuk actionedit” di atas bukan? Flow aplikasinya adalah object Author akan dicari berdasarkan paramater “id” yang dilempar dari http request dan kemudian ditampung dalam variable author. Jika object dengan paramater tersebut tidak ditemukan maka flow aplikasi akan me-redirect request tersebut ke actionlist”. Sementara jika diteliti dari kode tersebut maka sebuah variable bernama “model” akan menampung data Author tersebut untuk dihantarkan ke dalam form. Langkah selanjutnya adalah buatlah sebuah file bernama “edit.gsp” dan letakkan di dalam foldergrails-app/views/customAuthor” adapun isi file tersebut adalah sebagai berikut:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <meta name="layout" content="custom-layout"/>
    <title></title>
</head>

<body>
<form action="update" method="POST">
    <input type="hidden" id="id" name="id" value="${model?.id}">
    %{--template here--}%
    <g:render template="form"/>
    %{--button action here--}%
    <p>
     <input type="submit" class="btn btn-default btn-info btn-sm" value="Update">
     <input type="button" class="btn btn-default btn-info btn-sm btnCancel" value="Cancel">
    </p>
</form>
<script type="text/javascript">
    $(document).ready(function(){
        $(".btnCancel").click(function(){
            window.location = "${request.contextPath}/customAuthor/list";
        });
    })
</script>
</body>
</html>
Dalam file tersebut kita kembali memanfaatkan template_form.gsp” yang sudah kita buat sebelumnya, disinilah kekuatan templating tersebut, bayangkan jika field author lebih banyak dari yang kita buat, maka kita sudah menghemat ukuran aplikasi ini bukan?. Perhatikan kode pada fileedit.gsp” tersebut, form action akan menuju kepada controller dengan actionupdate”, ini artinya kita harus membuat sebuah action baru dalam controller CustomAuthor bernama “update”, berikut ini kode dari actionupdate” tersebut:
def update() {
    def author = Author.get(params.id);
    if (author != null) {
        author.authorName = params.authorName
        author.address = params.address
        author.age = params.age

        if (author.save(flush: true)) {
           redirect(action: "list")
        } else {
           [msg: "Update Failed"]
        }
    }else{
        [msg:"Update Failed, data cannot be found"]
    }
}
Pada actionupdate” ini saya ingin membuat trik baru, supaya seluruh pembahasan kita dapat lebih optimal lagi, trik ini terletak pada flow aplikasi actionupdate” ini, berdasarkan kode diatas, pada action ini terlihat object Author akan dicari terlebih dahulu berdasarkan parameter id yang dilempar dari form, jika object author ditemukan maka setiap properti milik Author akan dirubah nilainya berdasarkan paramater-parameter yang dilempar dari form, kemudian object author tersebut akan coba disimpan, jika penyimpanan object author tersebut berhasil, maka flow aplikasi akan me-redirect ke actionlist” untuk dapat menampilkan perubahan data tersebut, dan jika data tersebut tidak dapat di simpan maka sebuah view akan menampilkan messageUpdate Failed”. Demikian juga jika object author tidak ditemukan, maka sebuah view akan menampilkan messageUpdate Failed, data cannot be found”.
Oleh karena ada sebuah view baru yang kita kehendaki dibuat untuk menampilkan message pada kode-kode actionupdate” di atas, maka sudah seharusnya kita membuat sebuah view baru dengan nama fileupdate.gsp” yang diletakkan ke dalam foldergrails-app/views/customAuthor”, adapun isi dari fileupdate.gsp” tersebut adalah sebagai berikut:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <meta name="layout" content="custom-layout"/>
    <title></title>
</head>

<body>
<p>
    ${msg}
</p>

<p class="text-center">
    <a href="${request.contextPath}/customAuthor/list" class="btn btn-default btn-warning btn-sm">
        <span class="glyphicon glyphicon-arrow-left"></span> Back to List
    </a>
</p>
</body>
</html>
Silahkan simpulkan sendiri isi file tersebut, yang pasti kita menggunakan banyak class milik twitter-bootstrap css framework disana.
Sampai disini pembahasan untuk mekanisme update dalam rangkaian CRUD untuk controller CustomAuthor telah selesai dan silahkan para pembaca lakukan tes terhadap apa-apa yang sudah kita lakukan dari awal tulisan ini sampai selesai. Para pembaca saya bebaskan untuk berkreasi dengan cara mengubah atau memiliki gaya tersendiri dalam berfikir tentang flow aplikasi diatas sesuai dengan kesenangan para pembaca tentunya. Pada artikel selanjutnya kita akan kembali meneruskan pembahasan tentang pembuatan controller CustomBook, oleh karena class domain Book tadi memiliki perbedaan dengan class domain Author ini yaitu adanya relasi antar class domain, maka saya pastinya akan membatasi dalam pembahasan yang tersendiri agar bisa lebih fokus. Sampai disini dulu pembahasan kita ini, sampai berjumpa pada tulisan saya berikutnya. 

Salam dan semoga bermanfaat
Menteng, 22 Februari 2014


Josescalia

No comments: