01 March 2014

Memulai Grails (Bag-6) [Habis]

Pembaca yang budiman, setelah pada tulisan lalu kita membahas secara detail tentang bagaimana pembuatan CRUD untuk class domain Author pada web aplikasi dengan menggunakan Grails Framework, kali ini kita akan melanjutkan pembahasan tersebut dengan melakukan pembuatan custom controller untuk class domain Book. Sengaja saya pisahkan pembuatan controller untuk class domain Book sebab class domain ini memiliki object yang relational dengan class domain Author. Baiklah mari kita langsung saja meneruskan latihan kita.
Pada tulisan sebelumnya kita telah membuat action pada custom controller untuk Book yang berisi list untuk menampilkan data Book pada gsp, sekarang kita akan membuat sebuah action dengan nama "add", dan berikut kode-kode untuk action tersebut:
def add() {
   [model: new Book(), authorList : Author.list(), yearList:initYear()]
}
    
/*create year list*/
def initYear(){
   def yearList = new ArrayList<Integer>();
   for(Integer year=1980; year <= 2030; year ++){
        yearList.add(year);
   }
   return yearList;
}
Pada baris kode di atas kita mendefinisikan dua buah fungsi yang bernama initYear dan add. Fungsi add merupakan action dari controller CustomBookController, sehingga seharusnya dia akan memiliki page gsp sebagai view-nya, sementara fungsi initYear adalah fungsi biasa yang tidak perlu dibuatkan view-nya. Fungsi initYear ini memilki kembalian berupa ArrayList yang berisi collection tahun mulai dari 1980 hingga 2030. Fungsi add atau action add pada kode diatas bisa kita lihat memiliki sebuah variable Book dengan nama model, authorList berisi daftar Author, dan variable yearList yang memanggil sebuah fungsi bernama initYear tadi, semua variable ini nantinya akan ditangkap oleh gsp. Dimana model adalah sebuah instansiasi object Book baru yang kosong, authorList adalah daftar Author yang kita ambil dari database, dan yearList berisi collection tahun yang sudah kita buat pada fungsi initYear sebelumnya.
Kemudian mari kita buat view untuk action add tersebut, buatlah sebuah file dengan nama "add.gsp" dan letakkan dalam folder "grails-app/views/customBook", kita akan tetap menggunakan metode yang sama dengan waktu kita membuat action-action pada controller CustomAuthor yaitu menggunakan mekanisme templating untuk form-nya. Adapun isi dari file tersebut adalah sebagai berikut:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <meta name="layout" content="custom-layout"/>
    <title></title>
</head>

<body>
   <h4>Add New Book</h4>
   <form action="addNew" method="post" style="width:45%">
       %{--include form here--}%
       <g:render template="form"/>
       %{--button action here--}%
       <p>
       <input type="submit" class="btn btn-default btn-info btn-sm" value="Save">
       <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}/customBook/list";
        });
    });
</script>
</body>
</html>
Templating disini dapat terlihat dari kita menggunakan layoutcustom-layout” dan penggunaan form yang kita render pada tengah-tengah body halaman gsp tersebut. Kemudian mari kita buat lagi sebuah file template-nya dengan nama "_form.gsp" dan letakkan pada folder yang sama dengan file "add.gsp" yang kita buat sebelumnya diatas. Adapun isi dari file "_form.gsp" tersebut adalah sebagai berikut:
<table class="table">
    <tr>
        <td><label for="bookTitle">Title</label></td>
        <td>
            <input type="text" class="form-control" id="bookTitle" name="bookTitle" value="${model?.bookTitle}">
        </td>
    </tr>
    <tr>
        <td><label for="authorId">Author</label></td>
        <td>
            <select class="form-control" id="authorId" name="authorId">
                <g:each in="${authorList}" var="author">
                    <option value="${author?.id}"
                            <g:if test="${author.id == model?.author?.id}">selected</g:if>>${author?.authorName}</option>
                </g:each>
            </select>
        </td>
    </tr>
    <tr>
        <td><label for="publishedYear">Year Published</label></td>
        <td>
            <select class="form-control" id="publishedYear" name="publishedYear">
                <g:each in="${yearList}" var="year">
                    <option value="${year ?: 1900}"
                            <g:if test="${year == model?.publishedYear}">selected</g:if>>${year ?: 1900}</option>
                </g:each>
            </select>
        </td>
    </tr>
</table>
Yang harus kita perhatikan pada kode-kode di atas adalah, untuk variable authorList yang dilempar dari customBookController kita tempatkan pada element htmlselect-option” dan disana kita buat sebuah loop (perulangan) untuk menampilkan data author pada isinya serta mengisi value pada setiap tagoption” yang diulang. Demikian halnya dengan variable yearList yang kita lempar dari controller, kita juga melakukan cara yang sama dengan variable authorList yaitu melakukan perulangan dan menempatkannya pada element htmlselect-option”. Dan tidak lupa menyisipkan tag “<g:if>” di dalam masing-masing option tersebut dengan tujuan agar jika ada properti author dan year yang di-set pada class domain Book, maka secara otomatis element html  “select-option” tersebut akan terpilih dengan sendirinya.
Jika sudah selesai, silahkan test apa yang sudah kita buat tadi dengan cara memanggil action tersebut pada URL : http://localhost:8080/grails-book-catalog/customBook/add pada browser favorit anda, dan seharusnya seperti ini kira-kira halaman yang tampil:
Ya, seperti itulah kira-kira tampilan yang bisa kita dapatkan untuk action add ini, coba periksa apakah masing-masing combo box yang ada pada halaman tersebut benar2 berisi data yang kita lempar dari controller CustomBook tadi.
Langkah selanjutnya adalah kita membuat action pada controller CustomBook untuk menangani proses jika tombol save di eksekusi. Sama halnya dengan yang kita buat pada controller Author sebelumnya, oleh karena form "add.gsp" ini akan mengarah pada actionaddNew” seperti yang terlihat pada baris code:
<form action="addNew" method="post" style="width:45%">
Maka kita akan membuat sebuah action pada controller CustomBook dengan nama "addNew" juga, sementara kode-kode untuk action "addNew" tersebut adalah sebagai berikut:
def addNew() {
  def book = new Book()
  book.bookTitle = params.bookTitle
  book.publishedYear = Integer.parseInt(params.publishedYear)
  book.author = Author.get(params.authorId)
  if (!book.save(flush: true)) {
      render(view: "add", model: [book: book])
      return
  }
  redirect(action: "list")
}
Pada kode-kode di atas terlihat kita mendeklarasikan sebuah object Book dengan nama book, dan kita isi masing-masing property object book tersebut dari masing-masing parameter http yang dilempar dari form. Namun oleh karena property author pada class domain Book ini merupakan class domain Author maka kita perlu mengeset properti author tersebut dengan cara mencari dulu ke dalam table Author.
book.author =  Author.get(params.authorId)
Setelah itu object Book kita simpan ke dalam database dan kemudian proses selanjutnya adalah kita redirect ke actionlist”. Sampai disini pekerjaan kita dalam membuat salah satu rangkaian CRUD yaitu insert sudah selesai. Silahkan anda test sendiri apakah semuanya sudah berjalan seperti semestinya.
Kemudian langkah selanjutnya kita akan membuat satu buah action lagi untuk melengkapi rangkaian mekanisme CRUD yaitu delete. Silahkan buat action delete pada controller CustomBook seperti kode-kode dibawah ini:
def delete() {
  def book = Book.get(params.id)

  def map;
  if (book == null) {
     map = [res: "Delete Failed", msg: "Data Not Found"]
  } else {
     try {
        book.delete(flush: true)
        map = [res: "Delete Succeed"]
     } catch (DataIntegrityViolationException dive) {
        map = [res: "Delete Failed", msg: dive.message]
     }
  }
  render map as JSON
}
Jika anda perhatikan, kode diatas sama persis dengan apa yang kita buat sebelumnya pada controller CustomAuthor, hanya saja disini kita bedakan object yang mau kita delete yaitu Book, dan kita juga menggunakan mekanisme ajax untuk action ini. Untuk action delete ini, pertanyaannya adalah halaman mana yang akan mengeksekusi action ini ? Ya, kita akan mengeksekusi action ini pada halaman "list" seperti pada gambar berikut:
Pada gambar tersebut terlihat bahwa sebuah alert akan muncul ketika kita mengeksekusi tombol Del pada "list" tersebut. Alert ini berisi pesan yang diambil dari hasil render data JSON pada actiondelete” pada controller CustomBook yang kita buat tadi, kemudian data dengan JSON format yang di-render sebagai JSON tadi diolah oleh javascript menjadi sebuah alert seperti gambar di atas, mari kita lihat kode-kode javascript tersebut:
<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>
Nah kode-kode javascript ini kita tempelkan ke dalam filelist.gsp” yang ada pada foldergrails-app/views/customBook”. Kemudian silahkan ada lakukan kembali tes untuk action tersebut dan pastikan segalanya bisa berjalan dengan baik. Dan akhirnya sekarang kita sudah juga menyelesaikan satu lagi metode pada rangkaian operasi CRUD yaitu “delete”.
Untuk rangkaian CRUD yang terakhir yang akan kita buat pada controller CustomBook tersebut adalah mekanisme “update”. Langkah pertama yang kita buat adalah membuat sebuah action dengan nama "edit" pada controller CustomBook, action "edit" ini secara konsep sebenarnya adalah menampilkan sebuah data yang diambil dari table Book berdasarkan paramater Id yang di-supply oleh http request tersebut. Berikut ini kode-kode actionedit” pada controller CustomBook:
def edit(Long id) {
   def book = Book.get(id)
   if (book == null) {
      redirect(action: "list")
      return
   }
   [model: book, authorList: Author.list(), yearList: initYear()]
}
Perhatikan kode di atas, sebuah object Book akan diambil dari dalam table Book menggunakan parameter id yang dilempar dari http request, kemudian jika object Book tidak ditemukan dalam database, proses akan me-redirect untuk menampilkan action "list", dan sebaliknya jika object Book ditemukan, maka proses akan dilanjutkan dengan menampilkan form "edit.gsp" yang di isikan variablemodel”, ”authorList”, dan collection year yang diambil dari fungsi “initYear()”.
Oleh karena kita sudah membuat form template "_form.gsp" pada mekanisme templating controller CustomBook ini, maka fileedit.gsp” yang kita buat pun cukup dengan kode-kode seperti dibawah ini:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <meta name="layout" content="custom-layout">
    <title></title>
</head>

<body>
<form method="post" action="update" style="width:45%">
    <input type="hidden" id="id" name="id" value="${model?.id}">
    <g:render template="form"/>
    <p>
        <input type="submit" class="btn btn-default btn-info" value="Update">
        <input type="button" class="btn btn-default btn-info btnCancel" value="Cancel">
    </p>
</form>
<script type="text/javascript">
    $(document).ready(function () {
        $(".btnCancel").click(function () {
            window.location = "${request.contextPath}/customBook/list";
        });
    })
</script>
</body>
</html>
Anda bisa lihat kita memaksimalkan mekanisme templating pada formedit.gsp” tersebut, dengan cara menggunakan kembali template_form.gsp” dalam tag body pada halaman "edit.gsp" tersebut. Kemudian untuk meng-handle http request form edit ini, kita akan membuat action dengan nama “update” pada controller CustomBook, dibawah ini adalah kode-kode yang bisa kita buat:
def update() {
    def book = Book.get(params.id);
    if (book != null) {
        book.bookTitle = params.bookTitle
        book.author = Author.get(params.authorId)
        book.publishedYear = Integer.parseInt(params.publishedYear)

        if (book.save(flush: true)) {
            redirect(action: "list")
        } else {
            [msg: "Update Failed"]
        }
    }else{
        [msg:"Update Failed, data cannot be found"]
    }
}
Seperti actionsave” kita melihat untuk penge-set-an properti author pada object book yang kita buat, kita harus mencari dulu object Author dengan parameter id dari Author tersebut dalam database. Kemudian jika penyimpanan data berhasil dilakukan maka halaman akan di-redirect menuju actionlist” dan jika penyimpanan gagal, atau data tidak ditemukan maka viewupdate” akan ditampilkan. Adapun isi dari fileupdate.gsp” ini 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}/customBook/list" class="btn btn-default btn-warning btn-sm">
        <span class="glyphicon glyphicon-arrow-left"></span> Back to List
    </a>
</p>
</body>
</html>
Ya, sesederhana itulah halaman "update.gsp", yang hanya berisi informasi apakah data gagal di-update atau data tidak ditemukan dalam database, dan halaman "update.gsp" ini dilengkapi dengan tombol untuk bisa kembali ke actionlist”.
Para pembaca yang budiman, selesai sudah rangkaian CRUD yang kita buat untuk controller CustomBook ini, semua fungsionalitas yang kita buat seharusnya bisa befungsi secara baik. Untuk seluruh source-code yang kita buat dari mulai tulisan bagian ke-2 dapat anda clone dari sini. Anda bisa mengembangkannya kembali sesuai dengan yang anda inginkan. Saya berharap tutorial ini bisa membantu para pembaca memahami Grails Framework secara konseptual. Pada prakteknya saya pribadi menjadikan Grails Framework sebagai salah satu development stack pembuatan web aplikasi dengan java khususnya untuk pembuatan User Interface, sebab saya merasa kemudahan yang ditawarkan Grails Framework dari sisi Java sudah cukup buat saya, dan saya bisa fokus terhadap tampilan-tampilan setiap halaman web aplikasi yang saya buat dengan cara mengintegrasikannya dengan javascript framework, css framework dan lain-lain.

Sebelum kita akhiri, masih ada satu task dari 6 urutan rencana kita memahami Grails Framework ini, yaitu deployment Grails Framework. Distribusi aplikasi yang kita buat dengan  menggunakan Grails Framework dapat kita kemas dalam sebuah mekanisme standar distribusi web aplikasi java, yaitu membuat war-file. Dengan cara seperti ini web aplikasi yang menggunakan Grails Framework dapat di-deploy ke dalam application server dengan mudah layaknya web aplikasi java yang dibuat dengan menggunakan framework lain. Adapun perintah grails untuk membuat distribusi war ini adalah “grails war”, perintah ini akan menghasilkan sebuah war file yang terletak dalam folder target seperti gambar berikut ini:
Itulah perintah standar dalam Grails Framework untuk melakukan pembuatan distribusi web aplikasi, adapun opsi-opsi lain yang bisa disertakan dalam pembuatan war-file ini bisa anda lihat di website resmi Grails pada section ini
Dan demikianlah seluruh rangkaian tutorial memulai Grails Framewok telah kita selesaikan.

Semoga bermanfaat
Menteng, 2 Maret 2014



Josescalia

No comments: