Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
repositories {
jcenter()
}
dependencies {
/Fill this in with the version of kotlinx in use in your
project
def kotlinx_html_version = "your_version_here"
/ include for client-side
compile "org.jetbrains.kotlinx:kotlinx-html-
js:${kotlinx_html_version}"
}
Refactoring the HTML code using DSL
The DSL code to make a button with the title "Get Weather" looks as follows:
button {
+"Get Weather"
type = ButtonType.button
onClickFunction = {
/ Your code to handle button click goes here.
}
}
Simple and clean code.
Similarly, let's create a function that will display an entire div, which has a label, text input, and button:
fun getInputDiv(): HTMLDivElement {
val inputDiv = document.create.div {
label {
+"Enter zip code : "
input {
id = "zipCode"
type = InputType.number
value = 411021.toString()
}
}
button {
+"Get Weather"
type = ButtonType.button
onClickFunction = {
/ Your code to handle button click goes here
}
}
}
return inputDiv
}
Observe how we have provided ID, input types, and a default ZIP code value. A default ZIP code value is optional.
Let's spend some time understanding the previous code. label, input, button, type, id, and onClickFunction are nothing but functions. They are basically Lambda functions.
Some of the functions that use Lambda parameters and call variations can be as follows:
someFunction({})
someFunction("KotlinBluePrints",1,{})
someFunction("KotlinBluePrints",1){}
someFunction{}
Let's run the code. You may get an error on the console saying:
Error Uncaught Error: Error loading module 'KotlinBluePrintsJSDemo'. Its dependency 'kotlinx-html-js' was not found. Please, check whether 'kotlinx-html-js' is loaded prior to 'KotlinBluePrintsJSDemo'.
This is because kotlinx-html-js is missing, which is required to process the DSL generated code. You can see the kotlinx-html-js file generated under the out/production/KotlinBluePrintsJSDemo/lib path.
Calling a weather API
Now it's time to get the weather data and display it on the page.
We will use XMLHttpRequest to achieve this. Register yourself at http://openweathermap.org/appid and get your application ID. Your application ID will be appended to the actual URL to make the authenticated call to the weather API. Once you get the app ID let's keep that information in the Constants.kt file:
const val IMAGE_URL = "http://openweathermap.org/img/w/%s.png"
const val BASE_URL =
"https://api.openweathermap.org/data/2.5/forecast/daily?
mode=json&units=metric&cnt=7"
const val APP_ID = "Your open weather map application id"
const val FULL_URL = "$BASE_URL&appid=$APP_ID&q="
The Constants.kt file is not as simple as it looks. Check how we have stored different values. We have used const val, which is equivalent to const and static used combined. Also defining FULL_URL uses the concept of string interpolation. String interpolation is used to concatenate static strings along with string objects. You can also call functions in string interpolation as follows:
h4 {
+"Weather info for ${forecastResult.city.name},
(${forecastResult.city.country})"
}
Now, in onClickFunction we write the following code to perform the API call and on the successful response we call a showData function, which takes a forecastResult object:
onClickFunction = {
val zipCode = document.getElementById("zipCode") as
HTMLInputElement
val xmlHttpRequest = XMLHttpRequest()
xmlHttpRequest.open("GET", FULL_URL + zipCode.value, false)
xmlHttpRequest.send()
println(xmlHttpRequest.responseText)
val forecastResult = JSON.parse<ForecastResult>
(xmlHttpRequest.responseText)
showData(forecastResult)
}
Reading data from input elements
See how we read data from input elements:
document.getElementById("zipCode") as HTMLInputElement
The as HTMLInputElement construct is basically casting a result into the HTMLInputElement class.
Using as directly is not advisable because it can give you ClassCastException; a proper way to use it is as? HTMLInputElement. This returns null if the class cast fails. And Kotlin will force you to use a Null Safety operator from that very moment.
Data classes
We are maintaining ForecastResult, which is our model. For this purpose, we have data classes in Kotlin. One of the coolest features in Kotlin is data classes. All the pain that we used to endure to create and maintain POJO classes in Java is gone. No need to have those dedicated packages to hold your model class. Any Kotlin file can hold your data class. By default it provides you methods such as toString(), equals(), copy(), and hashCode() method implementation. In Android, we mostly use these types of classes to hold our JSON responses in the form of model classes. You can check out the data classes we created in ServerResponses.kt:
data class ForecastResult(val city: City, val list:
Array<Forecast>)
data class City(val id: Long, val name: String, val coord:
Coordinates, val country: String, val population: Int)
data class Coordinates(val lon: Float, val lat: Float)
data class Forecast(val dt: Long, val temp: Temperature, val
pressure: Float, val humidity: Int, val weather: Array<Weather>,
val speed: Float, val deg: Int, val clouds: Int)
data class Temperature(val day: Float, val min: Float, val max:
Float, val night: Float, val eve: Float, val morn: Float)
data class Weather(val id: Long, val main: String, val description:
String, val icon: String)
Some of the points to consider while using data classes are:
- The primary constructor needs to have at least one parameter
- All primary constructor parameters need to be marked as val or var
- Data classes cannot be abstract, open, sealed, or inner
- (Before version 1.1) data classes may only implement interfaces
Showing data to the user
Now comes the interesting part. We gate a ForecastResult object, which holds all the records. The list object holds records for seven days. Let's create a showData function that takes a ForecastResult object and display title text in <h4>. The code will look like the following snippet. Also, it has yet again one more example of string interpolation:
fun showData(forecastResult: ForecastResult) {
val root = document.getElementById("container")
root?.appendChild(document.create.div(classes = "currentTemp") {
h4 {
+"Weather info for ${forecastResult.city.name
(${forecastResult.city.country})"
}
})
}
This is simple now, quickly create a showForecast function that will be called from showData and will display the weather forecast for seven days. The showForecast is used with a function from Kotlin. thewith() is one of those functions that is liked by the developer community a lot; it makes use of Kotlin sweeter. The with() function accepts the receiver and the code written inside the function automatically applies to the receiver object. It's an inline function. Check out the following document:
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
In the code, observe how each iteration is using a with block. We have removed some of the lines from the original code, so that we can have the clean code snippet here:
forecastResult.list.forEachIndexed { index, forecast ->
with(forecast)
{
weatherContainer.appendChild(document.create.div(classes =
"weatherBlock") {
div {
p(classes = "currentTemp") {
+"${Math.round(temp.day)} °C"
}
}
img(classes = "weatherImage") {
src = "images/weather_img.png"
}
div {
span(classes = "secondaryText") {
+weather[0].main
}
}
div {
with(temp) {
span(classes = "primaryText") { +"${Math.round(max)}
°C"
}
span(classes = "secondaryText") { +"
/${Math.round(min)} °C" }
}
}
onClickFunction = {
showDetailedForecast(forecastResult.city, forecast)
}
})
}
}
DSL and Kotlin code are now beautifully gelled. Also, notice the onClickFunction that we wrote on div. Sweet, isn't it?
Showing weather details
A very small part of the app is left now. Let's show some more details to the user. Along with this, we will also learn a few more features of Kotlin. We have created a showDetailedForecast function that takes the City and Forecast objects as parameters. The following code snippets provide two things to learn:
fun showDetailedForecast(city: City, forecast: Forecast) {
val root = document.getElementById("container")
val weatherDetailDiv = document.create.div(classes =
"detailsContainer")
val basicDetailDiv = document.create.div {
p(classes = "secondaryText") {
+"${city.name}, ${city.country}
(${city.coord.lat},${city.coord.lon})"
}
p(classes = "secondaryText") {
+forecast.dt.getFullDate()
}
p(classes = "secondaryText") {
+"${forecast.weather[0].main},
${forecast.weather[0].description}"
}
}
val otherDetailsDiv = document.create.div {
div {
id = "leftDiv"
span(classes = "currentTemp") {
+"${Math.round(forecast.temp.day)} °C"
}
img {
src = "images/weather_img.png"
width = 90.toString()
height = 90.toString()
}
}
div {
id = "rightDiv"
p(classes = "secondaryText") { +"Pressure:
${forecast.pressure}
mb" }
p(classes = "secondaryText") { +"Humidity:
${forecast.humidity}
%" }
p(classes = "secondaryText") { +"Wind: ${forecast.speed} mph" }
p(classes = "secondaryText") { +"Cloudiness:
${forecast.clouds}
%" }
}
div(classes = "clearBoth")
}
weatherDetailDiv.appendChild(basicDetailDiv)
weatherDetailDiv.appendChild(otherDetailsDiv)
root?.appendChild(weatherDetailDiv)
}
Named parameters
In Kotlin, we can call/bind a parameter with their name for any function. We can call the preceding function by interchanging the parameter sequence as well. Something like the following:
showDetailedForecast(forecast = forecast, city =
forecastResult.city)
Observe that we swapped the place of the variable. And no wonder, all CSS classes that we have applied so far have a named parameter. Check all previous <div>, <h>, and <p> tags. Consider the following examples:
val weatherDetailDiv = document.create.div(classes =
"detailsContainer")
button(classes = "getWeatherButton")
span(classes = "primaryText") { +"${Math.round(max)} °C" }
Extension functions
Extension functions are a beautiful feature of Kotlin. Extension functions allow us to add the functions in the native class sets. All extension functions are statically resolved. Check out DateExtension.kt, it has three extension functions written for Long objects. They return different date formats. The code inside it may look a bit strange, which we will discuss in the following section:
fun Long.getShortDate(): String {
val getFormattedDate: dynamic = js("window.getShortDate")
return getFormattedDate(this)
}
fun Long.getFullDate(): String {
val getFormattedDate: dynamic = js("window.getFullDate")
return getFormattedDate(this)
}
fun Long.getFullWeekDay(): String {
val getFormattedDate: dynamic = js("window.getFullWeekDay")
return getFormattedDate(this)
}
We don't need to write utility methods in Kotlin. We should prefer extension functions over Utils. Do not try to have any heavy methods as extension functions, instance functions are always good.
Writing extension functions to format dates and to have some validation functions is OK. But it's not good to write an API calling function for any string class. Remember they are statically resolved. A project loaded with static is not good for memory.
Giving final touches
We wrote many lines of code so far. We also refactored them periodically. Once again it's a time to refactor and look for the possible improvements. Let's take a look back and see if there is any possibility of refactoring the code further.
Adding CSS
Let's add some custom font and style some of the missed HTML elements. We have used Robot font, you can use any font of your desire.
It's a simple one-liner code to mention the font in the app. Add the following line to your index.html page just after the <body> tag:
<link href="/cats-d8c4vu/fonts.googleapis.com/css?
family=Roboto+Condensed" rel="stylesheet">
And in main.css apply the font to an entire HTML page:
html *
{
font-family: 'Roboto Condensed', sans-serif;
}
Reload the page. Looks beautiful now, doesn't it?
To summarize, we learned various elements of Kotlin such as setting up Kotlin for JavaScript projects, interacting with DOM elements, DSL, and so on.
The purpose of this article was to show that Kotlin's support for JavaScript is no more an experiment. It's already production ready. You can see what can be done using the benefits of statically typed programming languages and powerful JavaScript ecosystems.
To know more about how to use Kotlin code for writing a Node.js application, you may refer to this book Kotlin Blueprints.
Build your first Android app with Kotlin
How to convert Java code into Kotlin
5 application development tools that will matter in 2018