Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • 13:42
    ddworak opened #514
  • 13:42

    ddworak on static-view-cache

    Test static view reuse Prevent rerendering of static v… (compare)

  • 13:32
    bgrochal synchronize #512
  • 13:32

    bgrochal on datepicker-options-init

    Datepicker setup callback shoul… TempusDominus datepicker unit t… (compare)

  • Jan 22 12:01
    mereszeta commented #512
  • Jan 22 12:00
    mereszeta assigned #512
  • Jan 22 11:46
    mereszeta commented #512
  • Jan 21 23:54
    scala-steward opened #513
  • Jan 21 17:47

    ddworak on packageDoc-fix

    (compare)

  • Jan 21 17:47

    ddworak on master

    Fixed inifinite loop in package… (compare)

  • Jan 21 17:47
    ddworak closed #511
  • Jan 21 15:44
    najder-k synchronize #511
  • Jan 21 15:44

    najder-k on packageDoc-fix

    Slightly better fix for infinit… (compare)

  • Jan 21 15:37
    bgrochal edited #512
  • Jan 21 15:34
    bgrochal review_requested #512
  • Jan 21 15:34
    bgrochal opened #512
  • Jan 21 15:26

    bgrochal on datepicker-options-init

    TempusDominus datepicker initia… (compare)

  • Jan 21 15:20

    bgrochal on datepicker-options-init

    (compare)

  • Jan 21 15:20

    bgrochal on datepicker-options-setup

    (compare)

  • Jan 21 15:20

    bgrochal on datepicker-options-setup

    (compare)

Des
@hughgearse
That was a great help tx. The solution you gave didn't suit exactly but put me on right track I think. I've added a second answer to the s.o. question with the solution I settled on.
Des
@hughgearse
Unfortunately this solution, although it works, is not sufficient. It seems like the many layers of transformations back and forth make the menu less responsive the deeper one goes into it.
Dawid Dworak
@ddworak
@hughgearse the number of updates is minimal with my solution. If you're updating in batches / loops, you might consider using CallbackSequencer to avoid triggering intermediate DOM changes
Dawid Dworak
@ddworak
There are a few improvements you can make. For produce, you don't need to transformToSeq really. If you have a hand on the original ModelProperty, you could just get the .subSeq (or even subProp, doesn't matter here)
Dawid Dworak
@ddworak
Also the transformed node property is a copy with a listener, you can use elemProperties here.
You can avoid collection transformation on nodes as well using .iterator.
Final version would be sth like:
  def renderItems(property: ModelProperty[MenuNode]): Modifier = {
    val seqProp = property.subSeq(_.subNodes)
    produceWithNested(seqProp) { (nodes, nested) =>
      div(nodes.iterator.zipWithIndex.flatMap { case (node, i) =>
        val nodeProp = seqProp.elemProperties(i)
        Seq(
          button(cls := "menu-button",
            onclick := { () => handleClick(nodeProp) },
            nested(produce(nodeProp)(_.item.label.render))
          )
        ) ++ node.subNodes.nonEmpty.option {
          div(cls := "sub-menu", renderItems(nodeProp))
        }
      }.toSeq: _*).render
    }
  }
When you're making updates to such nested / recursive structure, make sure to use callback sequencer to avoid intermediate states and you should be fine :) Nothing will be leaking with this version as well thanks to nested interceptor.
Des
@hughgearse
@ddworak I took a step back and decided a better approach would be to use a SeqProperty[MenuNode] to manage all the items and flatten the tree of menu nodes into this Seq. This way when a node is expanded/collapsed I only have to change the relevant parts of the Seq and my getTemplate has been greatly simplified to a single repeat() call on this Seq. I've updated the stack overflow answer with more detail on this. Working perfectly now.
Dawid Dworak
@ddworak
@hughgearse that sound good :) remember that if you're using repeat + get (as opposed to repeatWithNested + produce), the binding only reacts to structure changes, not to property value changes. Your example is fine as you're doing patches (insert, replace, set), but if you ever need to pass slot property somewhere for mutation and someone changes only its value (via .set), the callback is not going to be called. This is also explained in the repeat scaladoc.
Des
@hughgearse
Is there a way to combine showIfElse and produce? I have a scenario where I want to produce one of two optional DOM nodes depending on current property value. But these nodes need also to react to changes to a relevant property. The problem I have is that showIfElse requires a rendered DOM so I don't see how the relevant node can react to property changes?
Bartłomiej Grochal
@bgrochal

Hello, @hughgearse! The simplest solution in my opinion is to wrap your showIfElse with a renderable DOM element and produce such an element each time your value changes:

val booleanProperty = Property(true)
val valueProperty = Property("first")

produceWithNested(valueProperty)((value, nested) => div(
  nested(showIfElse(booleanProperty)(
    div(backgroundColor := "green")(s"$value component").render,
    div(backgroundColor := "yellow")(s"$value component").render
  ))
).render)

You can naturally place showIfElse on top and use produce inside:

showIfElse(booleanProperty)(
  div(produce(valueProperty)(value => div(backgroundColor := "green")(s"$value component").render)).render,
  div(produce(valueProperty)(value => div(backgroundColor := "yellow")(s"$value component").render)).render
)

Please note a subtle difference in those two snippets. If yourbooleanProperty toggles more often than valueProperty changes, the first solution is better since it does not impose re-appending the top level div to your DOM unnecessarily. However if displayed component is pretty constant and values change frequently, the second solution is preferred for the same reason.

Also, it should be mentioned that both produce(WithNested) and showIfElse returns Binding which should be handled to avoid memory leaks. Hence I like the first solution more because you can deal with your showIfElse binding lifecycle usingNestedInterceptor provided by outerproduceWithNested.

Des
@hughgearse
Thanks @bgrochal that clarifies things a lot for me
Kirill A. Korinsky
@catap

Hey! I guess I need help with "migration" css. I made some concept on udash and I love it. But to I've used some big custom css.

Anyway to migrate it to scala object? :)

Des
@hughgearse

Can someone tell me the correct way to combine two seq properties of the same type:

val seq1 = SeqProperty[Int](Seq(1, 2, 3))
val seq2 = SeqProperty[Int](Seq(4, 5, 6))

Then how to combine to produce:

SeqProperty[Int](Seq(1, 2, 3, 4, 5, 6))
Bartłomiej Grochal
@bgrochal
Hello, @catap ! Unfortunately, there is no translation mechanism between style rules gathered in a *.css file and ScalaCSS code provided AFAIK. The most seamless way I could think of to use custom stylesheets in your Scala code is to:
  1. attach your *.css file to the index.html header (just below the main.css sheet, like presented in this Guide section),
  2. gather all your style class names using regular expressions, simple script or whatever you wish,
  3. create io.udash.css#CssStyleName instance for each of the aforementioned classes, referring your custom styles in Scala code,
  4. (optional) migrate your global (unnamed) styles manually to the ScalaCSS DSL.
    That's the way we have implemented integration with Bootstrap styles in Udash - please take a look at io.udash.bootstrap.utils.BootstrapStyles implementation and sample usage. I think it's a good starting point for large CSS files migration. Please let us know if you encounter any problems.
Kirill A. Korinsky
@catap
@bgrochal thanks. I've done 1, 2 and 3 and looking for better solution :)
Bartłomiej Grochal
@bgrochal
Glad to hear that! Please let us know if you find or develop something more sophisticated than manual migration. We are not going to develop such a tool in the near future, but maybe there are some utilities provided by the ScalaCSS community. Good luck! :)
Bartłomiej Grochal
@bgrochal

@hughgearse You can't achieve your goal by dealing with SeqPropertyAPI because it's "element-oriented". Since each SeqProperty[T] is a Property[Seq[T]], you can handle SeqProperties like standard Properties filled with sequences:

val seq1: ReadableSeqProperty[Int] = Seq(1, 2, 3).toSeqProperty
val seq2: ReadableSeqProperty[Int] = Seq(4, 5, 6).toSeqProperty

val combined = (seq1: ReadableProperty[Seq[Int]]).combine(seq2)(_ ++ _)

However combined is of type ReadableProperty[Seq[Int]] since you treat seq1 and seq2 as ReadableProperties. In order to turn output ReadableProperty back to the ReadableSeqProperty, use transformToSeq:

val transformed = combined.transformToSeq(identity)
Kirill A. Korinsky
@catap
@bgrochal any easy way to save page's model inside some storage?
Des
@hughgearse
Tx @bgrochal that worked alright
Kirill A. Korinsky
@catap

I do have one more question and this one about i18n.

Let example that I'd like to have string like:

<p>Some text, <strong>with strong part</strong>, and more regular text.</p>

Right now I can do it by spliting text to three parts but it is a bit crazy and very wordy in term of code.

Do you know better solution?

Des
@hughgearse
Have you checked out the i18next framework @catap ? Its very powerful and includes support for interpolation
Has anyone successfully combined udash with jquery-ui? I have a scenario where I am dynamically loading inputs. But I can't find a way to apply the spinner widget to these inputs. I have elaborated the problem on stackoverflow here
Kirill A. Korinsky
@catap
@hughgearse have you got an example of i18next with udash?
Kirill A. Korinsky
@catap

About i18n. I've created: https://gist.github.com/catap/697cb5545c0eaebd3925fbed4ba4d255

@bgrochal would you like to have a pull request with this code?

Kirill A. Korinsky
@catap
Dawid Dworak
@ddworak
@hughgearse you might want to add an extension method for the udash-jquery object io.udash.wrappers.jquery.JQuery similarly to how io.udash.wrappers.jquery.JQuery.JQueryWrapper does it to extend it with the .spinner method. If you need more methods, use an auto-generated wrapper, e.g. https://github.com/oyvindberg/ScalablyTyped/tree/master/j/jqueryui
Des
@hughgearse
Ok, will look at that @ddworak but I can't see how it could resolve the issue? The issue is when can I actually call the spinner() method when my inputs are loaded dynamically depending on a SeqProperty.
Des
@hughgearse
Was able to resolve my spinner issue by wrapping my input in a div and appending a script that ran code, thus allowing the spinner() to be called everytime a new input is loaded. I have added an answer at stackoverflow
Bartłomiej Grochal
@bgrochal
@catap Regarding your question about model persistence, Udash is completely unaware of underlying data store and does not provide any utils itself. We use commons-mongo in our internal projects for MongoDB integration and org.scalajs.dom.ext.Storage for Local/Session Storage integration.
Kirill A. Korinsky
@catap

@bgrochal ok, so, I should implement keeping model inside Local/Session storage. handleState at Presenter looks as good place to recover it from storage and some custom wrapper to update storage version of model.

Have I missed something?

Des
@hughgearse
Is there a way I can bind a property value to an attribute value? Can't find anything in the docs. So want to do something like:
val prop = Property("class1")
div(cls := attrBind(prop))
prop.set("class2")
Bartłomiej Grochal
@bgrochal
@catap You're right. Presenter#handleState is usually the best place to deal with model changes, but please remember it should be called by RoutingEngine when resolving states (use Application#goTo instead of calling handleState explicitly). Web browser local storage is referred by org.scalajs.dom.ext.LocalStorage - usage is pretty straightforward. Feel free to ask if you have more doubts.
Bartłomiej Grochal
@bgrochal

@hughgearse Please take a look at io.udash.Bindings.AttrOps#bind. It is an extension method available for each Attr - just import io.udash._ to access it. Then, you can do the following:

val className = Property("myClass")
div(cls.bind(className))

className.set("myOtherClass")

If you want to handle attributes by name or deal with custom attributes, simply import scalatags.JsDom.all.attr and use:

val attributeName = "my-attribute"
val attributeValue = Property("my value")

div(attr(attributeName).bind(attributeValue))

Finally, if you want to manage the class attribute only, please take a look at io.udash.CssView.StyleOps and StyleFactoryOps - there are numerous utils to apply and unapply style classes without handling the attribute directly.

Des
@hughgearse
Great, thanks
Des
@hughgearse
@bgrochal when you say I should refer to io.udash.Bindings.AttrOps#bind do you mean there's a complete API reference somewhere? I can't find any such link on the udash website
Bartłomiej Grochal
@bgrochal
@hughgearse Unfortunately there is no Udash API reference hosted publicly, but there are some Scaladocs in sources. Usage of the bind extension method is however pretty straightforward, all you need is to call it on your attribute with a ReadableProperty[String] holding the attribute's value.
Des
@hughgearse
Given f: Property[A] => B what would be the best way to convert a SeqProperty[A] to a SeqProperty[B]?
Dawid Dworak
@ddworak
@hughgearse can you provide more details? What parts of property API does the function use (why isn't it A => B)?
Des
@hughgearse
Here is a simplification of what I'm trying to do. I would like the render method to handle aSeqProperty[B] as below. So that when a property A changes the relevant node is updated automatically.
    case class A(i: Int)

    case class B(a: Property[A]) {
        def render = div(bind(a))
    }

    def render(bs: SeqProperty[B]) = {
        val node = div(bs.get.map(_.render))
        bs.listenStructure { patch =>
            // Add/remove the B nodes accordingly
        }
        node
    }

    val seq = SeqProperty[A](Seq(A(3), A(4)))
    // How to call render(bs)?
    seq.append(A(5))
    seq.replace(0, 1, A(2))
Des
@hughgearse
@ddworak Here's a solution I'm going to try out. Do you forsee any issues with this?
    implicit class SeqPropTransformer[A](seqProp: ReadableSeqProperty[A) {
        def transformProps[B](f: ReadableProperty[A] => B): SeqProperty[B] = {
            val seq = SeqProperty(seqProp.elemProperties.map(f(_)))
            seqProp.listenStructure { patch =>
                seq.remove(patch.idx, patch.removed.size)
                seq.insert(patch.idx, patch.added.map(f(_)): _*)
            }
            seq
        }
    }
Bartłomiej Grochal
@bgrochal

@hughgearse You don't have to track SeqProperty structure changes manually in this case. If all what you need is to add/remove/change DOM nodes according to SeqProperty updates, use the io.udash.Bindings#repeat method (enabled by the io.udash._ import). Then, your render method could be:

def render(bs: ReadableSeqProperty[B]) = {
  div(
    repeat(bs)(item => span(
      // use produce(item) instead of item.get to react on item model changes as well
      // since produce returns a Binding, we need to wrap it inside a renderable element (span in this case)
      produce(item)(_.render.render)
    ).render)
  )
}

If you want to render each node in response to a SeqProperty update, just use produce instead of repeat. The difference between those two is presented in Udash Guide under the "repeat" section. Both produce and render require DOM Nodes to be returned by corresponding builders, hence rendering is required.

Bartłomiej Grochal
@bgrochal

If you also need to convert SeqProperty[A] to SeqProperty[B], use the transform method, which takes an A => B function converting each element of the input property into a corresponding element of the output property. In your case:

val seq = SeqProperty(Seq(A(3), A(4)))
val transformedSeq = seq.transform((elem : A) => B(Property(elem)))
render(transformedSeq)

Using elemProperties is a little bit tricky since it's a "one-shot" method, which does not track input property changes. That's why you need to set a structure listener on the seqProp and to call your f function manually during each update. The transform method is way more readable and convenient in my opinion.

Des
@hughgearse
@bgrochal Unfortunately the repeat/produce approach is not an option for me in this case. I am using a JQueryUI spinner widget in each node and the only way such a widget renders correctly is using the listenStructure approach. But the implicit class I outlined above seems to do the job for me.
Dawid Dworak
@ddworak
@hughgearse this looks messy, but should work fine. You may run into problems with cancelling the listenStructure registration with your approach if called repeatedly, as you don't seem to handle that anyhow. Anyway, since your B is basically Property[A], you're getting a SeqProperty[Property[A]], which is quite a complex representation of anything (as it can mutate both the containers (properties) and the content within them). It's easier to understand when considering Property[Property[A]]. Mutating just the nested property content won't trigger listeners on the outer property with this approach. Still, it might be what you need.
Des
@hughgearse
Great thanks for the info @ddworak. As I mentioned above, the problem stems from my need to run javascript code every time a new node is added. I tried appending a script tag to the node to get around this issue but this proved insufficient in the end. Unfortunately I can't remember why right now!
Des
@hughgearse
As a follow up @ddworak, as a general rule should I cancel a previous listenStructure on a property if called a second time and if so how can I?
Dawid Dworak
@ddworak
listenStructure returns Registration, which allows to to control the lifecycle of your callback. When you spawn the listeners dynamically, there's no way to determine when they're invalid automatically, so they would never GC. This can be a small or a big issue depending on how many times you call this method ;)