Git Product home page Git Product logo

Comments (15)

japgolly avatar japgolly commented on July 24, 2024

Erm, yes and no. Building Scala components and Scala mixins for them, you have traits provided by Scala itself which you would mix into components' backends, and you have ReactComponentB.configure for mixing-in lifecycle stuff. (See the .experiment package for examples.)

However, as for supporting what React uses for mixins, no. It's unneeded for Scala stuff and not type-safe to support it. I believe not porting that feature only affects interaction with JS components, and that is an open issue in #8.

from scalajs-react.

benjaminjackman avatar benjaminjackman commented on July 24, 2024

Ok let me take a step back then and show what I am trying to do.

I made a behavior for moving elements around the page, the interface is model making playing cards on a table, however there are other movable items such as chips.

I created a Movable component, but that is not what I really want, what I really want is to mix the move behavior into just about ANY component. This is I think, the perfect use case for mixins in react land. How would you suggest I model this in a scala-js-react friendly way since mixins are, if I understand correctly, not needed / supported?

object Movable {
  import japgolly.scalajs.react.vdom.ReactVDom._
  import japgolly.scalajs.react.vdom.ReactVDom.all._

  object State {def empty = State(Point2(0, 0), Point2(0, 0), false)}
  case class State(pos: Point2, rel: Point2, dragging: Boolean)
  case class Backend(T: BackendScope[ReactElement, State]) {
    def synthMousedown(e: SyntheticMouseEvent[HTMLDivElement]) {
      val pos = jq(e.target).offset().asJsDyn
      val top = pos.top.asInstanceOf[Double]
      val left = pos.left.asInstanceOf[Double]
      val x = e.asJsDyn.pageX.asInstanceOf[Double]
      val y = e.asJsDyn.pageY.asInstanceOf[Double]
      T.modState(s => s.copy(
        rel = Point2(x = x - left, y = y - top),
        dragging = true))
    }

    def synthMouseup(e: SyntheticMouseEvent[HTMLDivElement]) { nativeUp(e.nativeEvent) }

    def synthMousemove(e: SyntheticMouseEvent[HTMLDivElement]) { nativeMove(e.nativeEvent) }

    val jsUp  : js.Function1[dom.Event, Unit] = nativeUp _
    val jsMove: js.Function1[dom.Event, Unit] = nativeMove _

    def nativeMove(e: dom.Event) {
      if (T.state.dragging) {
        val x = e.asJsDyn.pageX.asInstanceOf[Double]
        val y = e.asJsDyn.pageY.asInstanceOf[Double]
        T.modState(s => s.copy(
          pos = Point2(x - s.rel.x, y - s.rel.y)
        ))
        e.stopPropagation()
        e.preventDefault()
      }
    }

    def nativeUp(e: dom.Event) { T.modState(s => s.copy(dragging = false)) }
  }

  //http://stackoverflow.com/questions/20926551/recommended-way-of-making-react-component-div-draggable

  val MovableComp = ReactComponentB[ReactElement]("MovableComp")
    .initialState(State.empty)
    .backend(Backend)
    .render { (p, s, b) =>
    div(
      cls := (if (s.dragging) "move card" else "card"),
      position := "absolute",
      top := s.pos.y,
      left := s.pos.x,
      height := "200px",
      width := "133px",
      onmousedown ==> b.synthMousedown,
      onmouseup ==> b.synthMouseup,
      onmousemove ==> b.synthMousemove
    )(p)
  }
    .componentDidUpdate { (scope, p, s) =>
    if (scope.state.dragging && !s.dragging) {
      document.addEventListener("mousemove", scope.backend.jsMove)
      document.addEventListener("mouseup", scope.backend.jsUp)
    } else if (!scope.state.dragging && s.dragging) {
      document.removeEventListener("mousemove", scope.backend.jsMove)
      document.removeEventListener("mouseup", scope.backend.jsUp)
    }
  }
    .build


}

It feels like something is missing here? Should I be writing something that generically "wraps" an existing component (it would probably have to hook in pre-build method) to do this correctly? Something similar to composing functions?

def compose[P1, S1, B1, P2, S2, B2](a : ReactComponentB[P1, S1, B1], b: a : ReactComponentB[P2, S2, B2])(name : String) : ReactComponentB[(P1, P2), (S1, S2), (B1, B2)]

in which case I could do

val MovableCardComp = compose(MoveComp, CardComp)("MovableCardComp")

(oh and one other thing, what is the purpose of naming components e.g. "MovableCardComp" i know in normal react it then lets you call elements with that name in jsx, is that needed for scalajs doesn't seem like I have ever had to refer to a component by it's string name)?

Maybe it is easier to just write the function as

addMovableBehavior[P,S,B](c : ReactComponentB[P,S,B])

I don't want to overthink this in case you already have a solution for this.

from scalajs-react.

japgolly avatar japgolly commented on July 24, 2024

To clarify, React mixins aren't ported to this lib, but (as far as I understand) you can achieve the same thing using traits in backends and .configure. So if you have an idea that works for you and you think React mixins are the answer, go for it, it should work in Scala too (as long as you don't plan to mix into pure JS components). It will just have different syntax.

As for your example, that is much code my friend, I didn't study it but I think I have something related that might be of help. I need drag-and-drop for my app. Earlier I discovered how to do it in React and prototyped it in scalajs-react and it worked. But I haven't used it for real yet. (In fact once I do start using it I plan to polish and move it in to this lib an an extra (#30).) This is the PoC I came up with a few months back: https://gist.github.com/japgolly/4bcfdcac208bbb7d925e. I'm sure I'd change some things now having a bit more experience with React but that's how the learning goes, it still works fine. DND is supposed to be a generic module that I can use any time I want drag-and-drop, Example shows how I originally envisioned it being used.

from scalajs-react.

japgolly avatar japgolly commented on July 24, 2024

Wow looking at that thing now I would absolutely do a few things differently. :S Bah, back to work for me.

from scalajs-react.

benjaminjackman avatar benjaminjackman commented on July 24, 2024

Ok, If I am understanding correctly it sounds like there is not a suggested way to mixin behaviors instead I should just pick one that works for me? The nice part about using vanilla react is that when using the mixins is that User A can write a set of mixins and User B can very easily just mix them into his components because there is a standard way of doing that.

I am not suggesting that react mixins are the way to implement this behaviour, but I am suggesting that there is benefit in defining a clear & standard way that behavior authors would write their code so that clients could easily and uniformly add the behavior into their components.

So the movable behavior mixin is added in the same way that a tooltip on mouseover one would be, despite the fact that those two mixins might have different authors.

Or maybe it's just overkill. Probably wait and see how it turns out as I add all these behavior then I can suggest something as a pattern. I just didn't know if you had done enough work with scala-js-react to suggest a pattern and it sound like the answer there is not yet.

Thanks!

from scalajs-react.

longshorej avatar longshorej commented on July 24, 2024

I had asked a similar question a few months ago in #21 if you want to read through that thread. Since then, I've sort of ran with something similar to the example I just posted in #51.

So, maybe that'll give you some ideas. I have quite a few "mixins" implemented in that style and it makes it really easy to compose them in a type-safe way.

from scalajs-react.

benjaminjackman avatar benjaminjackman commented on July 24, 2024

@longshorej thanks I will look that over!

from scalajs-react.

japgolly avatar japgolly commented on July 24, 2024

@benjaminjackman I remember loving mixins when I discovered them ages ago but then after a few years of usage I've now come to dislike them. I hardly ever use them if at all, anymore and have come to believe they (along with class inheritance) are poor choices for software development. They make everything easy at first which is very appealing, but over time they cause way more harm than good.

But sometimes I forget that not everyone has been through the same experiences. Programming can be quite opinionated. Feedback I'm getting through this library is that other people still consider them an important tool. I'd normally try to impart the lessons I've learned but even should I change minds, I wasn't able to (effectively) code without inheritance overnight and we all have work to do.

And so, I see your point. I believe there is a clear way to write mixins in Scala with React but I'm not sure I've communicated it effectively. (In my mind it's just write a trait, mix into your backend class, done.) Would a document with maybe an example or two be what you think we need here? Do you think there's some part of the library that's missing?

There's also a danger in that if I write the document or add the methods, then you've got the guy who doesn't use mixins telling everyone else how to write mixins. Maybe yourself, @longshorej and others might want to work out what how you think it should work?

from scalajs-react.

japgolly avatar japgolly commented on July 24, 2024

FYI here is some doc of what I consider Scala React component mixins.
https://github.com/japgolly/scalajs-react/blob/v0.7.x/extra/README.md

from scalajs-react.

benjaminjackman avatar benjaminjackman commented on July 24, 2024

Ok I wasn't aware of .configure! That's pretty nifty and has the same signature / implementation as a function I was thinking about adding implicitly.

As I have thought about this more I think we might be hitting some hard parts in Scala's type system. We really need a type-conjuction to be able to do this well as a mixin will potentially change the type of props and state and backend.

The solution you use, if I understand it, is to:

  1. Have the mixin define traits that can be mixed into the components backend / props / state
  2. Install itself with .configure and call the relevant component(Did|Will)(Un)Mount etc

Part 2 is fine as far as I can tell.

What I don't like about part 1 is more a problem I think with Scala and most languages in general so probably beyond the scope of what's relevant, but I have to re-add all the parameters into Props (Assuming that props is a case class) in order to gain the behavior, if props isn't a case class I lose .copy.

What I want to try to see how much friction is there for mixins is to make a few example ones that should compose into any other mixin on the page:

KeyboardMoveable: A mixin that adds the ability to change the components absolute position on the page when the user presses the arrow keys while the element has focus.

KeyToPrimaryColor: Now suppose there is a separate mixin that changes the elements background color to red/green/blue when one of the keys r/g/b are pressed.

GlowingFocus: Suppose there is one more that adds a configurable glowing drop-shadow border when the element is clicked on and gains focus (this is best probably done by simply adding a css class when the element has focus).

What I don't like a lot is that the signature of props changes when these mixins are added and that I have to go and mix things into my state / props / backend to add this behaviour. They don't have to change for all the existing built in DOM behavior that browsers implement some of which does stuff very similar to this. As I write a component, let's say it's a playing card component, I should be able to write that as it's own self contained component.

Then a user of that component should be able to say, I want to be able to move this box around the page so I will just add in my KeyboardMoveable mixin and not have to make a whole new set of props and also not have to go an modify the PlayingCard.scala or KeyboardMoveable.scala code in any way.

Essentially the code for that should ultimately be no more complex than this:

React.render(PlayingCard(Card(Jack, Spades)).withMixins(KeyboardMoveable(Position(10,10)), KeyToPrimaryColor(), GlowingFocus("blue")), mountNode)

Does that make some sense?

(edit formatting)

from scalajs-react.

japgolly avatar japgolly commented on July 24, 2024

Yeah, I I hear you. I'm really torn on this issue.

On one hand, the aspect that you don't like, I actually like. At least in theory. Mixins installed via .configure don't directly mix into props or state, rather they add constraints or expectations to the types of props, state, backend. I like this because you're then forced by the compiler to maintain a proper audit trail of what affects a component. The top-level component's props and state will have a number of additional fields and that's good because it makes explicitly clear what you need to consider when you're working with it. You can see everything that affects its state which is great for complicated pages. Much easier to reason about, harder to accidentally screw up some other part of state when adding a new feature, etc.

But the downside is it makes all the piping and wiring complex and verbose. As you say, changing one small component can have you modifying everything up the chain. It's time consuming. Fighting scalac when types are large and complex can be a significant pain.

Like you say though, it may just be life with type-safety. If there are ways to maintain the recommended model (everything stateless except top) and simply the type gymnastics, I'd love to do it. Without compromising that immutability principle though, I'm not sure what else we could do.

Your example of withMixins(KeyboardMoveable(Position(10,10))... would (I believe) have to break the immutability principle if it were to work in that where would the state for KeyboardMoveable go? If there's hidden state everywhere we may as well just switch to angular.js. So I wonder, do big, proper React-based apps happily do this without issue? Can they classify certain types of state as being ok unobservable? I don't know. Tell you what though, there's nothing you can't do if you're willing. 😏 If you were determined you could either just have a Map[String, Any] on your state and then all the mixins could pull things out by name - it's how React JS does things, smashing everything onto this. Or another way is to have mixins use anonymous state (eg. def x = {var a=0; () => {a+=1; a}}; val y = x; y(); y()). 😄

from scalajs-react.

benjaminjackman avatar benjaminjackman commented on July 24, 2024

I like the audit trail aspects as well, I just wish it did not require so much work in boilerplate land (piping and wiring) to get it.

I think it's probably life with type-safety with the limited types expressible available in scala. Technically what I think we need is something like conjunctive types (A with B with C) that still auto-generate things like copy constructors. Tuples or K-Lists might work.

Oh boy, please don't say switch to angular.js! Using that framework was not fun.

withMixins isn't ever going to work because it's a var-args structure and those things LUB their types away in scala, which doesn't work for us. so there will have to be repeated calls to withMixin[B <: Mixin[B]](mixin : B) : ReactComponentB[MixedIn[A, B]] or something like that, will require more learning on my part but sucessive calls to withMixin would essential make a ConsList like type hierachy.

I think the big problem we are facing is there a dichotomy between fire and forget mixins. Mixins that you just want to add into a Component, potentially with an initial value, to give it some behavior. Taking the keyboard example.

withMixin((propsOuter) => KeyboardMoveable(Position(10,10)).onComponentUpdated((props) => println(props))) notice that the onComponentUpdated was stuck on Keyboard moveable, and not on the outer type.

... Hmmm this is starting to look a lot like configure, I have to run so can't finish my thought, I will post more later, sorry for leaving this in a messey rambly state.

(Map[String, Any] Is the route I will probably end up going down for now if I need to get something concrete done, but I'd like to figure out how to work around it in the future.)

from scalajs-react.

japgolly avatar japgolly commented on July 24, 2024

Between .configure and .mixinJS I think we've got this covered. Newer Scala-based mixins are functioning happily in the extra module using .configure. You good to close this @benjaminjackman ?

from scalajs-react.

benjaminjackman avatar benjaminjackman commented on July 24, 2024

definitely

On Mon, Jun 15, 2015 at 9:24 PM, David Barri [email protected]
wrote:

Between .configure and .mixinJS I think we've got this covered. Newer
Scala-based mixins are functioning happily in the extra module using
.configure. You good to close this @benjaminjackman
https://github.com/benjaminjackman ?


Reply to this email directly or view it on GitHub
#52 (comment)
.

from scalajs-react.

japgolly avatar japgolly commented on July 24, 2024

sweet

from scalajs-react.

Related Issues (20)

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.