I'm using Diode 1.0.0 with scalajs-react 0.11.1.
Use case:
Parent component with list of child components
Child's model fragment contains Pot for asynchronously fetched image
Child component fetches image when mounted and Pot is Empty, updating its model fragment
With a naive approach, this causes the following scenario (order of events might be different):
Parent is rendered.
Child 1 is rendered.
Child 1 dispatches its GetImageAction. Model fragment Pot is updated to Pending.
Model is updated, causing parent to re-render.
All children are re-rendered.
Children 2 … n still have an Empty Pot, so they trigger their GetImageActions again.
Now Child 2 is rendered.
Model is updated, causing parent to re-render.
Etc.
This causes a huge tree of GetImageAction invocations and re-renderings.
Some questions:
Is it wrong to use the model for this purpose? Would it be better to use component states?
How can the re-rendering of the parent be avoided when only the child needs to be updated? I couldn't figure out if / how I can use shouldComponentUpdate for this purpose.
Update 1
I am now adding a React key to each child component. This got rid of the React warning regarding unique keys, but unfortunately didn't solve the issue above. The children get re-rendered, even if their shouldComponentUpdate method returns false.
From ParentComponent.render():
items.zipWithIndex.map { case (_, i) =>
proxy.connector.connect(
proxy.modelReader.zoom(_.get(i)), s"child_$i": js.Any).
apply(childComponent(props.router, _))
}
Update 2
I tried implementing the listener functionality in the parent component, but unfortunately the children are still unmounted and re-mounted. Here's the code of my parent component:
package kidstravel.client.components
import diode.data.{Empty, Pot}
import diode.react.ModelProxy
import diode.react.ReactPot._
import diode.{Action, ModelR}
import japgolly.scalajs.react.extra.router.RouterCtl
import japgolly.scalajs.react.vdom.prefix_<^._
import japgolly.scalajs.react.{BackendScope, ReactComponentB, _}
import kidstravel.client.KidsTravelMain.Loc
import kidstravel.client.services.{KidsTravelCircuit, RootModel}
case class TileProps[T](router: RouterCtl[Loc], proxy: ModelProxy[T])
/**
* Render sequence of models as tiles.
*/
trait Tiles {
// The type of the model objects.
type T <: AnyRef
/**
* Override to provide the action to obtain the model objects.
* #return An action.
*/
def getAction: Action
/**
* Returns the tile component class.
* #return
*/
def tileComponent: ReactComponentC.ReqProps[TileProps[T], _, _, _ <: TopNode]
case class Props(router: RouterCtl[Loc], proxy: ModelProxy[Pot[Seq[T]]])
class Backend($: BackendScope[Props, Pot[Seq[T]]]) {
private var unsubscribe = Option.empty[() => Unit]
def willMount(props: Props) = {
val modelReader = props.proxy.modelReader.asInstanceOf[ModelR[RootModel, Pot[Seq[T]]]]
Callback {
unsubscribe = Some(KidsTravelCircuit.subscribe(modelReader)(changeHandler(modelReader)))
} >> $.setState(modelReader())
}
def willUnmount = Callback {
unsubscribe.foreach(f => f())
unsubscribe = None
}
private def changeHandler(modelReader: ModelR[RootModel, Pot[Seq[T]]])(
cursor: ModelR[RootModel, Pot[Seq[T]]]): Unit = {
// modify state if we are mounted and state has actually changed
if ($.isMounted() && modelReader =!= $.accessDirect.state) {
$.accessDirect.setState(modelReader())
}
}
def didMount = $.props >>= (p => p.proxy.value match {
case Empty => p.proxy.dispatch(getAction)
case _ => Callback.empty
})
def render(props: Props) = {
println("Rendering tiles")
val proxy = props.proxy
<.div(
^.`class` := "row",
proxy().renderFailed(ex => "Error loading"),
proxy().renderPending(_ > 100, _ => <.p("Loading …")),
proxy().render(items =>
items.zipWithIndex.map { case (_, i) =>
//proxy.connector.connect(proxy.modelReader.zoom(_.get(i)), s"tile_$i": js.Any).apply(tileComponent(props.router, _))
//proxy.connector.connect(proxy.modelReader.zoom(_.get(i))).apply(tileComponent(props.router, _))
//proxy.wrap(_.get(i))(tileComponent(_))
tileComponent.withKey(s"tile_$i")(TileProps(props.router, proxy.zoom(_.get(i))))
}
)
)
}
}
private val component = ReactComponentB[Props]("Tiles").
initialState(Empty: Pot[Seq[T]]).
renderBackend[Backend].
componentWillMount(scope => scope.backend.willMount(scope.props)).
componentDidMount(_.backend.didMount).
build
def apply(router: RouterCtl[Loc], proxy: ModelProxy[Pot[Seq[T]]]) = component(Props(router, proxy))
}
Most probably this is due to calling connect inside the render method. This will force unmounting/remounting of all child components. It's best to call connect for example when the parent component is mounted and then use the result in render.
Alternatively you could skip connect altogether and implement change listener inside the parent component directly. When the items collection changes, update the state which forces a re-render updating all components that have changed. Using shouldComponentUpdate allows React to determine which components have really changed.
Related
When I try to pass a list to the view page of a Play application I receive an error:
illegal cyclic reference involving object models
Error screenshot:
models.scala.html:
#(liValues: List[String])
#for(value <- liValues){
<li>#value</li>
}
entry in routes files:
GET /models/tictactoe controllers.ModelController.index
index method in ModelController.scala where I pass the values:
def index = Action {
Ok(views.html.models(List("Link1" , "Link2" , "Link3")))
}
Complete ModelController:
package controllers
import javax.inject._
import play.api.libs.json.Json
import play.api.mvc._
/**
* This controller creates an `Action` to handle HTTP requests to the
* application's home page.
*/
#Singleton
class ModelController #Inject()(cc: ControllerComponents) extends AbstractController(cc) {
/**
* Create an Action to render an HTML page with a welcome message.
* The configuration in the `routes` file means that this method
* will be called when the application receives a `GET` request with
* a path of `/`.
*/
def index = Action {
Ok(views.html.models(List("Link1" , "Link2" , "Link3")))
}
def sj = Action {
Ok(Json.toJson(List(1,2,3)).toString());
}
}
it seems I'm not declaring the list value in the view page correctly ?
It seems DummyPlaceHolder.scala
package models
/*
* Empty placeholder object to make sure templates keep compiling (due to
* imports in template files), even if projects don't have any models.
*/
object DummyPlaceHolder
is interfering with your views.html.models which also has models so the generated template target/scala-2.13/twirl/main/views/html/models.template.scala will have something like
import models._
object models extends ...
which causes illegal cycle. Try changing the name of your template from views.html.models to say views.html.model.
Lets say we modified an event to have a new field added. I understand that we can handle the serialization for event mapping changes in this documentation https://www.lagomframework.com/documentation/1.5.x/scala/Serialization.html but how does lagom know which version the event is? When declaring and defining case classes events, we do not specify the event version. So how does lagom serialization know which event version mapping to use?
In the image below, there is a field called fromVersion. How does lagom know the current version of events pulled from event store datastore?
So, to implement migration you add following code:
private val itemAddedMigration = new JsonMigration(2) {
override def transform(fromVersion: Int, json: JsObject): JsObject = {
if (fromVersion < 2) {
json + ("discount" -> JsNumber(0.0d))
} else {
json
}
}
}
override def migrations = Map[String, JsonMigration](
classOf[ItemAdded].getName -> itemAddedMigration
)
}
That means that all new events of type ItemAdded now will have version 2. All previous events will be treated as version 1.
It is defined in the class PlayJsonSerializer
Please see the following code:
private def parseManifest(manifest: String) = {
val i = manifest.lastIndexOf('#')
val fromVersion = if (i == -1) 1 else manifest.substring(i + 1).toInt
val manifestClassName = if (i == -1) manifest else manifest.substring(0, i)
(fromVersion, manifestClassName)
}
Also, you can check it in the database. I use Cassandra, I if I will open my database, in eventsbytag1 collection I can find the field ser_manifest where described version. Where is simple class - it is version 1, where you have specified additional '#2', it means version 2 and so on.
If you need more information about how it works, you can check method fromBinary in class PlayJsonSerializer.
The goal of this function is to create a stream that emits values periodically until it encounters one that matches a predicate.
Here is some skeleton code that I've come up with:
class Watcher<T : Any>(
/**
* Emits the data associated with the provided id
*/
private val callable: (id: String) -> T,
/**
* Checks if the provided value marks the observable as complete
*/
private val predicate: (id: String, value: T) -> Boolean
) {
private val watchPool: MutableMap<String, Observable<T>> = ConcurrentHashMap()
fun watch(id: String): Observable<T> {
// reuse obesrvable if exists
val existing = watchPool[id]
if (existing != null)
return existing
val value = callable(id)
if (predicate(id, value)) return Observable.just(value)
// create new observable to fetch until complete,
// then remove from the map once complete
val observable = Observable.fromCallable<T> {
callable(id)
}.repeatWhen { /* What to put here? */ }.doOnComplete {
watchPool.remove(id)
}.distinctUntilChanged()
watchPool[id] = observable
return observable
}
}
As an example, if I have the following enums:
enum class Stage {
CREATED, PROCESSING, DELIVERING, FINISHED
}
And some callable that will retrieve the right stage, I should be able to pass the callable and a predicate checking if stage == FINISHED, and poll until I get the FINISHED event.
The issue I have is in generating an observable when the event received is not a final event. In that case, the observable should continue to poll for events until either it receives an event matching the predicate or until it has no more subscribers.
This observable should:
Not poll until it receives at least one subscriber
Poll every x seconds
Mark itself as complete if predicate returns true
Complete itself if it ever goes from >0 subscribers to 0 subscribers
The use of watch pools is simply to ensure that two threads watching the same id will not poll twice the number of times. Removal of observables from the map is also just so it doesn't pile up. For the same reason, observables that emit just one variable are not stored for reference.
How do I go about adding the functionality for the points added above?
I will link to one existing RxJava Github issue that I found useful, but from what I'm aware, it doesn't allow for predicates dealing with the value emitted by the callable.
I ended up with just using takeUntil, and using the observal's interval method to poll.
abstract class RxWatcher<in T : Any, V : Any> {
/**
* Emits the data associated with the provided id
* At a reasonable point, emissions should return a value that returns true with [isCompleted]
* This method should be thread safe, and the output should not depend on the number of times this method is called
*/
abstract fun emit(id: T): V
/**
* Checks if the provided value marks the observable as complete
* Must be thread safe
*/
abstract fun isCompleted(id: T, value: V): Boolean
/**
* Polling interval in ms
*/
open val pollingInterval: Long = 1000
/**
* Duration between events in ms for which the observable should time out
* If this is less than or equal to [pollingInterval], it will be ignored
*/
open val timeoutDuration: Long = 5 * 60 * 1000
private val watchPool: MutableMap<T, Observable<V>> = ConcurrentHashMap()
/**
* Returns an observable that will emit items every [pollingInterval] ms until it [isCompleted]
*
* The observable will be reused if there is polling, so the frequency remains constant regardless of the number of
* subscribers
*/
fun watch(id: T): Observable<V> {
// reuse observable if exists
val existing = watchPool[id]
if (existing != null)
return existing
val value = emit(id)
if (isCompleted(id, value)) return Observable.just(value)
// create new observable to fetch until complete,
// then remove from the map once complete
val observable = Observable.interval(pollingInterval, TimeUnit.MILLISECONDS, Schedulers.io()).map {
emit(id)
}.takeUntil {
isCompleted(id, it)
}.doOnComplete {
watchPool.remove(id)
}.distinctUntilChanged().run {
if (timeoutDuration > pollingInterval) timeout(timeoutDuration, TimeUnit.MILLISECONDS)
else this
}
watchPool[id] = observable
return observable
}
/**
* Clears the observables from the watch pool
* Note that existing subscribers will not be affected
*/
fun clear() {
watchPool.clear()
}
}
I know that in Play! using Scala that there is no Http.context available since the idea is to leverage implicits to pass any data around your stack. However, this seems like kind of a lot of boiler plate to pass through when you need a piece of information available for the entire context.
More specifically what I'm interested in is tracking a UUID that is passed from the request header and making it available to any logger so that each request gets its own unique identifier. I'd like this to be seamless from anyone who calls into a logger (or log wrapper)
Coming from a .NET background the http context flows with async calls, and this is also possible with the call context in WCF. At that point you can register a function with the logger to return the current uuid for the request based on a logging pattern of something like "%requestID%".
Building a larger distributed system you need to be able to correlate requests across multiple stacks.
But, being new to scala and play I'm not even sure where to look for a way to do this?
What you are looking for in Java is called the Mapped Diagnostic Context or MDC (at least by SLF4J) - here's an article I found that details how to set this up for Play. In the interest of preserving the details for future visitors here is the code used for an MDC-propagating Akka dispatcher:
package monitoring
import java.util.concurrent.TimeUnit
import akka.dispatch._
import com.typesafe.config.Config
import org.slf4j.MDC
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.{Duration, FiniteDuration}
/**
* Configurator for a MDC propagating dispatcher.
* Authored by Yann Simon
* See: http://yanns.github.io/blog/2014/05/04/slf4j-mapped-diagnostic-context-mdc-with-play-framework/
*
* To use it, configure play like this:
* {{{
* play {
* akka {
* actor {
* default-dispatcher = {
* type = "monitoring.MDCPropagatingDispatcherConfigurator"
* }
* }
* }
* }
* }}}
*
* Credits to James Roper for the [[https://github.com/jroper/thread-local-context-propagation/ initial implementation]]
*/
class MDCPropagatingDispatcherConfigurator(config: Config, prerequisites: DispatcherPrerequisites)
extends MessageDispatcherConfigurator(config, prerequisites) {
private val instance = new MDCPropagatingDispatcher(
this,
config.getString("id"),
config.getInt("throughput"),
FiniteDuration(config.getDuration("throughput-deadline-time", TimeUnit.NANOSECONDS), TimeUnit.NANOSECONDS),
configureExecutor(),
FiniteDuration(config.getDuration("shutdown-timeout", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS))
override def dispatcher(): MessageDispatcher = instance
}
/**
* A MDC propagating dispatcher.
*
* This dispatcher propagates the MDC current request context if it's set when it's executed.
*/
class MDCPropagatingDispatcher(_configurator: MessageDispatcherConfigurator,
id: String,
throughput: Int,
throughputDeadlineTime: Duration,
executorServiceFactoryProvider: ExecutorServiceFactoryProvider,
shutdownTimeout: FiniteDuration)
extends Dispatcher(_configurator, id, throughput, throughputDeadlineTime, executorServiceFactoryProvider, shutdownTimeout ) {
self =>
override def prepare(): ExecutionContext = new ExecutionContext {
// capture the MDC
val mdcContext = MDC.getCopyOfContextMap
def execute(r: Runnable) = self.execute(new Runnable {
def run() = {
// backup the callee MDC context
val oldMDCContext = MDC.getCopyOfContextMap
// Run the runnable with the captured context
setContextMap(mdcContext)
try {
r.run()
} finally {
// restore the callee MDC context
setContextMap(oldMDCContext)
}
}
})
def reportFailure(t: Throwable) = self.reportFailure(t)
}
private[this] def setContextMap(context: java.util.Map[String, String]) {
if (context == null) {
MDC.clear()
} else {
MDC.setContextMap(context)
}
}
}
You can then set values in the MDC using MDC.put and remove it using MDC.remove (alternatively, take a look at putCloseable if you need to add and remove some context from a set of synchronous calls):
import org.slf4j.MDC
// Somewhere in a handler
MDC.put("X-UserId", currentUser.id)
// Later, when the user is no longer available
MDC.remove("X-UserId")
and add them to your logging output using %mdc{field-name:default-value}:
<!-- an example from the blog -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %coloredLevel %logger{35} %mdc{X-UserId:--} - %msg%n%rootException</pattern>
</encoder>
</appender>
There are more details in the linked blog post about tweaking the ExecutionContext that Play uses to propagate the MDC context correctly (as an alternative approach).
I have a template structure like this:
modal.scala.view
#()
... HTML code to display a modal in my app ...
foo.scala.view
#()
#scripts {
... The scripts required by foo.scala.view components ...
... The scripts required by modal.scala.view ... I want to avoid this!
}
#main(scripts){
... HTML code of foo ...
#modal
}
main.scala.view
#(scripts: Html)
... Main HTML code ...
#scripts
I would like to keep the scripts of modal in the modal.scala.view but I cant find a way to pass the scripts from the sub-template to the parent in order to render them in the correct place of the main template. Anyideas? Thanks in advance!
I don't think there's a canonical answer to your question that the Play team has blessed, but I can think of a couple of approaches: a monadic approach and an imperative approach.
Wrap views in sub-controllers; encapsulate scripts in the output
A large project I'm working on uses this strategy. We created a MultipartHtml type that contains Html output that should be contained in the document body and a type we created called Resources which contain stuff that should go elsewhere. We treat this type like a monad, so that we can map and flatMap it to manipulate the Html document content, while accumulating and deduplicating the Resources.
All of our controllers return MultipartHtml. They construct an instance from the results of the views and then simply :+ Resource tags to the result. Our page-level controllers composite these pieces together. The core of what we do looks something like this:
/**
* Output type for components to render body, head and end-of-body content
* #param bodyMarkup the visual part of the component output
* #param resources tags for content to include in the head or footer
*/
case class MultipartHtml(bodyMarkup: Html, resources: Seq[MultipartHtml.Resource] = Nil) {
import com.huffpost.hyperion.lib.MultipartHtml._
/**
* Apply a transformation to the body content of this object
* #param bodyMapper transformation function
* #return a new object with transformed body content
*/
def map(bodyMapper: Html => Html): MultipartHtml = MultipartHtml(bodyMapper(bodyMarkup), resources)
/**
* #param bodyMapper transformation function
* #return the bodyMapper result combined with the component resource list
*/
def flatMap(bodyMapper: Html => MultipartHtml): MultipartHtml = bodyMapper(bodyMarkup) ++ resources
/**
* Add a head and/or footer content to this object
* #param resources the resources to add
* #return a new object with the resource added
*/
def ++(resources: GenTraversableOnce[Resource]): MultipartHtml = resources.foldLeft(this)(_ :+ _)
/**
* Add a head or footer content to this object
* #param resource the resource to add
* #return a new object with the resource added
*/
def :+(resource: Resource): MultipartHtml = MultipartHtml(bodyMarkup, (resources :+ resource).distinct)
/**
* Prepend a head or footer content to this object
* #param resource the resource to add
* #return a new object with the resource added
*/
def +:(resource: Resource): MultipartHtml = MultipartHtml(bodyMarkup, (resource +: resources).distinct)
/** Get tags by resource type for injection into a template */
def renderResourcesByType(resourceType: ResourceType): Html = Html(resources.filter(_.resourceType == resourceType).mkString("\n"))
}
/** Utility methods for MultipartHtml type */
object MultipartHtml {
/** Empty MultipartHtml */
def empty = MultipartHtml(Html(""))
/** A resource that can be imported in the HTML head or footer*/
trait ResourceType
trait Resource {
def resourceType: ResourceType
}
object HeadTag extends ResourceType
object FooterTag extends ResourceType
/** A style tag */
case class StyleTag(styleUrl: String) extends Resource {
val resourceType = HeadTag
override def toString = {
val assetUrl = routes.Assets.at(styleUrl).url
s"""<link rel="stylesheet" type="text/css" media="screen" href="$assetUrl">"""
}
}
/** A script tag */
case class ScriptTag(scriptUrl: String) extends Resource {
val resourceType = FooterTag
override def toString = {
val assetUrl = routes.Assets.at(s"javascript/$scriptUrl").url
s"""<script type="text/javascript" src="$assetUrl"></script>"""
}
}
}
There's a whole architecture built on top of this, but I probably shouldn't share too much, as it's an unreleased product.
Create a helper that stores scripts in a mutable collection
Another strategy might be to have your top level controller create an object that stores up the tags in a mutable data structure. I haven't tried this myself, but you could do something like:
import play.twirl.api.Html
case class TagStore(id: String) {
val tags = scala.collection.mutable.Set[Html]()
def addTag(tag: Html): Unit = {
store += tag
}
}
Then in your template, you could do:
#(addTag: Html => Unit)
#addTag {
<script src="myscript.js"></script>
}
#* Generate HTML *#
Downside here is that you have to forward this object down the line somehow, which could be a pain if your hierarchy of partial views can get deep.