Skip to content

Kunafa 0.2.0: What's new

Kabbura edited this page Mar 15, 2019 · 2 revisions

Kunafa 0.2.0-beta released

Here at Narbase we use Kunafa for all of our front-end web development. Since its first release in February 2018, Kunafa has evolved to become very flexible and extremely fun to use. And now, Kunafa 0.2.x has finally been released. There are many changes to the the 0.1.x releases. Here are the main changes. But before we go into details, you can join us at Slack by clicking this link.

Styles

Kunafa 0.2.0 has revamped the styles and styles sheets. As the philosophy of Kunafa is to make web development easy, and allow developers to do everything with Kotlin, kunafa now allows you to use almost all of CSS directly in Kotlin. First, you can add a style to any view simply by using the style { } function. For example:

        textView {
            text = "Failed to connect to the internet"
            style {
                width = 290.px
                height = wrapContent
                textAlign = TextAlign.Center
                backgroundColor = Color.white
                borderRadius = 4.px.toString()
                border = "1px solid #d4d4d4"
            }
        }

The style { } function creates on-the-fly a new CSS class rule set and adds it to the textView view above. To reuse the same style for multiple views, you can use the following syntax.

val errorStyle = classRuleSet {
    width = 290.px
    height = wrapContent
    textAlign = TextAlign.Center
    backgroundColor = Color.white
    borderRadius = 4.px.toString()
    border = "1px solid #d4d4d4"
}

and then to add it to a view:

        textView {
            text = "Failed to connect to the internet"
            addRuleSet(errorStyle)
        }

The classRuleSet() function creates a ruleset with a CSS class selector. If you want to create a custom css selector you can use

val customSelectorRuleSet = stringRuleSet("ul #id") {
    color = Color("323243")
}

And then add it to a view the same way as above.

Components:

Kunafa has two concepts, views and component. The view is the basic building block of the UI. It is basically a wrapper around HTML DOM elements. Every view contains an HTML element, but adds many functionalities around it. It has helper functions for easy ui creation. Unless you want to add new functionality to the core of Kunafa, you shouldn't extend the view classes. To build complicated UI blocks, you can use the views DSL or Components.

For example, this a reusable ui block of user details row with an image and a name:

fun View.userDetailsView(imageUrl: String, userFullName: String) {
    horizontalLayout {
        imageView {
            element.src = imageUrl
            style {
                width = 50.px
                height = 50.px
            }
        }
        textView {
            text = userFullName
        }
    }
}

and then it can be called like this:

    page {
        verticalLayout {
            userDetailsView("/image/1.png", "First user name")
            userDetailsView("/image/2.png", "Second user name")
            userDetailsView("/image/3.png", "Third user name")
        }
    }

So what are components? The view itself is what appears on the screen. But how do you listen to its lifecycle events? Enter components. Components are wrappers around a view to make it easier to listen to the view lifecycle events, and to make it easy to have a detached view that is not yet added to the DOM. For example, if we wan't to wrap userDetailsView inside a components, it will be like this:

class UserDetailsView(val imageUrl: String, val userFullName: String) : Component() {
    override fun View?.getView(): View {
        return horizontalLayout {
            style {
                width = matchParent
            }
            imageView {
                element.src = imageUrl
                style {
                    width = 50.px
                    height = 50.px
                }
            }
            textView {
                text = userFullName
                style {
                    width = weightOf(1)
                }
            }
        }
    }
}

The function View?.getView(): View is the only function that needs to be implemented, and it should return only one view.

Listening to view lifecycle

Now that we have a component, we can listen to the view lifecycle events:

class UserDetailsView(val imageUrl: String, val userFullName: String) : Component() {
    override fun onViewMounted(lifecycleOwner: LifecycleOwner) {
        console.log("View mounted")
    }

    override fun onViewRemoved(lifecycleOwner: LifecycleOwner) {
        console.log("View removed")
    }
    
    override fun View?.getView(): View {
        return horizontalLayout {
            style {
                width = matchParent
            }
            imageView {
                element.src = imageUrl
                style {
                    width = 50.px
                    height = 50.px
                }
            }
            textView {
                text = userFullName
                style {
                    width = weightOf(1)
                }
            }
        }
    }
}

The view has four lifecycle events: viewWillMount, onViewMounted, viewWillBeRemoved, and onViewRemoved

Routing

Routing should be easy and intuitive. In Kunafa it feels very natural. To specify that a certain view should be mounted at a certain path, simply wrap it with a route() function. For example:

    page {
        verticalLayout {
            link("/") { text = "Home" }
            link("/about") { text = "About" }
            
            route("/", isExact = true) {
                textView { 
                    text = "Home page"
                }
            }
            route("/about") {
                textView { 
                    text = "About page"
                }
            }
        }
    }

The link is a wrapper around the anchor a view. The view inside the route function will be mounted when the browser path matches the path given to the route. Notice the isExact in the home route so that the home page will only be matched when the browser path is exactly /.

Nested routes

You can have nested routes be calling the route function inside another route function. Fo example to have a /about/contact and /about/team routes, it can be as follows:

            route("/about") {
                verticalLayout {
                    textView {
                        text = "About page"
                    }

                    route("/contact") {
                        textView {
                            text = "Contact us"
                        }
                    }
                    route("/team") {
                        textView {
                            text = "Our awesome team"
                        }
                    }
                }
            }

Notice that we don't need to pass the /about in the nested routes. as the route path will be relative to its parent route. But what if we need it to be absoulte? Simple, set the isAbsolute flag to true as follows.

                    route("/about/team", isAbsolute = true) {
                        textView {
                            text = "Our awesome team"
                        }
                    }

There are more to routing, such as path parameters and getting parent route path. But that will be in a detailed tutorial.

Conclusion

So that's Kunafa 0.2.0. We like the way it has evolved until now, and we find it very enjoyable to work with. We will keep on writing the documentation for it, and will try to publish complete tutorials and example to make it easier to get started with Kunafa. We would really like to hear from you. Give it a try and let us know what you think. Also, don't forget to join us at Slack here, and leave us a star in Github.