Hello again :)
In the first part of this article we have designed the Redux model for our application. Everything is ready, the Pokémons are downloading, but there is nothing on the screen. We need to create a GUI.
Remember, you can find the complete application and all the sources in the kvision-examples GitHub repository.
The GUI
The user interface of our application consists of four parts:
- A search input field.
- An information text (visible during loading process and in case of an error).
- A responsive grid of boxes for selected Pokémons.
- Pagination buttons.
We can use the following skeleton of the start
method, which is the entry point of our app and takes care of the GUI creation. The stateBinding
method is the core part of the Redux architecture in KVision. It makes all the components inside the given container become functions of the state, automatically refreshed when the state changes.
override fun start(state: Map<String, Any>) {
root = Root("kvapp") {
vPanel(alignItems = FlexAlignItems.STRETCH) {
searchField()
vPanel(alignItems = FlexAlignItems.STRETCH) {
maxWidth = 1200.px
textAlign = TextAlign.CENTER
marginLeft = auto
marginRight = auto
}.stateBinding(store) { state -> // !!! Redux binding is here !!!
informationText(state)
pokemonGrid(state)
pagination(state)
}
}
}
store.dispatch(downloadPokemons())
}
As you can see, we can delegate most of the work to some smaller methods (all of them are extension methods for the Container
class).
The search input field
The searchField
method is responsible for instantiating Text
component. The event listener dispatches SetSearchString
Redux action when the user enters some text into the field.
private fun Container.searchField() {
text {
placeholder = "Enter pokemon name ..."
width = 300.px
marginLeft = auto
marginRight = auto
autofocus = true
setEventListener<Text> {
input = {
store.dispatch(PokeAction.SetSearchString(self.value))
}
}
}
}
The information text
The informationText
method is responsible for displaying "Loading ..." label or an error message.
private fun Container.informationText(state: Pokedex) {
if (state.downloading) {
div("Loading ...")
} else if (state.errorMessage != null) {
div(state.errorMessage)
}
}
The grid
First let's design the box, which will display the name and the picture of the single Pokémon. We will declare a new class inheriting from a DockPanel
container and a Style
object with some CSS properties. The PokeBox
class is fairly simple - it has just two child components - an Image and a Div. We apply the style by using addCssClass
method.
val pokeBoxStyle = Style {
border = Border(1.px, BorderStyle.SOLID, Col.GRAY)
width = 200.px
height = 200.px
margin = 10.px
style("img") {
marginTop = 30.px
}
style("div.caption") {
textAlign = TextAlign.CENTER
background = Background(Col.SILVER)
width = 100.perc
}
}
class PokeBox(pokemon: Pokemon) : DockPanel() {
init {
addCssClass(pokeBoxStyle)
image(
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.url.substring(34,pokemon.url.length - 1)}.png",
centered = true
)
add(Div(pokemon.name.capitalize(), classes = setOf("caption")), Side.DOWN)
}
}
The pokemonGrid
method is responsible for rendering the CSS grid with a single PokeBox
object inside every cell. It uses the GridPanel
component with an appropriate templateColumns
property value to make it responsive.
private fun Container.pokemonGrid(state: Pokedex) {
if (!state.downloading && state.errorMessage == null) {
gridPanel(
templateColumns = "repeat(auto-fill, minmax(250px, 1fr))",
justifyItems = GridJustify.CENTER
) {
state.visiblePokemons.forEach {
add(PokeBox(it))
}
}
}
}
The pagination
The pagination is created by the following method, using ButtonGroup
and Button
KVision components. Both "next" and "previous" buttons are disabled when necessary and both dispatch suitable Redux actions.
private fun Container.pagination(state: Pokedex) {
if (!state.downloading && state.errorMessage == null) {
hPanel(justify = FlexJustify.CENTER) {
margin = 30.px
buttonGroup {
button("<<") {
disabled = state.pageNumber == 0
onClick {
store.dispatch(PokeAction.PrevPage)
}
}
button(" ${state.pageNumber + 1} / ${state.numberOfPages} ", disabled = true)
button(">>") {
disabled = state.pageNumber == (state.numberOfPages - 1)
onClick {
store.dispatch(PokeAction.NextPage)
}
}
}
}
}
}
Final words
Our application is ready. We've created the whole GUI with about 100 lines of code (half of the original JavaScript project). KVision really gives you "the best of both worlds" - modern, expressive language, with lots of reusable components, that can be used with well known patterns and libraries like Redux.
The complete application in the GitHub repository has some small additions, not mentioned in this article (touch gestures, I18n) and is also a fully compatible Progressive Web App (PWA).
Thank you for reading this article and hope I'll catch you in the next one!
Cheers :)
Top comments (0)