Slider 2: a JavaScript object instead of case classes

Slider 2: a JavaScript object instead of case classes

Slider 2: a JavaScript object instead of case classes

Two weeks ago we talked about how to create a beautiful Slider in Scala.js

Our Slider works well (See Demo). But there was one thing that worried me. We need to define a Slider property as a Scala case class. It’s not bad, if our Slider is a part of a single Scala/Scala.js application. But what if we need to install our Slider into some HTML page. In that situation we should think how to pass some JavaScript object into the Slider.

At first sight this is a frightening problem. It turned out that it is not. All we need to do is to write facade traits and then to use their companions for conversion from JavaScript objects to Scala case classes.

In our example we passed data to the Slider using this structure:


val slides = List(
      SlideProps(style = Some(js.Dictionary(
        "backgroundColor" -> "gray"
      )), containers = Seq(
        SlideContainer(
           elementType = "block",
           className = "loader up"
        ),
        SlideContainer(
           elementType = "block",
           className = "loader down"))
      ),
      SlideProps(
        style = Some(js.Dictionary(
          "backgroundImage" -> "url(img/step1.jpg)"
        )), containers = Seq(
          SlideContainer(
             elementType = "img",
             className = "car",
             classIn = "car-in",
             src = "img/car.png"
          ),
          SlideContainer(
            elementType = "block",
            className = "block",
            children = Some(
                Seq(
                   SlideChild(
                      elementType = "block",
                      classIn = "blah-in",
                      className = "blah",
                      text = "Blah"
                   ),
                   SlideChild(
                      elementType = "block",
                      classIn = "blah-in",
                      className = "blah blah1",
                      text = "blah"
                   ),
                   SlideChild(
                      elementType = "block",
                      classIn = "blah-in",
                      className = "blah blah2",
                      text = "blah"
                   )
            ))
          )
        )),
      SlideProps(
        style = Some(js.Dictionary(
          "backgroundImage" -> "url(img/step2.jpg)"
        )), containers = Seq(
          SlideContainer(
            elementType = "img",
            className = "car1",
            classIn = "car1-in",
            src = "img/car1.png"
           ),
          SlideContainer(
            elementType = "block",
            className = "block1",
            classIn = "block1-in",
            children = Some(Seq(
              SlideChild(
                 elementType = "block",
                 classIn = "blah-1-in",
                 className = "blah-1",
                 text = "Blah"
              ),
              SlideChild(
                 elementType = "block",
                 classIn = "blah-1-in", 
                 className = "blah-1 blah-1-1",
                 text = "blah"
              ),
              SlideChild(
                 elementType = "block",
                 classIn = "blah-1-in",
                 className = "blah-1 blah-1-2",
                 text = "blah"
              )
            ))
          )
        ))
)

Today we shall use this JavaScript object:


var slides =
  [
    {
      style: {
        "backgroundColor":"red"
        },
      containers:
        [
          {
             elementType:"block",
             className: "loader up"
          },
          {
             elementType: "block",
             className: "loader down"
          }
        ]
      },
      {
      style: {
          "backgroundImage": "url(img/step1.jpg)"
        },
      containers: [
          {
            elementType: "img",
            className: "car",
            classIn: "car-in",
            src: "img/car.png"
          },
          {
            elementType: "block",
            className: "block",
            children:
              [
                {
                  elementType: "block",
                  classIn: "blah-in",
                  className: "blah",
                  text: "Blah"
                },
                {
                  elementType: "block",
                  classIn: "blah-in",
                  className: "blah blah1",
                  text: "blah"
                },
                {
                  elementType: "block",
                  classIn: "blah-in",
                  className: "blah blah2",
                  text: "blah"
                }
              ]
           }
         ]
      },
      {
      style: {
          "backgroundImage": "url(img/step2.jpg)"
        },
      containers: [
          {
            elementType: "img",
            className: "car1",
            classIn: "car1-in",
            src: "img/car1.png"
          },
          {
            elementType: "block",
            className: "block",
            children:
              [
                {
                  elementType: "block",
                  classIn: "blah-1-in",
                  className: "blah-1",
                  text: "Blah"
                },
                {
                  elementType: "block",
                  classIn: "blah-1-in",
                  className: "blah-1 blah-1-1",
                  text: "blah"
                },
                {
                  elementType: "block",
                  classIn: "blah-1-in",
                  className: "blah-1 blah-1-2",
                  text: "blah"
                }
              ]
           }
         ]
      }
    ];


Both objects contain same data. The Slider understands only the first one. Let’s teach the Slider to read JavaScript!

To declare the nested structure one must first declare the structures that will be nested. We starts from the SlideChild case class. As JavaScript object, it looks something like that:


{
  elementType: "block",
  classIn: "blah-1-in",
  className: "blah-1 blah-1-2",
  text: "blah"
}

Here is a Scala facade trait for this JavaScript object:


@ScalaJSDefined
trait SlideChildJS extends js.Object {
  val elementType: String
  val style: js.UndefOr[js.Dictionary[Any]]
  val text: js.UndefOr[String]
  val src: js.UndefOr[String]
  val link: js.UndefOr[String]
  val className: js.UndefOr[String]
  val classIn: js.UndefOr[String]
}

Important: all optional fields MUST be of type js.UndefOr[].

To convert this to an appropriate case class we should use the companion object:


object SlideChildJS {
  implicit class SlideChildOps(val self: SlideChildJS) extends AnyVal {
    def toSlideChild: SlideChild = {
      SlideChild(
        self.elementType,
        self.style.toOption,
        self.text.toOption,
        self.src.toOption,
        self.link.toOption,
        self.className.toOption,
        self.classIn.toOption
      )
    }
  }
}


Now every instance of the SlideChildJS trait has a method toSlideChild, that will convert it to an instance of the SlideChild class.

We can do the same for a SlideContainer, using our SlideChildJS  trait.


@ScalaJSDefined
trait SlideContainerJS extends js.Object {
  val elementType: String
  val className: js.UndefOr[String]
  val style: js.UndefOr[js.Dictionary[Any]]
  val children: js.UndefOr[js.Array[SlideChildJS]]
  val classIn: js.UndefOr[String]
  val src: js.UndefOr[String]
  val link: js.UndefOr[String]
  val text: js.UndefOr[String]
}

object SlideContainerJS {
  implicit class SlideContainerOps(val self: SlideContainerJS) extends AnyVal {
    def toSlideContainer: SlideContainer = {
      SlideContainer(
        self.elementType,
        self.className.toOption,
        self.style.toOption,
        self.children.toOption.map(_.map(_.toSlideChild)),
        self.classIn.toOption,
        self.src.toOption,
        self.link.toOption,
        self.text.toOption
      )
    }
  }
}

Here is the full code:


@ScalaJSDefined
trait SliderPropsJS extends js.Object {
  val list: js.Array[SlidePropsJS]
  val generals: SliderGeneralsJS
  val preloads: js.Array[String]
}

object SliderPropsJS {
  implicit class SliderPropsOps(val self: SliderPropsJS) extends AnyVal {
    def toSliderProps: SliderProps = {
      SliderProps(
        self.list.map(_.toSlideProps).toList,
        self.generals.toSliderGenerals,
        self.preloads.toSeq
      )
    }
  }
}


@ScalaJSDefined
trait SlidePropsJS extends js.Object {
  val style: js.UndefOr[js.Dictionary[Any]]
  val text: js.UndefOr[String]
  val className: js.UndefOr[String]
  val link: js.UndefOr[String]
  val containers: js.Array[SlideContainerJS]
  val active: js.UndefOr[Boolean]
}

object SlidePropsJS {
  implicit class SlidePropsOps(val self: SlidePropsJS) extends AnyVal {
    def toSlideProps: SlideProps = {
      SlideProps(
        self.style.toOption,
        self.text.toOption,
        self.className.toOption,
        self.link.toOption,
        self.containers.map(_.toSlideContainer).toSeq,
        self.active.toOption.getOrElse(false)
      )
    }
  }
}





@ScalaJSDefined
trait SliderGeneralsJS extends js.Object {
  val controlsType: js.UndefOr[String]
  val delay: Int
  val firstDelay: Int
}

object SliderGeneralsJS {
  implicit class SliderGeneralsOps(val self: SliderGeneralsJS) extends AnyVal {
    def toSliderGenerals: SliderGenerals = {
      SliderGenerals(
        self.controlsType.toOption,
        self.delay,
        self.firstDelay
      )
    }
  }
}



@ScalaJSDefined
trait SlideChildJS extends js.Object {
  val elementType: String
  val style: js.UndefOr[js.Dictionary[Any]]
  val text: js.UndefOr[String]
  val src: js.UndefOr[String]
  val link: js.UndefOr[String]
  val className: js.UndefOr[String]
  val classIn: js.UndefOr[String]
}

object SlideChildJS {
  implicit class SlideChildOps(val self: SlideChildJS) extends AnyVal {
    def toSlideChild: SlideChild = {
      SlideChild(
        self.elementType,
        self.style.toOption,
        self.text.toOption,
        self.src.toOption,
        self.link.toOption,
        self.className.toOption,
        self.classIn.toOption
      )
    }
  }
}

@ScalaJSDefined
trait SlideContainerJS extends js.Object {
  val elementType: String
  val className: js.UndefOr[String]
  val style: js.UndefOr[js.Dictionary[Any]]
  val children: js.UndefOr[js.Array[SlideChildJS]]
  val classIn: js.UndefOr[String]
  val src: js.UndefOr[String]
  val link: js.UndefOr[String]
  val text: js.UndefOr[String]
}

object SlideContainerJS {
  implicit class SlideContainerOps(val self: SlideContainerJS) extends AnyVal {
    def toSlideContainer: SlideContainer = {
      SlideContainer(
        self.elementType,
        self.className.toOption,
        self.style.toOption,
        self.children.toOption.map(_.map(_.toSlideChild)),
        self.classIn.toOption,
        self.src.toOption,
        self.link.toOption,
        self.text.toOption
      )
    }
  }
}

Here it is! My congratulations! We have created the real JavaScript function, that can take a JavaScript object from other script.

By the way, we have written additional 130 lines of Scala code, but it hasn’t lead to increasing in the size of compiled JavaScript file.

 

 

See the full code of this example.

Scala.js works great again.

 

 

 

 

 

 

 

 

Please follow and like us:

About Alexandre Kremlianski

Scala / Scala.js / JavaScript programmer

Leave a Reply

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