Prerequisites

You will need an sbt installation (scala-sbt.org/0.13/docs/) You will also need an installation of node (nodejs.org)

Compiling the SP frontend

To install javascript dependencies, cd to npmdependencies/ and run npm install.

To compile the scalaJS code, run sbt fastOptJS. To compile the optimized version run sbt fullOptJS (slow process, not recommended in development).

Running SP frontend only

For development purposes, the frontend can be run without backend and with live reloading using lihaoyi's workbench. To do this, run sbt ~fastOptJS and open http://localhost:12345/js/target/scala-2.12/classes/index.html in a browser.

Making a widget

The simplest possible widget is created with SPWidget(spwb => <.h1("Hello, World!")). So to get started making a widget, create a file in widgets/ containing the following.

package spgui.widgets

import spgui.SPWidget

object MyWidget {
  def apply() = SPWidget(spwb => <.h1("Hello from MyWidget"))
}

(Defining apply is just the scala way of making your object callable with MyWidget().) To make your new widget available in the SP-menu, open WidgetList.scala and add it like so: ("My new widget", spgui.MyWidget()), next to the other widgets.

The argument to the function given as argument to SPWidget, above named spwb, provides the API to interact with SP. For example it contains access to a string of data stored in the browser storage, via the field data: String and the method saveData(data: String). This is conveniently used together with a case class, upickle and Try, as in the example below (found in the code in widgets/examples/).

object WidgetWithData {
  // calling MyData() (with no arguments) will give MyData(someInt = -17)
  case class MyData(someInt: Int = -17)

  def apply() = SPWidget{spwb =>
    // upickle's read tries to turn the string in browser storage into a MyData-instance
    // if there is nothing there or casting fails, creates the standard instance instead
    val myData = Try(read[MyData](spwb.data)).getOrElse(MyData())
    val theInt = myData.someInt

    // upickle's write turns a new version of myData into a string that is saved in storage
    def increment = Callback(spwb.saveData(write(myData.copy(theInt + 1))))

    <.div(
      <.h3("count is " + theInt),
      <.button("increment", ^.onClick --> increment),
      <.p("this piece of data is stored in the browser")
    )
  }
}

Note that no explicit re-rendering is necessary after calling saveData. This is handled automatically.

The case class can contain anything data-ish, i.e. strings, doubles, ints, lists of them as well as nested data-ish case classes.

README-TODO: add more about what's inside spwb here.

The html-like scala-objects prefixed by < and ^ are provided by the scalajs-react library. The function given as argument to SPWidget need to return either an <-object or a scalajs-react component. Learn about scalajs-react [here] (https://github.com/japgolly/scalajs-react/blob/master/doc/USAGE.md).

JavaScript dependencies

JS dependencies are handled by npm and made available through a bundle file generated with webpack. To add a JS dependency, go to npmdependencies/, add it to package.json and vendor.js, then run npm install.