Skip to content

Commit

Permalink
Merge pull request #76 from vlipovetskii/master
Browse files Browse the repository at this point in the history
Add DSL to build grid columns with LitRenderer
  • Loading branch information
mvysny authored Jan 17, 2025
2 parents d1ef7e2 + cac1b9e commit d0cf321
Show file tree
Hide file tree
Showing 10 changed files with 686 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,27 @@
package com.vaadin.starter.beveragebuddy.ui.categories

import com.github.mvysny.karibudsl.v10.*
import com.github.mvysny.kaributools.ModifierKey.*
import com.github.mvysny.kaributools.ModifierKey.Alt
import com.github.mvysny.kaributools.addShortcut
import com.vaadin.flow.component.Key.*
import com.vaadin.flow.component.Key.KEY_E
import com.vaadin.flow.component.button.Button
import com.vaadin.flow.component.button.ButtonVariant
import com.vaadin.flow.component.grid.Grid
import com.vaadin.flow.component.grid.GridVariant
import com.vaadin.flow.component.grid.contextmenu.GridContextMenu
import com.vaadin.flow.component.html.H3
import com.vaadin.flow.component.icon.Icon
import com.vaadin.flow.component.icon.VaadinIcon
import com.vaadin.flow.component.notification.Notification
import com.vaadin.flow.data.renderer.ComponentRenderer
import com.vaadin.flow.data.renderer.Renderer
import com.vaadin.flow.router.PageTitle
import com.vaadin.flow.router.Route
import com.vaadin.starter.beveragebuddy.backend.Category
import com.vaadin.starter.beveragebuddy.backend.CategoryService
import com.vaadin.starter.beveragebuddy.backend.ReviewService
import com.vaadin.starter.beveragebuddy.ui.*
import com.vaadin.starter.beveragebuddy.ui.MainLayout
import com.vaadin.starter.beveragebuddy.ui.Toolbar
import com.vaadin.starter.beveragebuddy.ui.toolbarView

/**
* Displays the list of available categories, with a search filter as well as
Expand All @@ -47,6 +49,7 @@ class CategoriesList : KComposite() {
private lateinit var header: H3
private lateinit var toolbar: Toolbar
private lateinit var grid: Grid<Category>

// can't retrieve GridContextMenu from Grid: https://github.com/vaadin/vaadin-grid-flow/issues/523
lateinit var gridContextMenu: GridContextMenu<Category>
private set
Expand All @@ -55,6 +58,7 @@ class CategoriesList : KComposite() {
{ category -> saveCategory(category) },
{ deleteCategory(it) })

@Suppress("unused")
private val root = ui {
verticalLayout(false) {
content { align(stretch, top) }
Expand All @@ -76,6 +80,41 @@ class CategoriesList : KComposite() {
flexGrow = 0; key = "edit"
}

/**
* *NOTE:* Using [ComponentRenderer] is not as efficient as the
* built-in renderers or using `LitRenderer`.
*
* *NOTE:* Using [Renderer] (built-in renderers or `LitRenderer`) is more efficient
* than [ComponentRenderer]
*/
column(buildLitRenderer<Category> {

/*
val onButtonClick = function("onButtonClick") { category ->
deleteCategory(category)
}
*/
// OR
val onButtonClick by function { category ->
deleteCategory(category)
}

templateExpression {

button(
cssClass("category__edit"),
themeVariant(ButtonVariant.LUMO_TERTIARY),
click(onButtonClick)
) {
icon(icon(VaadinIcon.TRASH.create()))
+"Delete"
}
}

}) {
flexGrow = 0; key = "edit-lit"
}

gridContextMenu = gridContextMenu {
item("New", { editorDialog.createNew() })
item("Edit (Alt+E)", { cat -> if (cat != null) edit(cat) })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class CategoriesListTest : AbstractAppTest() {
CategoryService.saveCategory(cat)
UI.getCurrent().page.reload()
val grid = _get<Grid<Category>>()
grid.expectRow(0, "Beers", "0", "Button[text='Edit', icon='vaadin:edit', @class='category__edit', @theme='tertiary']")
grid.expectRow(0, "Beers", "0", "Button[text='Edit', icon='vaadin:edit', @class='category__edit', @theme='tertiary']", " Delete ")
grid._clickRenderer(0, "edit")

// make sure that the "Edit Category" dialog is opened
Expand All @@ -66,7 +66,7 @@ class CategoriesListTest : AbstractAppTest() {
CategoryService.saveCategory(cat)
UI.getCurrent().page.reload()
val grid = _get<Grid<Category>>()
grid.expectRow(0, "Beers", "0", "Button[text='Edit', icon='vaadin:edit', @class='category__edit', @theme='tertiary']")
grid.expectRow(0, "Beers", "0", "Button[text='Edit', icon='vaadin:edit', @class='category__edit', @theme='tertiary']", " Delete ")
_get<CategoriesList>().gridContextMenu._clickItemWithCaption("Edit (Alt+E)", cat)

// make sure that the "Edit Category" dialog is opened
Expand All @@ -79,7 +79,7 @@ class CategoriesListTest : AbstractAppTest() {
CategoryService.saveCategory(cat)
UI.getCurrent().page.reload()
val grid = _get<Grid<Category>>()
grid.expectRow(0, "Beers", "0", "Button[text='Edit', icon='vaadin:edit', @class='category__edit', @theme='tertiary']")
grid.expectRow(0, "Beers", "0", "Button[text='Edit', icon='vaadin:edit', @class='category__edit', @theme='tertiary']", " Delete ")
_get<CategoriesList>().gridContextMenu._clickItemWithCaption("Delete", cat)
expectList() { CategoryService.findAll() }
_get<Grid<Category>>().expectRows(0)
Expand Down
28 changes: 28 additions & 0 deletions karibu-dsl/src/main/kotlin/com/github/mvysny/karibudsl/v10/Grid.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.mvysny.karibudsl.v10

import com.github.mvysny.kaributools.addColumnFor
import com.github.mvysny.kaributools.addHierarchyColumnFor
import com.github.mvysny.kaributools.getColumnBy
import com.vaadin.flow.component.Component
import com.vaadin.flow.component.HasComponents
import com.vaadin.flow.component.grid.Grid
Expand Down Expand Up @@ -257,6 +258,33 @@ public fun <T, V : Component?> (@VaadinDsl Grid<T>).componentColumn(
return column
}

/**
* Adds a new column that shows components using [Renderer].
*
* This is a shorthand for [Grid.addColumn] with a [Renderer].
*
* *NOTE:* Using [Renderer] (built-in renderers or `LitRenderer`) is more efficient
* than [ComponentRenderer]
*
* Example of use:
* ```kotlin
* column(buildLitRenderer<Category> { ... }) {
* flexGrow = 0; key = "edit"
* }
* ```
* @param renderer a (built-in renderer or `LitRenderer`) used to create the grid cell structure
* @return the new column
*/
@VaadinDsl
public fun <T> (@VaadinDsl Grid<T>).column(
renderer: Renderer<T>,
block: (@VaadinDsl Grid.Column<T>).() -> Unit = {}
): Grid.Column<T> {
val column = addColumn(renderer)
column.block()
return column
}

/**
* Adds a column for given [property]. The column [key] is set to the property name, so that you can look up the column
* using `Grid.getColumnBy()`. The column is also by default set to sortable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.github.mvysny.karibudsl.v10

/**
* [KCssVarNameA] allows you to insert a CSS variable
* [varName] - the name of variable
* [cssValue] - a css representation of the variable
* Example of usage:
* ```kotlin
* verticalLayout(style { lineHeight = +KLumoLineHeight.XS; }) {
* ...
* }
* ```
* Generated html code:
* ```
* <vaadin-vertical-layout> style="line-height: var(----lumo-font-size-xs);" </vaadin-vertical-layout>
* ```
*/
public interface KCssVarNameA {
public val varName : String
public val cssValue: String get() = "var($varName)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.github.mvysny.karibudsl.v10

import com.vaadin.flow.component.icon.Icon
import com.vaadin.flow.component.shared.ThemeVariant
import kotlinx.css.CssBuilder
import kotlinx.css.hyphenize

/**
* [KLitRendererAttribute] allows you to insert attribute of a html tag
* [name] - the name of attribute
* [value] - the value of attribute
* [toString] - a html representation of the attribute
* Example of usage:
* ```kotlin
* horizontalLayout(theme(spacing)) {
* ...
* }
* ```
* Generated html code:
* ```
* <vaadin-horizontal-layout> theme: "spacing" </vaadin-horizontal-layout>
* ```
*/
public data class KLitRendererAttribute(
val name: String,
val value: String
) {
override fun toString(): String = "$name=\"$value\""
}

/**
* [buildInlineStyleText] builds text value representation of the [style] attribute.
* Example of usage:
* ```
* verticalLayout(style { lineHeight = +KLumoLineHeight.XS; })
* ...
* }
* ```
* Generated html code:
* ```
* <vaadin-vertical-layout> style="line-height: var(----lumo-font-size-xs);" </vaadin-vertical-layout>
* ```
*/
private fun CssBuilder.buildInlineStyleText() =
declarations.asSequence().joinToString(separator = "; ") { (key, value) ->
"${key.hyphenize()}: $value"
}

public fun <TSource> KLitRendererTagsBuilderA<TSource>.style(value: String) =
KLitRendererAttribute("style", value)

public fun <TSource> KLitRendererTagsBuilderA<TSource>.style(block: CssBuilder.() -> Unit) =
KLitRendererAttribute("style", CssBuilder().apply(block).buildInlineStyleText())

public fun <TSource> KLitRendererTagsBuilderA<TSource>.theme(vararg names: KLitRendererTheme) =
KLitRendererAttribute("theme", names.joinToString(separator = " ") { it.name })

public fun <TSource> KLitRendererTagsBuilderA<TSource>.themeVariant(vararg names: ThemeVariant) =
KLitRendererAttribute("theme", names.joinToString(separator = " ") { it.variantName })

public fun <TSource> KLitRendererTagsBuilderA<TSource>.cssClass(vararg names: String) =
KLitRendererAttribute("class", names.joinToString(separator = " "))

public fun <TSource> KLitRendererTagsBuilderA<TSource>.id(value: String) =
KLitRendererAttribute("id", value)

public fun <TSource> KLitRendererTagsBuilderA<TSource>.name(value: KLitRendererBuilderA.Property<TSource>) =
KLitRendererAttribute("name", value.litItem)

public fun <TSource> KLitRendererTagsBuilderA<TSource>.src(value: KLitRendererBuilderA.Property<TSource>) =
KLitRendererAttribute("src", value.litItem)

/**
* <vaadin-icon icon="vaadin:edit" slot="prefix"></vaadin-icon>
*
* Example of use:
* ```kotlin
* val columnIcon by property { row ->
* if (row.isNew)
* VaadinIcon.PLUS_CIRCLE.createIcon().icon
* else
* VaadinIcon.EDIT.createIcon().icon
* }
* ...
* icon(+columnIcon)
* ...
* ```
*/
public fun <TSource> KLitRendererTagsBuilderA<TSource>.icon(value: KLitRendererBuilderA.Property<TSource>) =
KLitRendererAttribute("icon", value.litItem)

public fun <TSource> KLitRendererTagsBuilderA<TSource>.icon(value: Icon) =
KLitRendererAttribute("icon", value.icon)

public fun <TSource> KLitRendererTagsBuilderA<TSource>.img(value: KLitRendererBuilderA.Property<TSource>) =
KLitRendererAttribute("img", value.litItem)

public fun <TSource> KLitRendererTagsBuilderA<TSource>.href(value: String) =
KLitRendererAttribute("href", value)

public fun <TSource> KLitRendererTagsBuilderA<TSource>.href(value: KLitRendererBuilderA.Property<TSource>) =
KLitRendererAttribute("href", value.litItem)


public fun <TSource> KLitRendererTagsBuilderA<TSource>.click(value: KLitRendererBuilderA.Function<TSource>) =
KLitRendererAttribute("@click", value.litItem)
Loading

0 comments on commit d0cf321

Please sign in to comment.