Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Another html syntax to put id/css in the start? #42

Open
freewind opened this issue Mar 30, 2015 · 16 comments
Open

Another html syntax to put id/css in the start? #42

freewind opened this issue Mar 30, 2015 · 16 comments
Milestone

Comments

@freewind
Copy link

For a simple html page with scalatags:

    div(
      div(
        div(
          text()
            .bind(keyword)
            .css("search").placeholder("Search")
        ).css("search-panel")
      ),
      div()
    ).id("main-page")

You can see the id and css are put after the tag, which is not easy to locate a tag by its id/class.

What about provide a syntax which allow we put id/css in the start? e.g.

    div.id("main-page")(
      div(
        div.css("search-panel")(
          text.css("search")
            .bind(keyword)
            .placeholder("Search")
        )
      ),
      div()
    )
@tindzk tindzk added this to the v0.3 milestone Mar 30, 2015
@tindzk
Copy link
Contributor

tindzk commented Mar 30, 2015

Thanks for your suggestion. It would significantly improve the readability.

As it requires some restructuring and making all widgets immutable, it is quite an undertaking. Therefore, we should implement it in v0.3, which will be the next major release.

@freewind
Copy link
Author

Thanks!!

@tindzk
Copy link
Contributor

tindzk commented Mar 31, 2015

My approach would be to implement it as follows:

case object Widget extends Widget
class Widget {
  private var css = ""

  @inline def copy(f: Widget => Unit): Widget = {
    val res = new Widget
    res.css = css
    f(res)
    res
  }

  def css(s: String): Widget = {
    copy { c =>
      c.css = s
    }
  }

  // Return rendered widget
  def apply(views: View*) = ???
}

Can we come up with something simpler? This solution is not very convenient because we'd have to define copy() manually, and the approach wouldn't work that well with inheritance. I'd also prefer if all properties could stay immutable.

@freewind
Copy link
Author

freewind commented Jun 8, 2015

Just tried a simple solution:

def view() = "#main-page" >>> div(
  "#main-form.form.search" >>> div(
    ".search-panel" >>> div(
      ".search" >>> text().bind(keyword).placeholder("Search")
    )
  ),
  div()
)

https://github.com/freewind/widok-with-selectors/blob/master/src/main/scala/example/Main.scala#L18-L26

How do you feel it?

@tindzk
Copy link
Contributor

tindzk commented Jun 16, 2015

The solution you're suggesting doesn't look too bad. Unfortunately, it's only a partial fix. For example, if we were to register event handlers, we'd still need to place them at the end of the widget initialisation.

I believe we should use a macro-based approach that initialises the object in one go (unlike my previous suggestion which makes many unnecessary copies).

Another change I'm considering is to separate the widget creation from the actual DOM rendering:

val recipe = div                             // immutable widget
  .css("search-panel")
  .id("search")(
    text()
  )
val rendered  = recipe.render                // rendered DOM node
val rendered2 = recipe.id("search2").render  // modifying widgets does not have any side-effects

Due to their immutability, widgets would be compatible with the JVM. There could be interesting use cases such as UI testing or serialisation. We could instantiate widgets on the server and send the serialised objects to the client, where a simple .render call would create the DOM nodes.

Also, the macro could be adapted to parse HTML templates from inline strings or external files:

val recipe =
    html"""
        <div class="search-panel">
            <input type="text" bind="${keyword}" placeholder="Search" />
        </div>
    """

val recipe2 = tpl("_search_panel.html")

@freewind
Copy link
Author

I used my approach in a small project and basically satisfied so far (compared to the standard approach), because it's much easier to find the elements and write the styles in lesscss, and the implementation is quite easy.

The html version will give much better readability and I really like it. With the help of IDEA, I can mark a string as 'html language', so I can get the syntax highlight and completion.

A question that why you didn't mention the xml version with macro? Another project scala-js-react provide the xml version (although it's buggy)

@tindzk
Copy link
Contributor

tindzk commented Jun 16, 2015

Support for XML literals will soon be dropped in Scala. That's why I was suggesting to use string interpolators instead.

@tindzk tindzk added the ready label Jun 16, 2015
@freewind
Copy link
Author

Cool, @tindzk !

@tindzk tindzk removed the ready label Jun 17, 2015
@freewind
Copy link
Author

Sorry, maybe it's better to keep it open :)

@jn73
Copy link
Contributor

jn73 commented Jun 24, 2015

@tindzk , I think that the combination of the macro-based approach and separating the creation and rendering looks like a really good idea!

I don't know enough about scala macros to have a good opinion about a macro-based solution versus something else, but the syntax feels very natural and it would be very nice to avoid the copy-code in your first example. Also, the possibility to write html"..." snippets etc would be very useful!

Can you describe how/where the channel-bindings would take place? It must be done in the "render-phase" i assume? With html-templates this can be done with interpolation (bind-attribute?). How would it work when using "scala-recipes"? For example:

val name = Var("some name")

// old way
val myWidget = div(name.map("Name: " + _))

// how would you do this when creation and rendering is separated?
// just an example to explain what i'm after..
val myWidget = div("Name: $name").render.bind("name", name)

@tindzk tindzk added in progress and removed ready labels Jun 26, 2015
@mkotsbak
Copy link
Contributor

I just saw another possibility in a React Native SJS wrapper project: https://github.com/chandu0101/scalajs-react-native/blob/master/core/src/main/scala/chandu0101/scalajs/rn/components/Text.scala#L25

where the attributes are set in a first and then call apply() with the content:

   View(style = Styles.container("red"))(
      Text(style = Styles.text)("Welcome to Scala-JS ReactNative"),
      Text(style = Styles.text)("To get started, edit HelloNative.scala "),
    Text()
    )

The attributes then should either have default values or be Option with default None.

The only drawback I can see is that if you don't want to specify any attributes, an empty () needs to be added:

Text()("Content")

Maybe an idea for widgets with required attributes or where it is useless without parameters, like an Anchor widget:

Anchor(url = "github.com")("Github")

What do you think about that? Advantages are that it is placed in front, and typesafe, and it is easy to discover the available attributes, and IDE support is good.

@mkotsbak
Copy link
Contributor

mkotsbak commented Oct 5, 2015

Well, another drawback of the solution described above is that it is harder to support both reactive and static parameters. One could make one with only reactive attributes and one with only static attributes, but a combination might be desired depending on the usage.

@tindzk
Copy link
Contributor

tindzk commented Oct 5, 2015

That's exactly the reason why I decided against this syntax. In earlier revisions of the DSL I was using the syntax as you described it, but then the need for reactive parameter updates arose.

On the other hand, it may actually be a good idea to only allow constant values in the specification because that allows us to have an immutable representation of the tree. Reactive parameters should only be performed on the instantiated/rendered tree. That's how it's done right now in MetaWeb and I believe that a DSL should respect a similar distinction.

@mkotsbak
Copy link
Contributor

mkotsbak commented Oct 5, 2015

Yes, it will be interesting to look at the MetaWeb ideas.

It could be supported in a nasty way by including both static and reactive parameters wrapped in Option[] with default value None, but then its hard to require parameters without inventing a new hierarchy.

Btw, the tag mappings looks a bit redundant when all are already mapped by sjs dom library.

@tindzk
Copy link
Contributor

tindzk commented Oct 5, 2015

Do you mean MetaWeb's bindings? They are only redundant if you think of your application in terms of Scala.js. However, not relying on JavaScript's DOM allows us to render a static version for the JVM and even do pre-population for search engines.

@mkotsbak
Copy link
Contributor

mkotsbak commented Oct 5, 2015

I mean the Widok bindings. Look at how e.g. this line in my new PR is just delegating: c82bd08#diff-cf4957338261c0c2ad6021cc51230fa1R397

But server side rendering support would be nice. Compared to React and also the wrapper, Widok/MetaWeb would not need to execute javascript on the server, so they will have an advantage there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants