Let’s create a Slider. Again…

Let’s create a Slider. Again…

There have been three posts about creating a Slider with Scala.js already. The first post was about how to do it in principal. The second post was about the ScalaCSS library in general and how one could use it with our Slider in particular. The third post was about how to use a JSON object with our Slider.

Now it’s time to compare Scala.js with its closest alternative: I mean TypeScript (a typed superset of JavaScript that compiles to plain JavaScript).

It’s fun: the first version of this Slider was written in CoffeeScript. It took me about 3 hours. I spent five times more doing the same with Scala.js (to tell the truth, I was novice in Scala.js). I think it would be interesting to make the same Slider in TypeScript and to see what happens.

Let’s start!

See the Demo to this post.

Preparing

To set the project in TypeScript is not so trivial as in Scala.js. You should set several configuration files:

  • package.json – the main configuration file for any npm project
  • tsconfig.json – the typescript configuration file that specifies the root files and the compiler options required to compile the project
  • gulpfile.js – I’ll use Gulp as a build tool.

Besides that, you should download the Definitely Typed files for React and ReactDOM. I used Typings – The TypeScript Definition Manager.

So, if you make a TypeScript project from scratch, you need:

  1. Install Node.js
  2. Install TypeScript
  3. Make configuration files
  4. Install Typings
  5. Download all dependencies
  6. Download the Definitely Typed files
  7. Install one of recommended editors (I use Visual Studio Code)

Don’t panic! It seems terrible only for the first time!

To start editing my Slider:

  1. Install Node.js
  2. Clone the project from GitHub:
    git clone https://github.com/Kremlianski/typescript-slider-demo.git
  3. Install TypeScript:
    npm install typescript -g
  4. cd typescript-slider-demo
  5. npm install

 

Data Structure

From TypeScript official handbook:

One of TypeScript’s core principles is that type-checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”. In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project.

So we need to specify interfaces. Here is a structure.ts file:


export interface SlideContainer {
  elementType: string
  className?: string
  style?: {[index: string]: string}
  children?: SlideChild[]
  classIn?: string
  src? : string
  link?: string
  text?: string
}

export interface SlideChild {
  elementType: string
  style?: {[index: string]: string}
  text?: string
  src? : string
  link?: string
  className?: string
  classIn?: string
}

export interface SliderControls {
  controlsType: string
}


export interface SlideProps {
  style?: {[index: string]: string}
  text?: string
  className?: string
  link?: string
  containers?: SlideContainer[]
  active: boolean
}

export interface SliderProps {
  list: SlideProps[]
  generals: SliderGenerals
  preloads: string[]
}

export interface SliderGenerals {
  controlsType?: string
  delay: number
  firstDelay: number
}


export interface State {
  active: number
}

These interfaces do the same that case classes do in our Scala.js example:

...

case class SlideProps(
   style: Option[js.Dictionary[Any]] = None,
   text: Option[String] = None,
   className: Option[String] = None,
   link: Option[String] = None,
   containers: Seq[SlideContainer] = Seq.empty,
   active: Boolean = false
 )

case class SliderGenerals(
   controlsType: Option[String] = None,
    delay: Int = 4000,
    firstDelay: Int = 500
)

...

There is no Option type in TypeScript, but you can use optional members instead. More important: you can’t use default values in TypeScript Interfaces.

 

Let’s return to our structure. I need to pass to Slider Element an object of type SliderProps:


interface SliderProps {
  list: SlideProps[]
  generals: SliderGenerals
  preloads: string[]
}

where list is a list of properties for every slide, generals is a set of general properties (for the slider in whole), preloads is a sequence of strings, which specifies images, that we need to load to the browser before our slider starts to show slides.

So I need to define these interfaces:


interface SlideProps {
  style?: {[index: string]: string}
  text?: string
  className?: string
  link?: string
  containers?: SlideContainer[]
  active: boolean
}

interface SliderGenerals {
  controlsType?: string
  delay: number
  firstDelay: number
}

SliderGenerals – general properties. There are no much general properties. ControlsTypes is reserved for controls. I’m sure that control buttons is a good idea for the slider. May be I’ll do them later…

SlideProps – every slide has its own properties. These properties specify appearance and content.

Of course the power of this slider is in using the containers attribute!


interface SlideContainer {
  elementType: string
  className?: string
  style?: {[index: string]: string}
  children?: SlideChild[]
  classIn?: string
  src? : string
  link?: string
  text?: string
}

interface SlideChild {
  elementType: string
  style?: {[index: string]: string}
  text?: string
  src? : string
  link?: string
  className?: string
  classIn?: string
}

elementType is the only required attribute. It can be: 'block', 'img', 'linked-img'.

If the type is img or linked-img, you can’t place any content to it.

If the type is block, you can place text or children.

Our state has only one field:


interface State {
  active: number
}

It stores an index of an active slide.

Slide Element

We need a React component for a Slide:


class Slide extends React.Component<SlideProps, {}> {
  constructor(props:SlideProps) {
    super(props)
  }
  render() {

    const props = this.props

    function renderElement(
      i: number,
      elementType: string,
      className?: string,
      style?: {[index: string]: string},
      classIn?: string,
      src?: string,
      link?: string,
      text?: string,
       children: SlideChild[] = []): JSX.Element {
        let result: JSX.Element
        let classString = ""

        if (className) classString += className 
        if (className && classIn && props.active) classString += " "
        if (classIn &&  props.active) classString += classIn 

        let style0: {[index: string]: string} = {}

        if (props.active) style0 = style

        switch(elementType) {
          case 'block': 
            return (
              <div key={i} className={classString} style={style0}>{text} {
                children.map ((y, i) =>
                  renderElement(i, y.elementType, y.className,
                  y.style,y.classIn, y.src, y.link, y.text)
                )
              }
          </div>)
          
          case 'img': return (
            <img key={i} src={src} className={classString} style={style0}  />
            )

          case 'linked-img': return <a key={i} href={link}>
            <img src={src} className={classString} style={style0} />
          </a>
          default: return <div />
        }

    }


    let classString = "slider__slide"
    if(props.className) classString += " " + props.className

    return (
      <div className={classString} data-active={props.active} style={props.style}>

        <div className="slider__slide__text">
          {
            props.text && <a href={props.link}>{props.text}</a>
          }
        </div>
        {props.containers && props.containers.map ((x, i) => 
          renderElement(i, x.elementType, x.className,
           x.style,x.classIn, x.src, x.link, x.text, x.children))}
    </div>
    )
  }
}

The function renderElement is a major part of this code. It is called if a slide has a containers property with a sequence of container elements, or if a container has a children property with a sequence of child elements. This function check a type of every element and creates markup in accordance with a type and other parameters of an element. This function can be called recursively.

You can compare this code with the code of the Slide element from Scala.js example:

val activeAtr = "data-active".reactAttr

  val Slide = ReactComponentB[SlideProps]("Slide")
    .render_P(p => {

      def renderElement(elementType: String, className: Option[String],
            style: Option[js.Dictionary[Any]],
            classIn: Option[String], src: Option[String],
            link: Option[String], text: Option[String],
            children: Seq[SlideChild] = Seq.empty): ReactElement = {
        elementType match {
          case t if t == "block" => {
            <.div(
              ^.classSet1(
                 className.getOrElse(""),
                 classIn.getOrElse("") -> p.active),
              p.active ?= (^.style := style),
              text,
              children.map(y => {
                renderElement(
                  y.elementType,
                  y.className,
                  y.style,
                  y.classIn,
                  y.src,
                  y.link,
                  y.text)
               }
              )

            )
          }
          case img if img == "img" => {
            <.img(
               ^.src := src,
               ^.classSet1(
                  className.getOrElse(""),
                  classIn.getOrElse("") -> p.active),
               p.active ?= (^.style := style))
          }
          case img if img == "linked-img" => {
            <.a(^.href := link,
              <.img(
                 ^.src := src,
                 ^.classSet1(
                    className.getOrElse(""),
                    classIn.getOrElse("") -> p.active),
                 p.active ?= (^.style := style)))
          }
          case _ =>  true
        ),
        activeAtr := p.active,
        ^.style := p.style,
        <.div(
           ^.className := "slider__slide__text",
           p.text.map(t => <.a(^.href := p.link, t)) ),
           p.containers.map(x => {
              renderElement(
                 x.elementType,
                 x.className,
                 x.style,
                 x.classIn,
                 x.src,
                 x.link,
                 x.text,
                 x.children
              )
           } 
        )
      )
    }
    )
    .build

 

Slider Element

The code of the Slider Element:


export default class Slider extends React.Component<SliderProps, State> {

  timer: number

  constructor(props:SliderProps) {
    super(props)
    this.previousSlide = this.previousSlide.bind(this)
    this.nextSlide = this.nextSlide.bind(this)
    this.state = { active: 0 }
  }

  nextSlide(){
    let slide
    const activeSlide = this.state.active
    if (activeSlide + 1 < this.props.list.length) slide = activeSlide + 1
    else slide = 0

    if (slide == 0) slide = 1 

    this.setState({active: slide}) 
  }

  previousSlide() {
    let slide
    const activeSlide = this.state.active
    if (activeSlide - 1 <= 0) slide = this.props.list.length - 1
    else slide = activeSlide - 1


    this.setState({active: slide}) 
  }

  componentDidMount(){
    if(this.timer) window.clearTimeout(this.timer)
    
    function onLoadPromise(img:HTMLImageElement) {
      if (img.complete) {
        return Promise.resolve(img.src);
      } else {
        const p = new Promise((success) => {
          img.onload = (e) => {
            success(img.src);
          };
        });
        return p;
      }
    }


    function getInitialInterval(t: number):Promise<string> {
        const f = new Promise(success =>{
          window.setTimeout(() => success("!"), t)
        })
        return f
      }


    if (this.props.preloads && this.props.preloads.length > 0) {

    let promises: Promise<string>[] = this.props.preloads.map(s => {
      const img = document.createElement("img")
      img.src = s
      return onLoadPromise(img)
    })

    promises.push(getInitialInterval(this.props.generals.firstDelay))

    Promise.all(promises).then((ignore) => this.nextSlide())
    
    } else this.timer = window.setTimeout(this.nextSlide, this.props.generals.firstDelay)
  }
    
  componentDidUpdate() {

    if(this.timer) window.clearTimeout(this.timer)
    this.timer = window.setTimeout(this.nextSlide, this.props.generals.delay)
  }

  componentWillUnmount() {
    if(this.timer) window.clearTimeout(this.timer)
  }


  
  render() {
    const slides = this.props.list
    return <div className="slider">
      {slides.map((slide, index, array) =>
        <Slide key={index} 
        style={slide.style} 
        className={slide.className} 
        text={slide.text} 
        containers={slide.containers} 
        active={index == this.state.active} 
        link={slide.link} /> 
       )}
       
       
         {
           this.props.generals.controlsType && <div className="controls">
            <div className="slider__next" onClick={this.nextSlide}>
              <i className="fa fa-4x fa-arrow-circle-right"></i>
            </div>
            <div className="slider__previous" onClick={this.previousSlide}>
              <i className="fa fa-4x fa-arrow-circle-left"></i>
            </div>
          </div>
         }
       </div>

  }
}

The method componentDidUpdate sets a new timeout for a call of the nextSlide method. It supports continuity of our slideshow.

The method componentDidMount does more sophisticated work: it calls the nextSlide method only if all images are loaded. Compare this with componentDidMount method from Scala.js example:

 

.componentDidMount(i => {
      i.backend.timer.map(c => window.clearTimeout(c))

      def onLoadFuture(img: HTMLImageElement) = {
        if (img.complete) {
          Future.successful(img.src)
        } else {
          val p = Promise[String]()
          img.onload = { (e: Event) => {

            p.success(img.src)
          }
          }
          p.future
        }
      }

      def getInitialInterval(t: Int) = {
        val p = Promise[String]()
        window.setTimeout(() => p.success("!"), t)
        p.future
      }


      val futures = i.props.preloads.map(s => {
        val img = document.createElement("img").asInstanceOf[HTMLImageElement]
        img.src = s
        onLoadFuture(img)
      }) :+ getInitialInterval(i.props.generals.firstDelay)

      Callback(Future.sequence(futures).onComplete(_ => {
        i.backend.nextSlide.runNow()
      }))
    })

Both examples have very similar code of this method. In Scala.js  the Future is used in cases where in JavaScript the Promise is used . But to convert a callback into a Future in Scala you need to use a Promise.

There is no the Backend class in the TypeScript example. But in general examples have very similar code. It seems to me, that the TypeScript variant is more elegant, but it’s the matter of taste.

The main difference occurs when you try to pass a JavaScript object as a Slider property. As you can remember I wrote a facade in Scala.js example (additional 130 lines of Scala code). From the other side, TypeScript uses “duck typing” and it works well: you don’t need any additional code. But of course you can use case classes in your Scala.js projects and be happy.

Size

Scala.js resulting JavaScript compressed files with all dependencies weight 344KB.

TypeScript bundle file weights 175KB.

It’s not crucial but much less.

Editor

I use IntelliJ IDEA for Scala and Visual Studio Code for TypeScript. Both editors are very good.

It seems to me that VS Code is faster. And there are no annoying bugs. For example:

I reported this issue three months ago, but nothing happened yet.

Conclusion

I think that Scala is the best programming language in the world. TypeScript possesses less expressiveness. It’s a fact.

From the other hand, TypeScript is JavaScript by nature. And it has almost absolute compatibility with a vast bulk of JavaScript libraries. Scala.js doesn’t. For example, look at Scala.js React library. It’s very good. But don’t you think, it’s cumbersome a bit? Compare, for instance:

  componentWillUnmount() {
    if(this.timer) window.clearTimeout(this.timer)
  }

and

.componentWillUnmount(i => 
        Callback(i.backend.timer.map(c => window.clearTimeout(c))))

And at last. There is an illusion that someone can successfully write Scala.js code without knowledge of JavaScript. No, he can’t. But if a Scala coder should learn JavaScript when he want to become a front-end developer, why wouldn’t he try TypeScript as well.

 

See the full code of this example.

 

 

 

 

About Alexandre Kremlianski

Scala / Scala.js / JavaScript programmer

Page with Comments

  1. I didn’t read the whole article , but in your conclusion

    componentWillUnmount() {
    if(this.timer) window.clearTimeout(this.timer)
    }

    vs
    .componentWillUnmount(i =>
    Callback(i.backend.timer.map(c => window.clearTimeout(c))))

    hmm thats one way of writing, if you want ES6 classes use sri : https://github.com/chandu0101/sri or just write your own facade(hardly 12 LOC)

    Using Sri :

    class Slider extends Component[Props,State] {
    def componentWillUnmount() = {
    if(this.timer) window.clearTimeout(this.timer)
    }
    }

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.