Skip to content

Commit

Permalink
Merge pull request #20 from sherpal/master
Browse files Browse the repository at this point in the history
Publish with better mod api
  • Loading branch information
sherpal authored Aug 23, 2022
2 parents 7ce0fe3 + 9035fe3 commit 6d18675
Show file tree
Hide file tree
Showing 162 changed files with 945 additions and 1,066 deletions.
39 changes: 34 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ In order to use these bindings within your Scala.js project, you need to add the
```scala
resolvers += "jitpack" at "https://jitpack.io"

// if using scala 2.13
// if using scala 2.13 (see also warning section below)
scalacOptions ++= List("-Ytasty-reader")

libraryDependencies ++= List(
Expand Down Expand Up @@ -58,13 +58,31 @@ div(
_.required := true,
_.valueState := ValueState.Information,
_.placeholder := "Enter your name",
_ => onChange.mapToValue --> Observer(println)
onChange.mapToValue --> Observer(println)
)
)
```

This will be rendered as an input as documented [here](https://sap.github.io/ui5-webcomponents/playground/components/Input/).

### Remark for Scala 2.13 users

In order to achieve the highest ergonomics possible, the `apply` method of components uses a union type as argument. But this only would completely sacrifice the ability of Scala 2.13 users to use the library.

That is why all components also have an `of` method, only accepting mod functions (as opposed to accepting both mods and mod functions). This reduces the ergonomics a bit for Scala 2.13, but it allows us to move forward.

For you, concretely, instead of having, for example,

```scala
Input(_.required := true, onChange.mapToValue --> Observer(println))
```

you will have to write

```scala
Input.of(_.required := true, _ => onChange.mapToValue --> Observer(println))
```

### Running the Demo

The project contains a demo file for each component. These examples are located in the `demo` sub-project.
Expand Down Expand Up @@ -165,15 +183,14 @@ Fill in the docstring for that object with the contents of the "Overview" sectio
In the `object`, add the following things:
- make the object extends `WebComponent`
- create a trait `RawElement` extending `js.Object` and annotated with `@js.native`
- add an object `RawImport` extending `js.Object` and annotated with both `@js.native` and `@JSImport`, specifying the correct import (available in the official docs), setting `JSImport.Default` as second argument
- call `used(RawImport)` the line after (this is done to be sure that scala-js actually import the JS dependency)
- define an alias `type Ref` as `dom.html.Element & RawElement`
- define an alias `type ModFunction` as `YourObject.type => Mod[ReactiveHtmlElement[Ref]]`
- define a private `tag` variable of type `HtmlTag[Ref]` specifying the ui5 tag name from the doc (for example, for the Button component, it's `private val tag: HtmlTag[Ref] = customHtmlTag("ui5-button")`). ⚠️: when copy-pasting from an existing component, this is usually the one we forget! When that happens, you will observe a component doing basically nothing. It's a sign you put the wrong import.
- define the protected `tag` variable of type `HtmlTag[Ref]` specifying the ui5 tag name from the doc (for example, for the Button component, it's `protected val tag: HtmlTag[Ref] = customHtmlTag("ui5-button")`). ⚠️: when copy-pasting from an existing component, this is usually the one we forget! When that happens, you will observe a component doing basically nothing. It's a sign you put the wrong import.
- create an empty object `slots`
- create an empty object `events`
- define an apply method as `def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(YourObject)): _*)`
- in the case where your component is linked to other components (for example a `TableCell` is always contained in `TableRow`, so the `TableRow` object will have a reference to the `TableCell` object)

#### Filling the reactive attributes
Expand Down Expand Up @@ -257,3 +274,15 @@ object RawElement {
def colour: Colour = Colour.fromString(rawElement.colourJS)
}
```

#### Making the demos

The best way to make sure that your implementations work, and to also help future users at the same time, is to write a demo for the new component.

In the `demo` subproject, in the `demo` package, create an object `MyComponentExample`, extending `Example` with the SAP name of the component.

You will then need to fill the `component` def. You should make use of the `DemoPanel` and wrapping your example codes in `//-- Begin: name of your demo panel` and `//-- End`. Once your code will be merged on master, it will make the source code directly displayed beneath the examples.

You then need to add your example in the list in the `EntryPoint` in the `demo` package.

For the strucutre, you can take inspiration from the existing other examples. For the content of your demo, you can take inspiration from the official SAP docs.
4 changes: 2 additions & 2 deletions demo/src/main/scala/demo/AvatarExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ object AvatarExample extends Example("Avatar") {
): HtmlElement = div(
DemoPanel("Basic examples") {
//-- Begin: Basic examples
div(Avatar(_ => sherpal), Avatar(_.shape := AvatarShape.Square, _ => sherpal))
div(Avatar(sherpal), Avatar(_.shape := AvatarShape.Square, sherpal))
//-- End
},
DemoPanel("Avatar sizes") {
//-- Begin: Avatar sizes
div(
AvatarSize.allValues
.zip(MTG.manaSymbolsShortNames)
.map((size, mana) => Avatar(_.size := size, _ => manaSymbolImage(mana)))
.map((size, mana) => Avatar(_.size := size, manaSymbolImage(mana)))
)
//-- End
},
Expand Down
10 changes: 5 additions & 5 deletions demo/src/main/scala/demo/AvatarGroupExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ object AvatarGroupExample extends Example("AvatarGroup") {
//-- Begin: Avatar Group - type 'Individual'
AvatarGroup(
_.tpe := AvatarGroupType.Individual,
_ => IconName.allValues.take(5).map(icon => Avatar(_.icon := icon, _.size := AvatarSize.M))
IconName.allValues.take(5).map(icon => Avatar(_.icon := icon, _.size := AvatarSize.M))
)
//-- End
},
DemoPanel("Avatar Group - type 'Group'") {
//-- Begin: Avatar Group - type 'Group'
AvatarGroup(
_.tpe := AvatarGroupType.Group,
_ => IconName.allValues.take(5).map(icon => Avatar(_.icon := icon, _.size := AvatarSize.M))
IconName.allValues.take(5).map(icon => Avatar(_.icon := icon, _.size := AvatarSize.M))
)
//-- End
},
Expand All @@ -35,14 +35,14 @@ object AvatarGroupExample extends Example("AvatarGroup") {
div(
AvatarGroup(
_.tpe := AvatarGroupType.Individual,
_ => allAvatars,
_ => width := "400px",
allAvatars,
width := "400px",
_.events.onClick.map(_.target.hiddenItems.length).map(allAvatars.takeRight) --> avatarsForPopoverBus.writer,
_.events.onClick.filter(_.detail.overflowButtonClicked).map(_.detail.targetRef) --> popoverOpenerBus.writer
),
Popover(
_.showAtFromEvents(popoverOpenerBus.events),
_ => children <-- avatarsForPopoverBus
children <-- avatarsForPopoverBus
)
)
//-- End
Expand Down
6 changes: 3 additions & 3 deletions demo/src/main/scala/demo/BadgeExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ object BadgeExample extends Example("Badge") {
div(
ColourScheme.allValues
.zip(someTexts)
.map((colourScheme, text) => Badge(_.colourScheme := colourScheme, _ => text)),
Badge(_ => width := "200px", _ => "This text is very long and it will be truncated with ellipsis")
.map((colourScheme, text) => Badge(_.colourScheme := colourScheme, text)),
Badge(width := "200px", "This text is very long and it will be truncated with ellipsis")
)
//-- End
),
DemoPanel("Badge with icon")(
//-- Begin: Badge with icon
div(
Badge(_.colourScheme := ColourScheme._1, _.slots.icon := Icon(_.name := IconName.add), _ => "Add"),
Badge(_.colourScheme := ColourScheme._1, _.slots.icon := Icon(_.name := IconName.add), "Add"),
Badge(_.colourScheme := ColourScheme._2, _.slots.icon := Icon(_.name := IconName.customer))
)
//-- End
Expand Down
8 changes: 4 additions & 4 deletions demo/src/main/scala/demo/BarExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ object BarExample extends Example("Bar") {
_.tooltip := "Go home",
_.design := ButtonDesign.Transparent
),
_ => Label(_ => title),
_ => Label(title),
_.slots.endContent := Button(_.icon := IconName.`action-settings`, _.tooltip := "Go to settings")
)

private def footerBarContent = List[Bar.ModFunction](
_.slots.endContent := Button(_.design := ButtonDesign.Positive, _ => "Agree"),
_.slots.endContent := Button(_.design := ButtonDesign.Negative, _ => "Decline"),
_.slots.endContent := Button(_.design := ButtonDesign.Transparent, _ => "Cancel")
_.slots.endContent := Button(_.design := ButtonDesign.Positive, "Agree"),
_.slots.endContent := Button(_.design := ButtonDesign.Negative, "Decline"),
_.slots.endContent := Button(_.design := ButtonDesign.Transparent, "Cancel")
)

def component(using
Expand Down
8 changes: 4 additions & 4 deletions demo/src/main/scala/demo/BarcodeScannerDialogExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ object BarcodeScannerDialogExample extends Example("BarcodeScannerDialog") {
_.events.onScanSuccess.map(_.detail) --> barcodeScanSuccessBus.writer,
_.events.onScanError.map(_.detail) --> barcodeScanFailedBus.writer
),
Title.h3(_ => "Click on the button below and show a QR code to the camera:"),
Title.h3("Click on the button below and show a QR code to the camera:"),
Button(
_.icon := IconName.camera,
_.tooltip := "Start Camera",
_ => "Scan",
"Scan",
_.events.onClick.mapTo(()) --> openScannerBus.writer
),
div(Label(_ => child.text <-- barcodeScanSuccessBus.events.map(_.text))),
div(Label(_ => child.text <-- barcodeScanFailedBus.events.map(_.message)))
div(Label(child.text <-- barcodeScanSuccessBus.events.map(_.text))),
div(Label(child.text <-- barcodeScanFailedBus.events.map(_.message)))
)
//-- End
}
Expand Down
16 changes: 8 additions & 8 deletions demo/src/main/scala/demo/BreadcrumbsExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ object BreadcrumbsExample extends Example("Breadcrumbs") {
_.Item(
_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings",
_.target := LinkTarget._blank,
_ => "Root page"
"Root page"
),
_.Item(_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings", _ => "Parent page"),
_.Item(_ => "Current page"),
_.Item(_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings", "Parent page"),
_.Item("Current page"),
_.events.onItemClick.map(_.detail.item) --> Observer(x => org.scalajs.dom.console.log(x))
)
//-- End
Expand All @@ -30,9 +30,9 @@ object BreadcrumbsExample extends Example("Breadcrumbs") {
_.Item(
_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings",
_.target := LinkTarget._blank,
_ => "Root page"
"Root page"
),
_.Item(_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings", _ => "Parent page")
_.Item(_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings", "Parent page")
)
//-- End
),
Expand All @@ -45,10 +45,10 @@ object BreadcrumbsExample extends Example("Breadcrumbs") {
_.Item(
_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings",
_.target := LinkTarget._blank,
_ => "Root page"
"Root page"
),
_.Item(_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings", _ => "Parent page"),
_.Item(_ => "Current page")
_.Item(_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings", "Parent page"),
_.Item("Current page")
)
)
)
Expand Down
13 changes: 6 additions & 7 deletions demo/src/main/scala/demo/BusyIndicatorExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,19 @@ object BusyIndicatorExample extends Example("BusyIndicator") {
val fetchedData = numberOfDataToFetch.map(allData.take)
val busyStates = EventStream
.merge(
fetchListDataBus.events.map(_ => 1),
numberOfDataToFetch.changes.map(_ => -1)
fetchListDataBus.events.mapTo(1),
numberOfDataToFetch.changes.mapTo(-1)
)
.foldLeft(0)(_ + _)
.map(_ > 0)
div(
div(Button(_ => "Fetch list data", _.events.onClick.mapTo(()) --> fetchListDataBus.writer)),
div(Button("Fetch list data", _.events.onClick.mapTo(()) --> fetchListDataBus.writer)),
div(
BusyIndicator(
_.active <-- busyStates,
_ =>
UList(
_ => width := "400px",
_ => children <-- fetchedData.map(data => data.map(pieceOfData => UList.item(_ => pieceOfData))),
UList(
width := "400px",
children <-- fetchedData.map(data => data.map(pieceOfData => UList.item(pieceOfData))),
_.noDataText := "No Data"
)
)
Expand Down
26 changes: 13 additions & 13 deletions demo/src/main/scala/demo/ButtonExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,26 @@ object ButtonExample extends Example("Button") {
//-- Begin: Basic Button
div(
className := exampleButtonContainerClass,
Button(_.design := ButtonDesign.Default, _ => "Default"),
Button(_.disabled := true, _ => "Disabled"),
Button(_.design := ButtonDesign.Transparent, _ => "Cancel"),
Button(_.design := ButtonDesign.Positive, _ => "Approve"),
Button(_.design := ButtonDesign.Negative, _ => "Decline"),
Button(_.design := ButtonDesign.Attention, _ => "Warning"),
Button(_.design := ButtonDesign.Emphasized, _ => "Subscribe")
Button(_.design := ButtonDesign.Default, "Default"),
Button(_.disabled := true, "Disabled"),
Button(_.design := ButtonDesign.Transparent, "Cancel"),
Button(_.design := ButtonDesign.Positive, "Approve"),
Button(_.design := ButtonDesign.Negative, "Decline"),
Button(_.design := ButtonDesign.Attention, "Warning"),
Button(_.design := ButtonDesign.Emphasized, "Subscribe")
)
//-- End
),
DemoPanel("Button with Icon")(
//-- Begin: Button with Icon
div(
className := exampleButtonContainerClass,
Button(_.icon := IconName.employee, _ => "Add"),
Button(_.icon := IconName.download, _ => "Download"),
Button(_.design := ButtonDesign.Positive, _.icon := IconName.add, _ => "Add"),
Button(_.design := ButtonDesign.Negative, _.icon := IconName.delete, _ => "Remove"),
Button(_.design := ButtonDesign.Attention, _.icon := IconName.`message-warning`, _ => "Warning"),
Button(_.design := ButtonDesign.Transparent, _.icon := IconName.accept, _ => "Transparent")
Button(_.icon := IconName.employee, "Add"),
Button(_.icon := IconName.download, "Download"),
Button(_.design := ButtonDesign.Positive, _.icon := IconName.add, "Add"),
Button(_.design := ButtonDesign.Negative, _.icon := IconName.delete, "Remove"),
Button(_.design := ButtonDesign.Attention, _.icon := IconName.`message-warning`, "Warning"),
Button(_.design := ButtonDesign.Transparent, _.icon := IconName.accept, "Transparent")
)
//-- End
),
Expand Down
5 changes: 2 additions & 3 deletions demo/src/main/scala/demo/CalendarExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ object CalendarExample extends Example("Calendar") {
//-- Begin: Basic Calendar
val maybeSelectedDateVar: Var[Option[Calendar.events.SelectedDatesChangeInfo]] = Var(None)
div(
Label(_ =>
child.text <-- maybeSelectedDateVar.signal.map {
Label( child.text <-- maybeSelectedDateVar.signal.map {
case None => "No selected date yet."
case Some(info) => s"Selected dates: ${info.values.zip(info.dates).mkString(", ")}."
}
Expand Down Expand Up @@ -51,7 +50,7 @@ object CalendarExample extends Example("Calendar") {
// (for example where your application starts, or the first place you use this calendar type)
calendarType.importObject
div(
Title.h3(_ => s"Type of calendar: ${calendarType.value}"),
Title.h3(s"Type of calendar: ${calendarType.value}"),
Calendar(_.primaryCalendarType := calendarType)
)
})
Expand Down
Loading

0 comments on commit 6d18675

Please sign in to comment.