Memakai Glazed Lists Di Griffon


Pada tulisan Operasi CRUD Database Pada Aplikasi Desktop Dengan Griffon, saya membuat sebuah aplikasi desktop berbasis Griffon dengan menggunakan DefaultTableModel bawaan Swing.   Disitu, saya menemukan beberapa kelemahan, terutama kesulitan melakukan binding otomatis untuk JTable yang ada.   Oleh sebab itu, pada tulisan ini, saya akan mencoba menggunakan Glazed Lists dalam mengatasi permasalahan tersebut.   Saya akan meng-install plugin Glazed Lists untuk Griffon dengan memberikan perintah seperti berikut ini:

griffon install-plugin glazedlists

Saya akan memodifikasi kode program yang telah selesai di tulisan Operasi CRUD Database Pada Aplikasi Desktop Dengan Griffon. MahasiswaModel yang sebelumnya memiliki listMahasiswa dengan tipe java.util.List akan saya ganti menjadi sebuah EventList seperti yang terlihat pada kode program berikut ini:

import ca.odell.glazedlists.*

class MahasiswaModel {
    @Bindable String nim
    @Bindable String nama
    @Bindable Integer angkatan

    EventList listMahasiswa = new BasicEventList()
    EventSelectionModel eventSelectionModel
}

Kode program di model sekarang terlihat lebih sederhana tanpa banyak operasi logika.  Hal ini sesuai dengan fungsi sebuah model yaitu mewakili data.

Berikutnya, saya melakukan perubahan di view sehingga terlihat seperti berikut ini:

import ca.odell.glazedlists.swing.EventSelectionModel
import net.miginfocom.swing.MigLayout

// Helper untuk mengambil nilai kolom dari baris terpilih
def getSelectedValue = { kolom ->
    EventSelectionModel selection = model.eventSelectionModel
    if (!selection.isSelectionEmpty()) {
        selection.getSelected()[0][kolom]
    } else {
        null
    }
}

application(title: 'mahasiswa',
  preferredSize: [320, 440],
  pack: true,
  locationByPlatform: true,
  iconImage:   imageIcon('/griffon-icon-48x48.png').image) {

    borderLayout()

    scrollPane(constraints: CENTER) {
        table (rowSelectionAllowed: true, id: 'table') {
            eventTableModel(source: model.listMahasiswa, format:
                    defaultTableFormat(columnNames: ['Nim', 'Nama', 'Angkatan']))
            model.eventSelectionModel = installEventSelectionModel(source: model.listMahasiswa)
        }
    }

    panel(layout: new MigLayout('', '[right][left,grow]',''), constraints: PAGE_END) {
        label('NIM:')
        textField(id: 'txtNIM', columns: 15, text: bind(target: model, targetProperty: 'nim'), constraints: 'wrap')
        label('Nama:')
        textField(id: 'txtNama', text: bind(target: model, targetProperty: 'nama'), constraints: 'growx,wrap')
        label('Angkatan:')
        textField(id: 'txtAngkatan', text: bind(target: model, targetProperty: 'angkatan', converter: { v ->
            try { Integer.parseInt(v) } catch (NumberFormatException) { 0 }
        }), columns: 6, constraints: 'wrap')

        bind(source: model.eventSelectionModel, sourceEvent: 'valueChanged',
             sourceValue: {getSelectedValue("nim")}, target: txtNIM, targetProperty: 'text')
        bind(source: model.eventSelectionModel, sourceEvent: 'valueChanged',
             sourceValue: {getSelectedValue("nama")}, target: txtNama, targetProperty: 'text')
        bind(source: model.eventSelectionModel, sourceEvent: 'valueChanged',
             sourceValue: {getSelectedValue("angkatan")}, target: txtAngkatan, targetProperty: 'text')

        panel(constraints: 'span, growx, wrap') {
            flowLayout()
            button("Simpan", actionPerformed: controller.simpan)
            button("Hapus", enabled: bind (source: model.eventSelectionModel, 
                 sourceEvent: 'valueChanged', sourceValue: {!model.eventSelectionModel.isSelectionEmpty()}), 
                 actionPerformed: controller.hapus)
        }
    }

}

Kode program yang membuat JTable sekarang terlihat lebih sederhana.  Komunikasi antara view dan model sekarang sudah memakai binding melalui event valueChanged.   Event tersebut terjadi bila pengguna memilih sebuah baris di tabel.   Ini berarti bila pengguna memilih salah satu baris di tabel, maka view dan model akan di-sinkronisasi secara otomatis tanpa campur tangan controller.

Berikutnya, saya mengubah kode program controller menjadi seperti yang terlihat berikut ini:

import groovy.sql.DataSet
import groovy.sql.Sql

class MahasiswaController {

    def model

    def onStartupEnd = { app ->
        // kini membaca seluruh data dari tabel hanya dilakukan sekali saja, pada saat
        // aplikasi dijalankan
        withSql { dataSourceName, Sql sql ->
            sql.eachRow("SELECT nim, nama, angkatan FROM mahasiswa") {
                Map baris = [nim: it.nim, nama: it.nama, angkatan: it.angkatan]
                edt {
                    model.listMahasiswa << baris
                }
            }
        }
    }

    def simpan = {
         withSql { dataSourceName, Sql sql ->
            DataSet mahasiswa = sql.dataSet("mahasiswa")
            if (sql.firstRow("SELECT nim FROM mahasiswa WHERE nim = ${model.nim}") != null) {
                // sudah ada NIM yang sama, maka lakukan update.
                mahasiswa.executeUpdate("UPDATE mahasiswa SET nama = ${model.nama}, angkatan = ${model.angkatan} WHERE nim = ${model.nim}")

         // update baris di model
                edt {
                    int index = model.listMahasiswa.findIndexOf{it['nim']==model.nim}
                    model.listMahasiswa[index] += [nama: model.nama, angkatan: model.angkatan]
                }

            } else {
                // belum ada NIM, maka tambahkan.
                mahasiswa.add(nim: model.nim, nama: model.nama, angkatan: model.angkatan)

 // tambahkan baris baru ke model
                edt { model.listMahasiswa << [nim: model.nim, nama: model.nama, angkatan: model.angkatan] }                      }
         }
    }

    def hapus = {
        withSql { dataSourceName, Sql sql ->
           sql.executeUpdate("DELETE FROM mahasiswa WHERE nim = ${model.nim}")
        }

        // menghapus baris dari model
        edt {
            int index = model.listMahasiswa.findIndexOf{it['nim']==model.nim}
            model.listMahasiswa.remove(index)
        }
    }
}

Controller sekarang tidak perlu lagi memperbaharui view secara manual, karena view melakukan binding ke model melalui event.   Dengan demikian, tidak ada referensi ke variabel view lagi di controller.    Hal ini membuat pemisahan tugas secara rapi di controller.

Perubahan lain pada controller adalah setiap operasi CRUD tidak akan men-select seluruh data dari database seusai melakukan operasi SQL.   Mereka kini cukup mengubah model.listMahasiswa; nantinya, tampilan JTable akan secara otomatis berubah mengikuti  isi model.listMahasiswa yang terbaru.

Kelemahan controller yang saya buat adalah masih bergabungnya operasi SQL (yang seharusnya pada domain model) dan operasi presentation layer (pada model dari MVC).   Agar lebih rapi, saya seharusnya memisahkan operasi SQL ke dalam service layer.   Selain itu, fitur yang saat ini masih kurang dari Griffon adalah tidak adanya dukungan domain model + persistence layer yang mirip sepeti GORM di Grails.

Sebagai penutup, saya akan menambahkan fitur filtering pada JTable. Pencarian disini hanya men-filter isi tabel tanpa melakukan query ke database. Untuk itu saya mengubah view menjadi seperti berikut ini:

...
panel(constraints: PAGE_START) {
   flowLayout(alignment: FlowLayout.RIGHT)
   label("Cari: ")
   textField(id: "txtPencarian", columns: 10)
}

scrollPane(constraints: CENTER) {
   table (rowSelectionAllowed: true, id: 'table') {
      eventTableModel(source: new FilterList(model.listMahasiswa,
          new TextComponentMatcherEditor(txtPencarian, new StringTextFilterator())),
          format: defaultTableFormat(columnNames: ['Nim', 'Nama', 'Angkatan']))
      model.eventSelectionModel = installEventSelectionModel(source: model.listMahasiswa)
   }
}
...

Karena filtering dilakukan berdasarkan isi JTable di view dan tidak melakukan query ke database, maka hanya kode program view yang perlu diubah.   Saya menggunakan TextComponentMatcherEditor dari Glazed Lists yang dihubungkan ke txtPencarian. Begitu pengguna mengetik pada txtPencarian, maka isi tabel akan di-filter berdasarkan apa yang diketik oleh pengguna, seperti yang terlihat pada gambar berikut ini:

Filtering Dengan Glazed Lists

Filtering Dengan Glazed Lists

Perihal Solid Snake
I'm nothing...

13 Responses to Memakai Glazed Lists Di Griffon

  1. Komang Hendra Santosa mengatakan:

    Mas, saya jalanin kok muncul error gini mas:

    [griffonc] Compiling 11 source files to C:\Users\Krsna\.griffon\1.2.0\projects\mahasiswa\classes\main
    [griffonc] org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
    [griffonc] G:\latihan\mahasiswa\griffon-app\models\mahasiswa\MahasiswaModel.groovy: 17: unable to resolve class BasicEventList
    [griffonc] @ line 17, column 31.
    [griffonc] EventList listMahasiswa = new BasicEventList()
    [griffonc] ^
    [griffonc]
    [griffonc] 1 error
    Compilation error: Compilation Failed

    Process finished with exit code 1

    • Solid Snake mengatakan:

      Glazed Lists bukan bawaan Griffon. Apakah kamu sudah meng-install plugin Glazed LIsts? Bila memakai IntelliJ IDEA, tekan Ctrl+Alt+G, kemudian berikan perintah “install-plugin glazedlists”. Pastikan komputermu terhubung ke Internet karena Griffon akan men-download JAR yang diperlukan. Cara lain, masuk ke command prompt/console, pindah ke direktori proyek dengan cd, kemudian berikan perintah berikut ini “griffon install-plugin glazedlists” di console.

  2. Komang Hendra Santosa mengatakan:

    Saya sudah menginstall pluginnya mas, tapi itulah error yang moncul ketika menjalankan programnya..glazedlist yang saya pakai yang versi terbaru dari repo griffonnya..kenapa ya kira2 mas? apakah syntax importnya yg salah?saya sudah mengikuti contoh yang mas berikan, tapi hasilnya seperti diatas..

    • Solid Snake mengatakan:

      Lokasi BasicEventList yang benar terletak di package ca.odell.glazedlists. Pada contoh kode program yang saya buat, saya men-import seluruh class yang ada di package tersebut dengan perintah import ca.odell.galzedlists.*
      Bila class BasicEventList masih juga tidak ditemukan, ini berarti JAR yang mengandung class tersebut tidak dipakai/tidak ditemukan.

      1) Pastikan bahwa pada saat memberikan “griffon install-plugin glazedlists”, proses instalasi berlangsung sukses. Tidak adak pesan “-= MISSING =”, melainkan baris terakhir harus berupa “Installed plugin ‘glazedlists-1.1.0’ in C:\Users\[nama]\.griffon\1.2.0\projects\[namaproyek]\plugins/glazedlists-1.1.0″

      2) Cek langsung ke lokasi yang disebutkan di nomor 1, masuk ke folder ‘dist’, pastikan terdapat file ‘griffon-glazedlists-1.1.0-runtime.jar’.

      3) Pastikan file JAR untuk GlazedLists telah ter-download. Buka folder ‘C:\Users\[nama]\.ivy2\cache\net.java.dev.glazedlists\glazedlists_java15\jars” dan pastikan file “glazedlists_java15-1.x.x.jar” terdapat ditempat tersebut.

      4) Bila semuanya OK sampai disini, hapus folder proyek dari ‘C:\Users\[nama]\.griffon\1.2.0\projects\”. Lalu buat sebuah proyek baru dengan nama yg sama dan mulai dari awal lagi.

  3. Komang Hendra Santosa mengatakan:

    Masih sama mas, saya sudah lakukan sesuai petunjuknya tapi tetap sama, kenapa ya kira-kira?Apakah classnya tidak ditemukan ya?

  4. Komang Hendra Santosa mengatakan:

    Setelah saya search2 di google ternyata memang classnya yg tidak ditemukan, walaupun sudah mengimport semua class di glazedlists, tp beberapa class tidak ditemukan, jadi saya tambahkan import sbg berikut:

    MahasiswaModel.groovy
    import ca.odell.glazedlists.EventList
    import ca.odell.glazedlists.BasicEventList
    import ca.odell.glazedlists.swing.EventSelectionModel

    MahasiswaView.groovy
    import ca.odell.glazedlists.*
    import ca.odell.glazedlists.impl.filter.*
    import ca.odell.glazedlists.swing.*

    dan sekarang program sudah berjalan dengan baik🙂

  5. Solid Snake mengatakan:

    Tips untuk menghindari kesalahan ini adalah memakai bantuan IDE untuk menghasilkan import secara otomatis. Seandainya referensi ke JAR sudah ditambahkan ke proyek (lokasi JAR ada di C:\Users\[nama]\.ivy2\cache\net.java.dev.glazedlists\glazedlists_java15\jars) , maka dengan menekan tombol Ctrl+Alt+O di IntelliJ IDEA (optimize import) akan menghasilkan import secara otomatis, begitu juga dengan Ctrl+Shift+O di Eclipse..

    Selain itu, bila pada saat mengetik class dengan Ctrl+Space dan memilih nama class, maka IDE secara otomatis akan menambahkan import untuk class bersangkutan.

  6. Komang Hendra Santosa mengatakan:

    Maksudnya “referensi ke JAR sudah ditambahkan ke proyek” ini apa mas?kita copy JARnya ke griffon\lib ya mas?Maaf saya kurang paham..

    • Solid Snake mengatakan:

      Bila kamu mengetik ‘Basic’ kemudian menekan tombol Ctrl+Space di IDE dan tidak ada pilihan class ‘BasicEventList’, itu berarti IDE-mu belum mengenali adanya class tersebut. Biasanya IDE hanya mengenali class bawaan Java.

      Agar IDE bisa mengenali class-class tambahan yang ada di JAR eksternal, maka kamu perlu memberitahu lokasi seluruh JAR yang kamu pakai di proyek. Nantinya IDE akan men-scan seluruh class yang ada di JAR, sehingga sewaktu Ctrl+Space akan muncul pilihan class di JAR tersebut. IDE juga melakukan import secara otomatis.

      Bila memakai Eclipse: Klik kanan nama proyek, pilih Properties. Pilih Java Build Path, Libraries. Klik pada tombol Add External JARs.. dan pilih lokasi JAR yang dipakai oleh proyek.

      Bila memakai NetBeans: Klik kanan nama proyek, pilih Properties. Pilih Libraries, klik pada tombol Add JAR/Folder dan masukkan lokasi file JAR yang dipakai oleh proyek.

      Bila memakai IntelliJ IDEA: Klik kanan nama proyek, pilih Open Module Settings. Pilih Dependencies, dan klik pada tombol +, pilih Jars or directories… dan pilhi lokasi file JAR yang dipakai oleh proyek.

      Sebagai contoh, saat kamu memasukkan kode program di atas, IDE-mu secara otomatis akan menyarankan bahwa class EventSelectionModel belum memiliki import. Bila saran otomatis tidak ada dan saat menjalankan program terdapat kesalahan import, maka kamu bisa memilih menu organize import supaya IDE membuat statement import secara otomatis.

      Kebanyakan developer Java men-setup proyeknya agar nyaman dalam membuat kode program. Saya sendiri tidak hanya menambahkan JAR, tetapi sebisa mungkin menambahkan JAR source code dan javadoc (dokumentasi) sehingga IDE bisa menampilkan deskripsi sebuah method beserta parameter-nya saat saya mengetik.

  7. Komang Hendra Santosa mengatakan:

    Wah, saya baru tahu nih ternyata..
    Makasi banget ya mas, nah kalau begini kan lebih asik belajarnya, hehe.. Karna udah ada docnya jadi ga repot deh🙂

    O ya, mas..ada tutorial buat laporan ga di griffon?

    • Solid Snake mengatakan:

      Untuk membuat laporan tidak akan berbeda jauh dari penggunaannya di Swing.. Griffon menyediakan node widget(),container(), noparent(), dsb untuk komponen yang bukan bawaan Swing. Jika reporting engine yang kamu pakai menyediakan Swing viewer (misalnya Jasper Reports), maka kamu bisa meletakkan viewer di dalam node tersebut.

  8. Komang Hendra Santosa mengatakan:

    Mas, sekarang kan saya udah coba pake jasper report, tp ada muncul error kya gini mas:

    2013-04-02 11:04:47,378 [pool-2-thread-1] ERROR griffon.util.GriffonExceptionHandler – Uncaught Exception
    org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.NoClassDefFoundError: com/lowagie/text/SplitCharacter
    at org.codehaus.griffon.runtime.util.AbstractUIThreadHandler$1.run(AbstractUIThreadHandler.java:41)
    Caused by: java.lang.NoClassDefFoundError: com/lowagie/text/SplitCharacter
    at net.sf.jasperreports.engine.JasperExportManager.exportToPdfFile(JasperExportManager.java:145)
    at net.sf.jasperreports.engine.JasperExportManager.exportReportToPdfFile(JasperExportManager.java:497)
    at net.sf.jasperreports.engine.JasperExportManager$exportReportToPdfFile.call(Unknown Source)
    at report.ReportController$_closure1_closure2.doCall(ReportController.groovy:29)
    at report.ReportController$_closure1_closure2.doCall(ReportController.groovy)
    … 1 more
    Caused by: java.lang.ClassNotFoundException: com.lowagie.text.SplitCharacter
    … 6 more

    saya dapet sourcenya di http://www.iaeronz.com/?p=562

    settingan report.jrxml saya seperti ini: (saya taruh filenya di staging folder)

    kira-kira kenapa ya?
    bisa dibantu mas?

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: