com-lihaoyi / scalatags

ScalaTags is a small XML/HTML construction library for Scala.
https://com-lihaoyi.github.io/scalatags/
MIT License
758 stars 117 forks source link

Implicit, type-safe conversions of value classes for attribute pairs #171

Closed mb720 closed 4 years ago

mb720 commented 7 years ago

Hi!

Despite the extensive title, my question is rather simple.

I happily use ScalaTags to generate HTML on the server. For type safety, I have introduced a little value class HtmlId that identifies an HTML element:

case class HtmlId(value: String) extends AnyVal {
  override def toString: String = value
}

Currently, I use it like this:

import scalatags.Text.all._

val myId = HtmlId("my-id")
div(id := myId.value)

I would like to use it like this:

import scalatags.Text.all._
import my.package.HtmlIdImplicitConversion

val myId = HtmlId("my-id")
// HtmlIdImplicitConversion is applied here so I don't need to call `.value`
div(id := myId)

The obvious thing to do is to have an implicit conversion from HtmlId to string:

implicit def implicitToString(id: HtmlId): String = id.value

I don't want to use that, though, since string is too general; I'd prefer to have a more type-safe conversion.

I'm not sure how to create such a conversion, maybe using ScalaTags' AttrPair or AttrValue?

Thanks!

CyberGear commented 6 years ago

My solution:

trait PageIds

object PageIds extends PageIds {
  val Content: PageId = "content"
}

case class PageId(id: String)

object PageId {
  implicit def boxToPageId(id: String): PageId = PageId(id)

  implicit def unboxToId(pid: PageId): String = s"#${pid.id}"

  implicit final def unboxToAttr(pid: PageId): Modifier = builder => {
    builder.setAttr("id", Builder.GenericAttrValueSource(pid.id))
  }
}

usage in scala js: jQuery(PageIds.Content) witch enup eq to: jQuery("#content")

usage in scala jvm scalatags: div(PageIds.Content, "some contents") witch results to: <div id="content">some contents</div>

sake92 commented 4 years ago

@mb720 @CyberGear

You just need to have an implicit AttrValue[T] in scope:


import scalatags.JsDom.all._

case class HtmlId(value: String) extends AnyVal {
  override def toString: String = value
}

implicit val htmlIdAttr = genericAttr[HtmlId] // it uses toString() of class...

div(
  cls := HtmlId("bla")
)("aaaaaaa")

https://scalafiddle.io/sf/m9bm6qx/0