Git Product home page Git Product logo

scala-dom-types's Introduction

Scala DOM Types

Build status Chat on https://discord.gg/JTrUxhq7sj Maven Central

Scala DOM Types provides listings of Javascript HTML and SVG tags as well as their attributes, DOM properties, and CSS styles, including the corresponding type information.

"com.raquo" %% "domtypes" % "<version>"     // JVM & SBT
"com.raquo" %%% "domtypes" % "<version>"    // Scala.js 1.14.0

Scala DOM Types is used by the following Scala.js UI libraries:

As well as by:

As the end-user of these libraries, you do not depend on Scala DOM Types at runtime, those libraries use it at their compile time (for SDT v17.0.0+).

Table of Contents

Community

Please use Github issues for bugs, feature requests, as well as all kinds of discussions, including questions on usage and integrations. You can watch this project on Github to get issue updates if you're interested in following discussions.

Contributing

Q: I want to add an element tag / attribute / prop / etc.

A: Awesome! It might seem daunting the first time, but it's not hard. Here's how to add a new key (i.e. tag / property / attribute / event / etc.):

  1. Find the documentation for it on MDN, for example gap.

  2. Confirm that it's reasonably well supported by browsers. Support by latest Firefox and Chrome is the bare minimum. example.

  3. If it's a property or an attribute, figure out whether it's: (a) non-reflected attribute, (b) non-reflected property, or (c) reflected property. The latter are pretty common. Read the MDN docs, read the docs below, and see other reflected properties we defined for reference.

  4. If applicable, figure out the type of values that this attribute / property / etc. accepts, or the type of events that it fires. See MDN docs for that. Note that we only care about the type that we can write into it, not the type we can read from it (the latter often includes null or js.undefined). See what codec(s) our other properties of the same type use, it's probably an "as-is" codec like StringAsIsCodec. See the docs on codecs below.

  5. Figure out what the new key should be called, according to the naming convention documented below.

  6. You now have enough information to easily test your understanding. For Laminar, try using htmlAttr / htmlProp / styleProp / eventProp / etc. locally as suggested here. For example: styleProp("gap") := "20px". For other UI libraries using Scala DOM Types, see their docs.

  7. If everything is looking good, you can now add the information necessary to create an SDT definition for this key. Add it to one of the traits in the shared/main/.../defs folder. Look at how other keys are done, and follow the lead. CSS props require a bit more annotation than others. Look at our defs for other CSS props of the same type to see which valueTraits, valueUnits, and implName to specify.

  8. Run sbt test before committing. This will make a few sanity checks, and generate sample code found in js/test/.../defs. You should commit that generated code too.

  9. And that's it. Send this PR, and I'll check everything (make sure to provide links to MDN docs!). You are not blocked by SDT releases – just keep using the temporary syntax from step 6 until the thing you've added to SDT lands in Laminar / Calico / etc.

If this is too much for you right now, or if you're not sure about something, open an issue or ask on Laminar discord.

Why use Scala DOM Types

Canonical use case: you're writing a Scala or Scala.js library that does HTML / DOM construction / manipulation and want to provide a type-safe API like this:

div(
  zIndex := 9000,
  h1(rel := "title", "Hello world"),
  p(
    backgroundColor := "red",
    "Welcome to my fancy page!",
    span(draggable := true, "Fancyness is important.")
  ),
  button(onClick := doFancyThing, "Do Fancy Thing"),
  a(href := "http://example.com", title := "foo", "Example")
)

Of course, your API doesn't need to look anything like this, that's just an example. Scala DOM Types doesn't actually provide the Tag.apply and := methods that you'd need to make this example work.

If you do in fact want to create similar syntax, see guidelines for library authors below.

What about ScalaTags

ScalaTags is a popular Scala library that contains DOM type definitions similar to what we have here. However, Scala DOM Types is different in a few ways:

  • More type safe. For example, in Scala DOM Types an input tag is linked to Scala.js HTMLInputElement class. This lets you provide exact types for the DOM nodes you create, so that you don't need to perform unsafe casts in your application code if you want to e.g. access the value property on an input you created. Similarly, all attributes, properties and styles are linked to the types that they accept to prevent you from assigning incorrect values.

  • More flexible. Scala DOM Types does not tell you how to define your attributes / props / styles / tags, or how to compose them together, and does not enforce any rendering paradigm. You are free to implement your own composition. I see that some projects fork ScalaTags just to get the type definitions without everything else. Scala DOM Types does not get in your way, eliminating the need for such forking.

  • Better representation of native DOM types. Scala DOM Types handles Reflected Attributes consistently, and uses Codecs to properly encode/decode DOM values.

There are some other differences, for example Scala DOM Types uses camelCase for attr / prop / style names because that is consistent with common Scala style.

What about scala-js-dom

The scala-js-dom project serves a very different purpose – it provides typed Scala.js interfaces to native Javascript DOM classes such as HTMLInputElement. You can use those types when you already have instances of DOM elements, but you can not instantiate those types without using untyped methods like document.createElement because that is the only kind of API that Javascript provides for this.

On the other hand, Scala DOM Types lets the consuming library create a type-safe representation of real JS DOM nodes or trees, and it is up to your library's code to instantiate real JS nodes from the provided description.

Oh, and Scala DOM Types does work on the JVM. Obviously you can't get native JS types there, but you can provide your own replacements for specific Scala.js types, or just not bother with such specificity at all.

Design Goals

The purpose of Scala DOM Types is to become a standard DOM types library used in Scala.js projects.

Precise Types

The most important type information must be encoded as Scala types. For example, DOM properties that only accept integers should be typed as such.

Reasonably Precise Types

The types we provide will never be perfect. For example, MDN has this to say about the list attribute:

The value must be the id of a element in the same document. [...] This attribute is ignored when the type attribute's value is hidden, checkbox, radio, file, or a button type.

A far as I know, encoding such constraints as Scala types would be very hard, if it's even possible at all.

This is not to say that we are content with the level of type safety we currently have in Scala DOM Types. Improvements are welcome as long as they provide significantly more value than burden to users of this library. This kind of thing is often subjective, so I suggest you open an issue for discussion first.

Flexibility

Scala DOM Types is a low level library that is used by other libraries. As such, its API should be unopinionated and focused solely on providing useful data about DOM elements / attributes / etc. to consuming libraries in a way that is easy for them to implement.

We achieve this with a code generation approach. Instead of providing Scala traits in a predefined format, we give you tools to generate such traits in your own library, with your desired data structures, types, naming conventions, etc.

You can also use the raw element / attribute / etc. data contained Scala DOM Types yourself, whether at compile time or at runtime.

Sanity Preservation Measures

We should provide a better API than the DOM if we can do that in a way that keeps usage discoverable and unsurprising.

Developers familiar with the DOM API should generally be able to discover the names of attributes / tags / etc. they need using IDE autocompletion (assuming they expect the names to match the DOM API). For example: forId is a good name for the for attribute. It avoids using a Scala reserved word, and it starts with for like the original attribute, so it's easy to find. It also implies what kind of string is expected for a value (an id of an element).

Within that constraint, we should also try to clean up the more insane corners of the DOM API.

  • For example, the difference between value attribute vs value property trips up even experienced developers all the time. Scala DOM Types on the other hand has a defaultValue reflected attribute and a value property, which behave the way everyone would expect from the given names or from their knowledge of the DOM API.
  • For another example, enumerated attributes like contentEditable that in the DOM accept "true" / "false" or "on" / "off" or "yes" / "no" should be boolean attributes in Scala DOM Types.

All naming differences with the DOM API should be documented in the README file (see below). Type differences are generally assumed to be self-documenting.

Documentation

API doc

How to Use Scala DOM Types in Your Library

You generally don't want to use Scala DOM Types directly as the end-user. If you just want to generate some HTML on the backend or something similarly simple, you might want to use ScalaTags instead, or create a new library for that based on Scala DOM Types using the guide below.

So, you're building a DOM manipulation library such as Laminar, Outwatch or ScalaJS-React (the former two use Scala DOM Types, the latter doesn't). This guide focuses on the Scala.js use case. Scala DOM Types is perfectly usable from the backend as well, but it will need more customization.

First off, if you're building such a library, you need to know quite a few things about how JS DOM works. Scala DOM Types is just a collection of type information, it's not an abstraction layer for the DOM. You're building the abstraction layer. We can't cover everything about JS DOM here, but we will touch on some of the nastier parts in the following sections.

  1. Look at MouseEventPropDefs in Scala DOM Types – several of such listings contain all the data that this library offers. This particular file lists all the mouse-related events that you can handle in the DOM. We create such listings manually. See discussion in #87 and #47 for why we don't generate these listings from some official source.

  2. The data in MouseEventPropDefs can be used as-is in certain cases, but typically we want to transform it into well typed Scala traits that look like GlobalEventProps. In fact, prior to #87, such typed traits were the only format in which Scala DOM Types offered its data. For example, here's the old MouseEventProps from Scala DOM Types version 0.16.0-RC3. As you can see, to make such a trait flexible enough for different libraries and runtimes, we had to use a lot of type params – not ideal, especially for end users who just want to see e.g. the type of events a certain key produces.

  3. The new version of Scala DOM Types relies on code generation to produce simple abstraction-free traits like GlobalEventProps, tailored for a specific UI library like Laminar. That GlobalEventProps file was in fact produced by this code generator as part of Scala DOM Types GeneratorSpec test, and its output is verified in CompileSpec.

    Previously, Scala DOM Types offered highly abstracted traits as a runtime dependency of libraries like Laminar. Now, Laminar uses Scala DOM Types at compile time only, generating similar traits at compile time.

    In Laminar, the code generation is done in DomDefsGenerator. As you see, the generator is customized with the names of Laminar's own types, package names, and desired folder structure. See Laminar's build.sbt and project/build.sbt for the compile-time generator build setup.

    You will need to create a similar generator setup for your library.

  4. There are several ways to customize Scala DOM Types code generation. Simpler ones first:

    1. Provide different params to CanonicalGenerator's constructor

    2. Provide different params to CanonicalGenerator's generate*Trait methods

      (Including by transforming the list of defs that you pass to them)

    3. Instantiate TraitGenerator subclasses manually instead of calling generate*Trait methods

    4. Override CanonicalGenerator's methods

    5. Extend individual *TraitGenerator classes, and override their methods

    6. Create your own generator, perhaps by extending TraitGenerator or SourceGenerator

    Typical usage of Scala DOM Types should not require overly-involved customization effort. If your Scala.js use case seems unnecessarily hard to achieve, please let me know.

  5. Provide the keys that are deliberately missing from Scala DOM Types

    We deliberately do not include a small set of "complex" keys that UI libraries tend to have different opinions about, such as the class and style HTML attributes. See the full list below. Your library needs to provide such keys itself, for example see ComplexHtmlKeys and ComplexSvgKeys in Laminar – those are not generated, but manually created.

  6. Provide the Codecs. These are used to translate between Scala values and DOM values. See codecs in Laminar. Your implementation will be almost identical, depending on whether you talk to the DOM directly or via some virtual DOM library with special needs. See below for more info on the codecs.

  7. Provide concrete types for Tags, Attributes, etc., as well as their functionality (apply and := methods, etc.). The type representing StyleProp should extend the GlobalKeywords generated style trait, or provide those keywords in some other way.

  8. Finally, create "the bundle". You've generated a bunch of well typed traits and created concrete types – now you need to instantiate a single object that will extend all those traits to expose all the keys like div, onClick, etc. The actual implementation of this might vary based on your preferences and on how you configured the generator, but you can refer to the top of the Laminar.scala file. As you see, I separate HTML keys from SVG keys and ARIA keys to avoid name collisions and to reduce IDE autocomplete pollution. You can choose to do this differently, but that will require some customization on your part.

  9. With the generator, you're adding comments derived from MDN content into your project – those comments are licensed under the CC-BY-SA license, so you need to add a corresponding notice to your project file (or customize code generation to not include the comments for every key). See the bottom of this README.

Migrating to code generation from an older version of Scala DOM Types

  1. Follow the guide above to set up a generator in your project as explained above

  2. There is no built-in support for TypeTargetEvent anymore – just native JS types.

    You can implement / customize that in your project if you wish, but this isn't useful enough IMO.

  3. CSS styles now have support for unit helpers – e.g. extensions like paddingTop.px or width.calc("20px + 10%"), however you need to implement all that behaviour, and copy-paste the unit traits into your code – see the units in Laminar for example.

Reflected Attributes

HTML attributes and DOM properties are different things. As a prerequisite for this section, please read this StackOverflow answer first.

For more on this, read Section 2.6.1 of this DOM spec. Note that it uses the term "IDL attributes" to refer to what we call "DOM properties", and "Content attributes" to refer to what we here call "HTML attributes".

So with that knowledge, id for example is a reflected attribute. Setting and reading it works exactly the same way regardless of whether you're using the HTML attribute id, or the DOM property id. Such reflected attributes live in ReflectedHtmlAttrs trait, which lets you build either attributes or properties depending on what implementation of ReflectedHtmlAttrBuilder you provide.

To keep you sane, Scala DOM Types reflected attributes also normalize the DOM API a bit. For example, there is no value attribute in Scala DOM Types. There is only defaultValue reflected attribute, which uses either the value HTML attribute or the defaultValue DOM property depending on how you implement ReflectedHtmlAttrBuilder. This is because that attribute and that property behave the same even though they're named differently in the DOM, whereas the value DOM property has different behaviour (see the StackOverflow answer linked above). A corresponding HTML attribute with such behaviour does not exist, so in Scala DOM Types the value prop is defined in trait Props. It is not an attribute, nor is it a reflected attribute.

Reflected attributes may behave slightly differently depending on whether you implement them as props or attributes. For example, in HTML5 the cols reflected attribute has a default value of 20. If you read the col property from an empty <textarea> element, you will get 20. However, if you try to read the attribute col, you will get nothing because the attribute was never explicitly set.

Note that Javascript DOM performs better for reading/writing DOM props than reading/writing HTML attributes.

Codecs

Scala DOM Types provides some normalization of the native HTML / DOM API, which is crazy in places.

For example, there are a few ways to encode a boolean value into an HTML attribute:

  1. As presence of the attribute – if attribute is present, true, else false.
  2. As string "true" for true, or "false" for false
  3. As string "yes" for true, or "no" for false.

Which one of those you need to use depends on the attribute. For example, attribute disabled needs option #1, but attribute contenteditable needs option #2. And then there are DOM Properties (as opposed to HTML Attributes) where booleans are encoded as actual booleans.

Similarly, numbers are encoded as strings in attributes, with no such conversion when working with properties.

Scala DOM Types coalesces all these differences using codecs. When implementing a function that builds an attribute, you get provided with the attribute's name (key), datatype, and a codec that knows how to encode / decode that datatype into a value that should be passed to Javascript's native DOM API.

For example, the codecs for the three boolean options above are BooleanAsPresence, BooleanAsTrueFalseString, and BooleanAsYesNoString.

Scala DOM Types provides a reference implementation of the codecs. Since you only use Scala DOM Types at compile time, you should copy-paste that implementation into your own library, instead of trying to load Scala DOM Types as a runtime dependency.

Complex Keys

Properties like className often require special handling in consuming libraries. For example, instead of a String based interface, you might want to offer a Seq[String] based one for className. Because there is little to standardize on, Scala DOM Types deliberately does not provide those keys anymore. You need to add them to your library manually.

List of complex keys:

  • class, role, rel, style HTML attributes
  • data-* HTML attributes
  • class and rel SVG attributes

Naming Differences Compared To Native HTML & DOM

Although each library using Scala DOM Types is free to generate whatever code it wants, we provide a canonical scalaName for every key that we recommend using. It is sometimes different from the native DOM name (domName).

Below are the scalaName-s of the DOM attributes / props / etc. For the record, Laminar uses these names verbatim.

General

  • All scalaName identifiers are camelCased for consistency with conventional Scala style, e.g. datalist domName translates to dataList scalaName.

Attributes & Props

  • value attribute is named defaultValue because native HTML naming is misleading and confusing (example)
    • Note that the value property retains its name
  • checked attribute is named defaultChecked for the same reason
    • Note that the checked property retains its name
  • selected attribute is named defaultSelected for the same reason
    • Note that the selected property retains its name
  • for attribute and htmlFor property are available as reflected attribute forId for consistency and to avoid Scala reserved word
  • id reflected attribute is named idAttr, max attribute is maxAttr, min is minAttr, and step is stepAttr to free up good names for end user code
  • name attribute is named nameAttr to free up a good name
  • offset and result SVG attributes are named offsetAttr and resultAttr respectively to free up good names for end user code
  • loading reflected HTML attribute is named loadingAttr to avoid using a good name
  • content attribute is named contentAttr to avoid using a common name
  • form attribute is named formId to avoid conflict with form tag
  • label attribute is named labelAttr to avoid conflict with label tag
  • height attribute is named heightAttr to avoid conflict with height CSS property
  • width attribute is named widthAttr to avoid conflict with width CSS property
  • list attribute is named listId for clarity and consistency
  • contextmenu attribute is named contextMenuId for clarity and consistency

CSS Style Props

  • content prop is named contentCss to avoid using a common name

Tags

  • Many tag names have a "Tag" suffix, usually to free up good names for end user code, or avoid some conflict, e.g.:
    • html -> htmlRootTag, style -> styleTag, link -> linkTag, param -> paramTag, map -> mapTag, title -> titleTag, etc.

Aliases

  • Attribute type == typ == tpe to avoid Scala reserved word

Special keys

Certain special keys are not defined in Scala DOM Types, and are left for the consuming library to define. Of those, typically:

  • class attribute is named className and aliased as cls
  • the style attribute is named styleAttr

My Related Projects

  • Laminar – Reactive UI library based on Scala DOM Types
  • Scala DOM TestUtils – Test that your Javascript DOM nodes match your expectations

Author

Nikita Gazarov – @raquo

License and Credits

Scala DOM Types is provided under the MIT license.

Files in the defs directory contain listings of DOM element tags, attributes, props, styles, etc. – Those were originally adapted from Li Haoyi's ScalaTags, which is also MIT licensed.

Comments pertaining to individual DOM element tags, attributes, properties and event properties, as well as CSS properties and their special values / keywords, are taken or derived from content created by Mozilla Contributors and are licensed under Creative Commons Attribution-ShareAlike license (CC-BY-SA), v2.5.

scala-dom-types's People

Contributors

ajablonski avatar ajaychandran avatar armanbilge avatar busti avatar cornerman avatar cyz1901 avatar deterdw avatar doofin avatar fabiopinheiro avatar fdietze avatar ioleo avatar lasering avatar lolgab avatar mariusmuja avatar mprihoda avatar nghuuphuoc avatar nikita-wcsc avatar raquo avatar weihsiu avatar yurique avatar zakpatterson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scala-dom-types's Issues

Switch definitions from lazy vals to vals?

Scala.js 1.11 (news) now removes unused fields.

If I understand correctly, this means that we can have e.g. val div = htmlTag("div") instead of it being a lazy val, and if you never use div-s in your app, div won't be included in the JS bundle, similar to how it works today with lazy vals.

This would be great for us because having a hundred lazy vals for all the tags and properties that you use obviously comes with some overhead, and even if it's not really noticeable, it would be great to simplify.

However, we need to test whether the Scala.js optimizer is smart enough to recognize our implementations of def htmlTag as "pure", and thus removable, otherwise we won't win anything. For Laminar the implementation instantiates a class HtmlTag which inherits from a trait with two public val-s, the constructor doesn't do anything else other than set those vals. For Outwatch and other consuming libraries, I'm not sure, but I suspect it's something similar. I'm hoping all our implementations are pure enough.

RFC: Use concrete types instead of generic type params?

Currently, the design of SDT type listings is maximally generic, providing a lot of flexibility to esoterically minded library authors.

The issues below mostly apply to all types of keys – props, attrs, css style props, and event props, but we'll be looking at specific event props as a concrete example:

trait MouseEventProps[
  EP[_ <: DomEvent],
  DomEvent,
  DomMouseEvent <: DomEvent,
  DomElementMouseEvent <: DomMouseEvent,
  DomDragEvent <: DomMouseEvent,
  DomWheelEvent <: DomMouseEvent
] { this: EventPropBuilder[EP, DomEvent] =>

    /**
    * The click event is raised when the user clicks on an element. The click
    * event will occur after the mousedown and mouseup events.
    *
    * MDN
    */
  lazy val onClick: EP[DomElementMouseEvent] = eventProp("click")

  // ... more props here

  /**
    * Fires when the mouse wheel rolls up or down over an element
    */
  lazy val onWheel: EP[DomWheelEvent] = eventProp("wheel")

  // ... more props here
}

A developer who uses a UI library like Laminar (or Outwatch or Calico) might see onWheel --> whatever in their code, and ctrl-click on the onWheel symbol in their IDE to go to its definition. The snippet above is where they will land.

The definition of onWheel has a short ScalaDoc comment, but there is no concrete information about the value we're dealing with. We don't know what this EP type is, or what shape DomWheelEvent is – these types are both abstract.


We could improve on DomWheelEvent by replacing it with org.scalajs.dom.WheelEvent – the concrete type that it resolves to for 99% of the users. Then users could proceed to ctrl-click into that type and see what kind of events they will get. This would improve ergonomics for most people. This benefit is obvious, the main issue is maintaining functionality for the other 1%, the use cases where we do want abstract type params.


Currently DomWheelEvent is an abstract type param because our trait MouseEventProps is in a shared project – it's cross-compiled for Scala.js and the JVM, and so it does not have access to the JS-only org.scalajs.dom types.

I designed it that way because I was hoping that Scala DOM Types would be used on the JVM as well, for example in tools that need to render HTML (web pages or email content) on the server. To my knowledge, as of today no such tools exist that use Scala DOM Types, but it's possible that they do exist, just that their authors are overly modest, and didn't let us know.

I also hoped that cross-compiling libraries would be using Scala DOM Types on both client and server, however, again, I'm not aware of any such libraries.

Also, @armanbilge pointed out that obviously there aren't enough IO monads in scalajs-dom, so he's looking into using our generic SDT design with a more FP-friendly alternative to scalajs-dom.


How could we provide concrete org.scalajs.dom types to the 99% of SDT users, while maintaining SDT's flexibility – one of our main design goals? I don't want developers to need to fork SDT or copy-paste our type listings into their project. Ideally there should be a single source of truth for SDT-style type listings in the Scala world. And thus it needs to be flexible enough for anyone to use.

I think one way or another, the answer has to involve code generation.

  1. We could keep existing type listings as-is in the shared project, with all their abstract params, and generate ScalaJS-specific copies of all those traits. A bunch of dumb stuff like replacing DomWheelEvent with dom.WheelEvent. Or, do it the other way, doesn't really matter. Laminar would use the ScalaJS version, other libraries which need more flexibility would use the shared project version with abstract type params. If someone is using both libraries in their ScalaJS project, redundant near-copies of SDT traits will be loaded into the JS bundle.

  2. Alternatively, we could keep some kind of "source of truth" for the type listings in the SDT project (whether in the shape of Scala traits, or something more structured and easier to parse), and have every consuming library (Laminar, Outwatch, etc.) generate their own bundle of DOM keys. We would need to provide a configurable generator for this, and I'm not quite sure how. Otherwise, same issues as option (1) in case you use more than one such library in your project, but there are some extra advantages too:

    a) In addition to making DomWheelEvent a concrete type, we could also make EP a concrete type, so in Laminar you would see a fully concrete type: lazy val onWheel: EventProp[dom.WheelEvent] = eventProp("wheel")

    b) Each library like Laminar could better customize what they want to generate, e.g. click vs onClick, typ vs `type`, etc. (this can be seen as a disadvantage too).

    c) For special types like DomElementMouseEvent (canonically TypedTargetMouseEvent), libraries would be able to choose the strategy they want, AND have that type be concrete too.

    d) Just like today, libraries could choose which traits they want to use, but the generator could also flatten the traits into a single fat trait, or into one trait per key type (props / attrs / etc.). Currently libraries need to create an object that mixes in 20+ traits. I have no specific insights, but I suspect that there are ways in which this is unhealthy in JS / ScalaJS.

    e) Libraries would be able to choose if they want val-s or lazy val-s (See #86)

    f) All the DOM types would be contained in the library project itself, no need to jump into SDT.

    g) Esoteric libraries that need something other than scalajs-dom would get the benefit of concrete types as well.

  3. We could "backport" some of the advantages of option (2) to option (1) if we generated a set of "blessed" SDT configurations inside SDT, but I'm not sure if that's a good tradeoff, e.g. EP type would still remain abstract.


Disadvantages of code generation:

  • The inelegance of redundant code, whether contained in SDT, or spread across several projects. There is no escape.

  • Working with generated code can sometimes feel like working with macros, i.e. "invisible code". We will avoid this by committing all generated code into github, so that all published code matches what you see on Maven Central, in the IDE, and on github. We do it this way in Airstream already.

  • Harder for users to figure out how to contribute


Examples of code generation:


Requesting comments!

I'm not yet sure which option is best, or whether the tradeoffs are worth it. There might be better options than what I've considered, or some other use cases I haven't realized. Any opinions or insights are welcome. Let me know what yall think.

Personal cc: @sjrd @keynmol @armanbilge @fdietze @cornerman @yurique @sherpal but the discussion is open to everyone.

SVG support

What needs to be done to support SVG? I'll need this soon.

Mouse events missing

The onMouseEnter and onMouseLeave event props seem to be missing, is this intentional?

Publish defs (and generators?) for popular web component libraries, e.g. UI5, Material

I think the new source-generation approach has been a big success. I am really really happy with how we've been able to use it in Calico. Thank you!

Building on that, I think an interesting next step would be to start aggregating and publish defs for web components. At some level, web components are very similar (or even basically the same) as HTML. So a lot of the machinery is already in place here, and it would mean we can pool our efforts to support web components as as well.

The laminar-web-components project is already doing basically this:
https://github.com/uosis/laminar-web-components/blob/cf9fdc33ae0e77a2eb5b169acc4bf8e4d89dd48c/generator/material.sc

Have protected builder functions

Can we make builder functions like attr in AttrBuilder protected?

Then the consuming library can decide whether it exposes them by implementing the method protected or not. This has the advantage of being able to expose a different interface for user-defined attributes.

Docs: Add MDN docs to SVG attributes

Copy over MDN docs from ScalaTags MDN to SVG attributes that don't have them, similar to other attributes.

@doofin please let me know if you will be working on this. If not, I will probably release v0.6 without this. I got SDB and Laminar parts ready.

More complete code example

Let's pretend for the sake of this issue, I'm an impatient ninja coder who can't be bothered to read any documentation.

<impatient ninja coder>
I copy paste the sbt libraryDependencies line, then the code example. I doesn't compile. What imports am I missing?
Because I'm a hipster dev who lives on #twitter and stack overflow, I use VS Code as "IDE" which is of no help. I start looking for tests. Where are they? I move on to the javadocs. Hosted javadocs, great! But… what is this? jsdom and generic? builders, codecs, defs? What do I import now? I randomly import both jsdom._ and generic._ to no avail.
I am left with this:

org.scalajs.dom.html.Div does not take parameters
       val meow = div(
                     ^

I give up, because I am impatient and just wanted to save time in the first place and I don't want to go down yet another rabbit hole.
</impatient ninja coder>

Every event should be of type TypedTargetEvent

Currently, only a handful of the event properties are defined as a TypedTargetEvent, some are plain dom.Event. For convenience, it would be good if we could assume this for every event, i.e., every event is at least a TypedTargetEvent[dom.EventTarget].

Add info about supported tags to props/attrs

For example, the current data for the label attribute is:

ReflectedHtmlAttrDef(
scalaName = "labelAttr",
domAttrName = "label",
domPropName = "label",
scalaValueType = "String",
domPropValueType = "String",
attrCodec = "StringAsIs",
propCodec = "StringAsIs",
commentLines = List(
"For `optgroup` elements, specifies the name of the group of options, which the browser can",
"use when labeling the options in the user interface.",
),
docUrls = Nil,
),

At least according to this page, it is supported only for <track>, <option>, <optgroup>, <menu>, and <menuitem> tags.

https://www.geeksforgeeks.org/html-label-attribute/

In fact, I'm confused—is the label for <track> and <option> really the same?

In any case, Calico would be able to take advantage of this information.


The reason I'm thinking about this now, is because I am looking at web components, and they often seem to be defining their own custom attributes that may be "clashing" with other HTML attributes.

For example, I'm stuck on label specifically because of its use in Material Button.
https://github.com/material-components/material-web/blob/e15c4b86d584cfda5dc850cb697bc9b9552e9536/docs/quick-start.md#usage

Basically we seem to have a "namespacing" issue, where "attributes" should be "namespaced" within their element, because they may have different implementations and types across elements (or not even be available).

Make it possible to have a different API for cls / className / classNames

As described in raquo/Laminar#22, consuming libraries might want to provide a special way to deal with CSS classes which is different from how they deal with other attributes.

It would benefit the consuming libraries if they could choose whether to keep the default cls / className / classNames API, but currently it's not possible.

I guess the most obvious solution to this problem would be to extract css / className / classNames (and perhaps also styleAttr?) into a separate OpinionatedAttrs trait that the consuming library may choose to not use.

Document basic usage

  • Add ToC to readme
  • Docs:
    • Shared code, generic vs jsdom, various def lists
    • Keys, nodes including reuse of those types in many libraries
    • Builders and canonical builders
    • Modifier type
    • mention sameRefTags somewhere
    • bundles, and example usage (reference SDB or laminar)

Separation of reflected attributes and attributes with complex keys

When using the ComplexHtmlKeys trait, we mix reflected attributes and attributes into one trait. For consuming libraries, it is not possible to use ComplexHtmlKeys and still maintain a separated API for reflected attributes and normal attributes.

Can we fix this by separating the attributes into more traits? Is this desired or do we need a different solution for catering the many different use-cases?

Extract StyleSetters (eg. for colors) into traits and mix them into revelant objects

The StyleSetters for the css attribute color are also relevant for other css attributes accepting the color property like background-color, border-color, box-shadow, outline-color, text-shadow.

I think code sharing makes sense here.

lazy val black: StyleSetter = buildStringStyleSetter(this, "black")

(I'm writing down ideas I have while porting a project to the current outwatch PR.)

Merge colliding html and svg attr names into shared namespace.

I would like to merge colliding html and svg attribute names into a shared definition.
Attrs like height or href exist in both html and svg so when we import both dsl._ and dsl.svg._ then this code a(href := "example.com") causes a compiler error until we rename one of the attrs: import dsl.svg.{href => svgHref, _}.

I would like to create a third attr definition file which contains all colliding attrs which could then be used in both svg and html tags.

Ideally I would like to do the same for tags, but thats where things get a little more complicated since they would also have to produce the correct element based on context and that would probably require some implicit conversion magic.

In this issue I would like to hear some feedback on the general idea before I go forth with implementing this.

Add flow-relative CSS properties

Such as:

  • margin-block, margin-block-start, margin-block-end
  • margin-inline, margin-inline-start, margin-inline-end
  • padding-block, padding-block-start, padding-block-end
  • padding-inline, padding-inline-start, padding-inline-end
  • inset
  • inset-block, inset-block-start, inset-block-end
  • inset-inline, inset-inline-start, inset-inline-end
  • etc

For example, margin-block-start is basically equivalent to margin-top except it's aware of flow direction and text direction.

There's more to these than just dimensional props, see this spec – note the TOC on the left, the new values for text-align, the new logical keyword, etc.

cc @Lasering

Server Side Rendering

How can scala-dom-types api be used to render html on the server side (non js) ? eg,

val indexhtml = div("hello world")
val indexhtmlstr = indexhtml.toString
// then serve indexhtmlstr, eg, via http server.

though one other way would be to use the Laminar api .L._ , to build html and then render it via scala.js on node/graal on server side.

Discussion about lazy val definitions

Currently, domtypes defines dom attributes, properties, styles, events and elements as lazy val.

Now, we use domtypes in OutWatch, where most of the things we are constructing are builders. For example, the attribute contenteditable is an AttributeBuilder to which you can assign Boolean values. So you would write something like contenteditable := true. This is evaluated into Attribute("contenteditable", "true"). Ideally, our code should be compiled into the evaluated Attribute directly. Now, our AttributeBuilder already uses inline annotations to inline most of the construction part, but the compiler cannot inline the whole expression, because contenteditable is defined as a lazy val.

I would like to evaluate whether we can use @inline def instead of lazy val for the definitions in domtypes. What do you think? What kind of tests/evaluations would you like to see?

Modifier trait should not qualify as a SAM type

I made Modifier a separate trait for safety, to prevent users from passing any random function as a modifier. The idea is that end users should generally not be easily creating their own modifiers on the fly, but should mostly be using modifiers provided to them by the consuming library.

However, Scala 2.12 magic SAM syntax silently converts any lambda literal like el => foo into a Modifier because Modifier qualifies as a SAM type (rules are here).

Specifically, this interferes with Laminar's syntax where I have an implicit conversion from A => Modifier[A] to Modifier[A]. With 2.12, that conversion is never triggered because Scala magicks A => Modifier[A] into A => Unit (I guess?) which magicks into Modifier[A].


There are a couple ways to solve this problem that I know of:

  1. Modifier trait should not extend Function1, and should instead define its own abstract def apply[X](element: El): Unit method. SAM syntax is not applied due to the phantom type param on apply.

  2. Add an meaningless abstract protected[this] def foo to Modifier. This will make SAM syntax inapplicable, and if I'm understanding Scala.js dead code elimination correctly, this bogus method will be eliminated from runtime.

I'm leaning towards Option 1 as it seems like less burden for implementers of Modifier, as well as more obvious that it will not affect runtime performance.

If I recall correctly there might be a compiler flag to disable SAM treatment, but I don't want to force end users to do that for their whole project. I just want to prevent this one trait from being magicked with.

I don't particularly like any of the options, so any ideas or pointers would be very welcome.

Key renaming proposals

So I guess we'll be collecting a few attr renaming proposals here?

id -> idAttr

Not sure if id reflected attribute is pulling its weight hogging up what could be the most common term name.

Variable shadowing is annoying in cases like valueByIdMap.map { case (id, value) => ... }.

Personally I'm not annoyed enough to change it yet. If anyone has a preference one way or another please let me know.

Track parent tag of properties

Currently, the children properties of a tag do not know, into which kind of tag they are inserted. There is no parent relation of the types.

For example, we can currently write something like div(value := "bla"), even though value is only defined on button, option, input, etc and not on div. Additionally, we can currently not type the currentTarget property of events. This is always the object, on which the event listener was attached. So, if we write input(onChange := fun), we know that the type of currentTarget is always html.Input.

We would need a sane syntax for writing this. In scala I can only think about a macro for the tags, that inject the correct types into the code or adds some implicit. Any other ideas?

Also see: outwatch/outwatch#93

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.