iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0
Modern Web

使用 Kotlin 快速開發 Web 程式 -- Vaadin系列 第 7

自訂 Vaadin 組件 / Grid 擴充功能 -- day07

重複使用程式碼

Vaadin 自訂 Component

各位發現了嗎?在寫完CRUD後,打開CreateStudentView.ktEditStudent.kt兩相對照,新增和編緝的畫面幾乎一樣,能不能共用程式碼?怎麼共用?可不可以寫成日後可重複使用的組件?

  • VoK 提供了非常簡單自訂組件的方法。開新檔案StudentEditorComponent.kt,將EditStudent.kt複制進來後,給student一個set()方法,讓這個component可設定student屬性
package com.example.vok

import com.github.mvysny.karibudsl.v10.*
import com.vaadin.flow.component.HasComponents

class StudentEditorComponent: KComposite() {
    private val binder = beanValidationBinder<Student>()
    var student: Student? = null
        set(value) {
            field = value
            value?.let { binder.readBean(value) }
        }
    private val root = ui {
        verticalLayout {
            isMargin = false
            textField("姓名 : "){
                bind(binder).bind(Student::name)
            }
            comboBox<Gender>("性別 : "){
                setItems(*Gender.values())
                bind(binder).bind(Student::gender)
            }
            datePicker("生日 : "){
                bind(binder).bind(Student::birthday)
            }
            numberField("身高"){
                bind(binder).bind(Student::height)
            }
            numberField("體重"){
                bind(binder).bind(Student::weight)
            }
            button("儲存"){
                onLeftClick {
                    val student = student!!
                    if (binder.validate().isOk && binder.writeBeanIfValid(student)){
                        student.save()
                        StudentView.navigateTo(student.id!!)
                    }
                }
            }
            routerLink(null, "返回", AllStudentsView::class)
        }
    }
}

fun HasComponents.studentEditorComponent(block: StudentEditorComponent.()->Unit = {}) = init(StudentEditorComponent(), block)

最後一行,HasComponents.studentEditorComponent() Extension function,參數為要加入組件的程式碼區段,再將組件回傳。

  • 畫面準備好了,但要怎麼把這個組件組合到其他畫面中呢?請打開CreateStudentView.kt,移除重複的程式碼,由於此畫面是新增學生資料,所以指定 student 屬性為 Student(),改好後程式如下 :
class CreateStudentView: KComposite() {
    private lateinit var editorComponent: StudentEditorComponent
    private val root = ui {
        verticalLayout {
            h1("新增學生資料")
            editorComponent = studentEditorComponent {
                student = Student()
            }
        }
    }
}

看起來是不是變得很簡潔?

  • 接著來看修改,請打開EditStudent.kt,這裡也一樣移除重複的程式碼後加上StudentEditorComponent
    private lateinit var studentEditorComponent: StudentEditorComponent
    private val root = ui {
        verticalLayout {
            h1("學生資料修改")
            studentEditorComponent = studentEditorComponent()
        }
    }
  • 設定student屬性
    override fun setParameter(event: BeforeEvent?, studentId: Long?) {
        studentEditorComponent.student = Student.getById(studentId!!)
    }

這段原本重新取讀 bean 讓 grid 自動更新的程式碼,改為設定組件的student屬性值。

執行後的結果和原本一樣一樣,畫面組件化後程式碼不但可讀性更高,且組件和原本程式碼脫勾相互不受影響。

擴充Grid功能,加上附 listener 的圖型按鍵

我們先來看看,如果今天想要在grid裡使用帶clicklistener的圖型按鍵需要做哪些事。開啟AllStudentsView.kt

    addColumn(ComponentRenderer<Button, Student>{ student: Student ->
        val button = Button(VaadinIcon.EYE.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick { StudentView.navigateTo(student.id!!)}
        button
    }).setWidth("50px").isExpand = false
                
    1. 首先要在column裡加入Renderer,因為button是component,這裡使用 ComponentRenderer
    1. 再來產制一個圖形Button,並設置圖示大小等圖形顯示樣式,
    1. 在 button 加上 clickListener 事件

加上三個帶事件的 button column 後,就會變成這樣

    addColumn(ComponentRenderer<Button, Student>{ student: Student ->
        val button = Button(VaadinIcon.EYE.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick { StudentView.navigateTo(student.id!!)}
        button
    }).setWidth("50px").isExpand = false
    addColumn(ComponentRenderer<Button, Student>{ student: Student ->
        val button = Button(VaadinIcon.EDIT.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick { EditStudent.navigateTo(student.id!!)}
        button
    }).setWidth("50px").isExpand = false
    addColumn(ComponentRenderer<Button, Student>{ student: Student ->
        val button = Button(VaadinIcon.TRASH.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick {
            confirmDialog(text = "是否確定刪除${student.name}的資料?") {
            student.delete()
            this.refresh()
        }}
        button
    }).setWidth("50px").isExpand = false

程式碼看起來不僅兀長且不易閱讀。如果 Grid 能夠直接提供一個方法,只要傳進icon、column key、clickListener三個參數,就能回給我一個帶有listener的圖形按鍵,好像還不錯。

fun <T> Grid<T>.addButtonColumn(icon: IconFactory, key: String, clickListener: (T) -> Unit): Grid.Column<T> {
    val renderer = ComponentRenderer<Button, T> { data: T ->
        val button = Button(icon.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick { clickListener(data) }
        button
    }
    val column: Grid.Column<T> = addColumn(renderer).setKey(key).setWidth("50px")
    column.isExpand = false
    return column
}

grid{} 加上圖形按鍵 column 的程式碼改為

        addButtonColumn(VaadinIcon.EYE, "view") { StudentView.navigateTo(it.id!!) }
        addButtonColumn(VaadinIcon.EDIT, "edit") { EditStudent.navigateTo(it.id!!) }
        addButtonColumn(VaadinIcon.TRASH, "delete"){
            confirmDialog(text = "是否確定刪除${it.name}的資料?") {
                it.delete()
                this.refresh()
            }
        }

執行結果如下:
https://ithelp.ithome.com.tw/upload/images/20210922/20138680uReelp97JZ.png

經過Refactor後,整個程式是不是看起來既乾淨又簡捷?
明天開始進入第二張資料表,就要講到資料庫關聯囉~

本日程式已上傳 GitHub


上一篇
CRUD的UD / ICON / confirmDialog - day06
下一篇
vok-orm 關聯性資料的新增/查詢 (上篇) -- d08
系列文
使用 Kotlin 快速開發 Web 程式 -- Vaadin30

尚未有邦友留言

立即登入留言