Scala – JavaScript class conversion example

Scala – JavaScript class conversion example

Scala is a great language. We are lucky to use it not only for back-end development but also in front-end.

That’s cool, but it’s an illusion that one can create the whole front-end by solely using Scala.js. Of course, it’s possible. And one could develop the great application. I won’t argue about that. But my point is: we should use the strong parts of Scala in conjunction with strong parts of JavaScript.

We all love Scala for many things. If we need to realize business logic, we will choose Scala.js . But what about view rendering, appearance, visual effects, styles, templates? What does Scala.js have in its arsenal? Some facades to JavaScript libraries, some ported JavaScript frameworks. Not too much.

Аt the same time, JavaScript has a vast range of ready to use frameworks, template machines, patterns. All that treasuries are in continuous development and improvement. Thousands of developers do their best to make them better and better.

I can suspect, that coders who are interested in Scala.js, mostly have back-end background (Scala, Java). Significant part of them is supposed to have some front-end experience as well. Number of Scala front-enders is not so big. It’s another problem. It’s difficult to form a front-end team solely from Scala.js adherents.

The logical alternative could be to split front-end in two parts: Scala and JavaScript. The Scala part would be liable for business logic and model, the JavaScript part would be liable for view rendering. In that situation a Scala front-ender would do its routine “back-end” work, creating API for JavaScript front-ender.

A JavaScript fron-ender would be freed from developing business logic. It’s a win-win for everyone.

In order to bring this to life, a Scala developer have to build some bridges between Scala and JavaScript worlds. The major one is two-way conversion of Scala and JavaScript objects.

Let’s start with a simple example. Say, we have some case class Car:

case class Car(
    id: UUID,
    brand: String,
    model: String
 )

We need to define its JavaScript counterpart and to develop tools for two-way conversion. After that we will create a State object and provide JavaScript API for its transformation.

Here is a JavaScript class of our Car:

@ScalaJSDefined
class CarJ extends js.Object {
var id: String = _
var brand: String = _
var model: String = _
}

We need two constructors (one takes the id as a parameter, the other generates it):

object CarJ {
def apply(x: String, y: String, z: String): CarJ = {
new CarJ{
id = x
brand = y
model = z
}
}

def apply(y: String, z: String): CarJ = {
new CarJ {
id = UUID.randomUUID().toString
brand = y
model = z
}
}
}

Now we create the object CarImplicits. It contains two implicit classes which add method convert to our classes and additionally two implicit methods for auto-conversion:

object CarImplicits {

// adds convert method to CarJ:
implicit class CarJ2S(carJ: CarJ) {

def convert(): Car= {

if ( carJ.id == null )

Car(
UUID.randomUUID(),
carJ.brand,
carJ.model
)
else
Car(
UUID.fromString(carJ.id),
carJ.brand,
carJ.model
)
}

}

// adds convert methods to Car:
implicit class CarS2J(car: Car) {

def convert(): CarJ = {
CarJ(
car.id.toString,
car.brand,
car.model
)
}
}


// 2 methods for auto-converting:
implicit def carJ2S(carJ: CarJ): Car =
carJ.convert()


implicit def carS2J(car: Car): CarJ =
car.convert()

}

As far as we have finished with conversions, let’s create a class, that will keep the application state. I use immutable.Map for that purposes, because I’m going to be finding Cars by their ids:

case class CarState ( cars: Map[UUID, Car] = Map.empty)

The companion object contains our state itself and several methods, which will be callable from JavaScript. These methods are the only way to communicate with the state:

@JSExportTopLevel("CarState")
object CarState {

  import scala.scalajs.js.JSConverters._

  // prefer immutable var over mutable val !
  var state = CarState()

  @JSExport
  def addCar(car: CarJ): String = {
    _addCar(car).toString
  }

  private def _addCar(car: Car): UUID = {
    state = state.copy(cars = state.cars + (car.id -> car))
    car.id
  }


  @JSExport
  def removeCar(id: String): Unit =
    _removeCar(UUID.fromString(id))

  private def _removeCar(id: UUID): Unit =
     state = state.copy(state.cars - id)

  @JSExport
  def findCar(id: String): UndefOr[CarJ] =_findCar(
     UUID.fromString(id)).map(_.convert()).orUndefined

  private def _findCar(id: UUID): Option[Car]  = state.cars.get(id)

  @JSExport
  def carsSize(): Int = state.cars.size

  @JSExport
  def allCars(): js.Array[CarJ] = {

    val list =state.cars.values.map(_.convert()).toList
    mutable.Seq(list: _*).toJSArray

  }

}

And last but not least, tests:

import org.scalatest._
import CarImplicits._

import scala.scalajs.js

class CarSpec extends FreeSpec {

  "Let's produce some operations with CarState" in {


    val volvo = CarJ("Volvo","2018")
    val mercedes = CarJ("Mercedes", "2019")
    val bmw = CarJ("BMW", "2017")

    assert(CarState.carsSize() == 0)

    val volvoId = CarState.addCar(volvo)

    assert(CarState.carsSize() == 1)

    val mercedesId = CarState.addCar(mercedes)

    assert(CarState.carsSize() == 2)

    val bmwId = CarState.addCar(bmw)

    assert(CarState.carsSize() == 3)

    assert(CarState.findCar(bmwId).isInstanceOf[CarJ])

    assert(CarState.findCar(bmwId).get.convert() == bmw.convert())

    assert(CarState.findCar("5f0c93a4-e7fa-407e-abbc-8d405b41ac34") == js.undefined)

    CarState.removeCar(bmwId)

    assert(CarState.carsSize() == 2)

    assert(CarState.findCar(bmwId) == js.undefined)

    assert(CarState.allCars().isInstanceOf[js.Array[CarJ]])

    assert(CarState.allCars().length == 2)

    assert(CarState.allCars().apply(0).brand == "Volvo")

    assert(CarState.allCars().apply(0).id == volvoId)

  }
}

The main idea of this post: conversion between Scala and JavaScript object is easy. If you don’t like monkey patching, you can specify convert methods directly in appropriate classes. I did it myself to check whether this reduces the bundle. No, it doesn’t.

We provided a JavaScript developer with the API for accessing the application state. We have left for him a task to create some visualization of our business logic.



To be continued


You can find the code here




About Alexandre Kremlianski

Scala / Scala.js / JavaScript programmer

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.