├── project ├── build.properties ├── plugins.sbt └── PublicOnFileSystem.scala ├── app ├── views │ ├── index.scala.html │ ├── main.scala.html │ ├── main.scala.stream │ └── explanation.scala.html ├── controllers │ ├── Comments.scala │ └── Application.scala └── ui │ └── HtmlStream.scala ├── .gitignore ├── public └── javascripts │ ├── serverside.js │ ├── Comment.js │ ├── CommentList.js │ ├── CommentForm.js │ ├── main.js │ ├── CommentBox.js │ ├── output.js │ └── react.js ├── test ├── IntegrationSpec.scala └── ApplicationSpec.scala ├── conf ├── logback.xml ├── routes └── application.conf └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.0") 2 | -------------------------------------------------------------------------------- /app/views/index.scala.html: -------------------------------------------------------------------------------- 1 | @(content: Html = Html("")) 2 | 3 | @main("React.js integrated with Play", content) 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | project/project 3 | project/target 4 | target 5 | tmp 6 | .history 7 | dist 8 | /.idea 9 | /*.iml 10 | /out 11 | /.idea_modules 12 | /.classpath 13 | /.project 14 | /RUNNING_PID 15 | /.settings 16 | -------------------------------------------------------------------------------- /public/javascripts/serverside.js: -------------------------------------------------------------------------------- 1 | var React = require('./react'), 2 | CommentBox = require('./CommentBox'); 3 | 4 | // take data from parameters 5 | var data = JSON.parse(process.argv[2]); 6 | 7 | var backend = { 8 | loadCommentsFromServer: function(settings) { 9 | }, 10 | handleCommentSubmit: function(settings) { 11 | } 12 | }; 13 | 14 | console.log(React.renderComponentToString(CommentBox(backend)({data: data, onServerSide: true}))); 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/javascripts/Comment.js: -------------------------------------------------------------------------------- 1 | var React = require('./react'); 2 | 3 | var Comment = React.createClass({ 4 | render: function() { 5 | return ( 6 | React.DOM.div({ 7 | className: 'commentAuthor', 8 | children: [ 9 | React.DOM.h1({className: 'commentAuthor', children: this.props.author}), 10 | this.props.children 11 | ] 12 | }) 13 | ); 14 | } 15 | }); 16 | 17 | module.exports = Comment; -------------------------------------------------------------------------------- /public/javascripts/CommentList.js: -------------------------------------------------------------------------------- 1 | var React = require('./react'); 2 | var Comment = require('./Comment'); 3 | 4 | var CommentList = React.createClass({ 5 | render: function() { 6 | var commentsNodes = this.props.data.map(function(comment) { 7 | return Comment({author: comment.author, children: comment.text}) 8 | }); 9 | return ( 10 | React.DOM.div({ 11 | className: 'commentList', 12 | children: commentsNodes 13 | }) 14 | ); 15 | } 16 | }); 17 | 18 | module.exports = CommentList; 19 | -------------------------------------------------------------------------------- /project/PublicOnFileSystem.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Keys._ 3 | import play.Play.autoImport._ 4 | import com.typesafe.sbt.SbtNativePackager._ 5 | 6 | object PublicOnFileSystem { 7 | 8 | val settings = Seq( 9 | mappings in Universal <++= (baseDirectory in Compile) { _ / "public" } map { dir: File => 10 | val directoryLen = dir.getCanonicalPath.length 11 | val pathFinder = dir ** "*" 12 | pathFinder.get map { 13 | publicFile: File => 14 | publicFile -> ("public/" + publicFile.getCanonicalPath.substring(directoryLen)) 15 | } 16 | } 17 | ) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /app/views/main.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String, content: Html) 2 | 3 | 4 | 5 | 6 | 7 | @title 8 | 9 | 10 |
@content
11 |

HTML from the server

12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/IntegrationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.specs2.mutable._ 2 | import org.specs2.runner._ 3 | import org.junit.runner._ 4 | 5 | import play.api.test._ 6 | import play.api.test.Helpers._ 7 | 8 | /** 9 | * add your integration spec here. 10 | * An integration test will fire up a whole play application in a real (or headless) browser 11 | */ 12 | @RunWith(classOf[JUnitRunner]) 13 | class IntegrationSpec extends Specification { 14 | 15 | "Application" should { 16 | 17 | "work from within a browser" in new WithBrowser { 18 | 19 | browser.goTo("http://localhost:" + port) 20 | 21 | browser.pageSource must contain("Your new application is ready.") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/views/main.scala.stream: -------------------------------------------------------------------------------- 1 | @(content: ui.HtmlStream) 2 | 3 | 4 | 5 | 6 | 7 | Integration of Play and React 8 | 9 | 10 | 11 | 12 | 13 |
@content
14 |

HTML from the server

15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %coloredLevel - %logger - %message%n%xException 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | # Home page 6 | GET / controllers.Application.index 7 | GET /clientSide controllers.Application.clientSide 8 | GET /serverSide controllers.Application.serverSide 9 | GET /serverSide2 controllers.Application.serverSideTrireme 10 | GET /serverSideJavax controllers.Application.serverSideJavax 11 | GET /serverSideNode controllers.Application.serverSideNode 12 | GET /serverSideStream controllers.Application.serverSideStream 13 | GET /comments.json controllers.Comments.list 14 | POST /comments.json controllers.Comments.newPost 15 | 16 | # Map static resources from the /public folder to the /assets URL path 17 | GET /assets/*file controllers.Assets.at(path="/public", file) 18 | -------------------------------------------------------------------------------- /test/ApplicationSpec.scala: -------------------------------------------------------------------------------- 1 | import org.specs2.mutable._ 2 | import org.specs2.runner._ 3 | import org.junit.runner._ 4 | 5 | import play.api.test._ 6 | import play.api.test.Helpers._ 7 | 8 | /** 9 | * Add your spec here. 10 | * You can mock out a whole application including requests, plugins etc. 11 | * For more information, consult the wiki. 12 | */ 13 | @RunWith(classOf[JUnitRunner]) 14 | class ApplicationSpec extends Specification { 15 | 16 | "Application" should { 17 | 18 | "send 404 on a bad request" in new WithApplication{ 19 | route(FakeRequest(GET, "/boum")) must beSome.which (status(_) == NOT_FOUND) 20 | } 21 | 22 | "render the index page" in new WithApplication{ 23 | val home = route(FakeRequest(GET, "/")).get 24 | 25 | status(home) must equalTo(OK) 26 | contentType(home) must beSome.which(_ == "text/html") 27 | contentAsString(home) must contain ("Your new application is ready.") 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/views/explanation.scala.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Play integration with ReactJS 6 | 7 | 8 |

This sample application is a demo of the integration of ReactJS with Play!

9 | 10 |

The application simulates a slow backend so that the AJAX requests take time to respond

11 | 12 |

Render the application only on the client side (JavaScript)

13 |

The application is pre-rendered on the server side + dynamically changed on the client side (JavaScript)

14 |

The application is pre-rendered on the server side and streamed to load JS immediately + dynamically changed on the client side (JavaScript)

15 | 16 | 17 | -------------------------------------------------------------------------------- /public/javascripts/CommentForm.js: -------------------------------------------------------------------------------- 1 | var React = require('./react'); 2 | 3 | var CommentForm = React.createClass({ 4 | handleSubmit: function() { 5 | var author = this.refs.author.getDOMNode().value.trim(); 6 | var text = this.refs.text.getDOMNode().value.trim(); 7 | if (!text || !author) { 8 | return false; 9 | } 10 | this.props.onCommentSubmit({author: author, text: text}); 11 | this.refs.author.getDOMNode().value = ''; 12 | this.refs.text.getDOMNode().value = ''; 13 | return false; 14 | }, 15 | render: function() { 16 | return ( 17 | React.DOM.form({ 18 | className: 'commentForm', 19 | onSubmit: this.handleSubmit, 20 | children: [ 21 | React.DOM.input({type: 'text', ref:'author', placeholder: 'Your name'}), 22 | React.DOM.input({type: 'text', ref:'text', placeholder: 'Say something...'}), 23 | React.DOM.input({type: 'submit', value: 'Post'}) 24 | ] 25 | }) 26 | ); 27 | } 28 | }); 29 | 30 | module.exports = CommentForm; 31 | -------------------------------------------------------------------------------- /public/javascripts/main.js: -------------------------------------------------------------------------------- 1 | var backend = { 2 | loadCommentsFromServer: function(settings) { 3 | // ugly mutable transformation 4 | settings.url = '/comments.json'; 5 | settings.dataType = 'json'; 6 | 7 | $.ajax(settings); 8 | }, 9 | handleCommentSubmit: function(settings) { 10 | // ugly mutable transformation 11 | settings.url = '/comments.json'; 12 | settings.dataType = 'json'; 13 | settings.contentType = 'application/json'; 14 | settings.type = 'POST'; 15 | 16 | $.ajax(settings); 17 | } 18 | }; 19 | 20 | if (document.getElementById('commentBox') == null) { 21 | React.renderComponent( 22 | CommentBox(backend)({data: []}), 23 | document.getElementById('content') 24 | ); 25 | } else { 26 | // commentBox already there 27 | // bind the client side component only with data 28 | backend.loadCommentsFromServer({ 29 | success: function(data) { 30 | React.renderComponent( 31 | CommentBox(backend)({data: data}), 32 | document.getElementById('content') 33 | ); 34 | }, 35 | error: function(xhr, status, err) { 36 | console.error(status, err.toString()); 37 | } 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /app/controllers/Comments.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api.mvc.{Action, Controller} 4 | import play.api.libs.json.Json 5 | import scala.concurrent.{Future, Promise} 6 | import play.api.libs.concurrent.Akka 7 | import play.api.libs.concurrent.Execution.Implicits._ 8 | 9 | object Comments extends Controller { 10 | 11 | case class Comment(author: String, text: String) 12 | object Comment { 13 | implicit val jsonFormat = Json.format[Comment] 14 | } 15 | 16 | object CommentRepository { 17 | private var comments = List( 18 | Comment(author = "Pete Hunt", text = "This is one comment"), 19 | Comment(author = "Jordan Walke", text = "This is *another* comment") 20 | ) 21 | 22 | def addComment(comment: Comment) { 23 | comments = comment :: comments 24 | } 25 | 26 | def getComments = { 27 | import play.api.Play.current 28 | import scala.concurrent.duration._ 29 | 30 | // simulate slow backend 31 | val promise = Promise[Seq[Comment]]() 32 | Akka.system.scheduler.scheduleOnce(500 milliseconds) { 33 | promise.success(comments) 34 | } 35 | promise.future 36 | } 37 | } 38 | 39 | def list = Action.async { 40 | CommentRepository.getComments map { comments => 41 | Ok(Json.toJson(comments)) 42 | } 43 | } 44 | 45 | def newPost = Action.async { request => 46 | val result = for { 47 | json <- request.body.asJson 48 | comment <- json.asOpt[Comment] 49 | } yield { 50 | CommentRepository.addComment(comment) 51 | CommentRepository.getComments map { comments => 52 | Ok(Json.toJson(comments)) 53 | } 54 | } 55 | result getOrElse Future.successful(BadRequest) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # 8 | # This must be changed for production, but we recommend not changing it in this file. 9 | # 10 | # See http://www.playframework.com/documentation/latest/ApplicationSecret for more details. 11 | play.crypto.secret = "changeme" 12 | 13 | # The application languages 14 | # ~~~~~ 15 | play.i18n.langs = [ "en" ] 16 | 17 | # Router 18 | # ~~~~~ 19 | # Define the Router object to use for this application. 20 | # This router will be looked up first when the application is starting up, 21 | # so make sure this is the entry point. 22 | # Furthermore, it's assumed your route file is named properly. 23 | # So for an application router like `my.application.Router`, 24 | # you may need to define a router file `conf/my.application.routes`. 25 | # Default to Routes in the root package (and conf/routes) 26 | # play.http.router = my.application.Routes 27 | 28 | # Database configuration 29 | # ~~~~~ 30 | # You can declare as many datasources as you want. 31 | # By convention, the default datasource is named `default` 32 | # 33 | # db.default.driver=org.h2.Driver 34 | # db.default.url="jdbc:h2:mem:play" 35 | # db.default.user=sa 36 | # db.default.password="" 37 | 38 | # Evolutions 39 | # ~~~~~ 40 | # You can disable evolutions if needed 41 | # play.evolutions.enabled=false 42 | 43 | # You can disable evolutions for a specific datasource if necessary 44 | # play.evolutions.db.default.enabled=false 45 | 46 | blocking-process-io-dispatcher { 47 | type = Dispatcher 48 | executor = "thread-pool-executor" 49 | thread-pool-executor { 50 | core-pool-size-min = 3 51 | core-pool-size-factor = 1.0 52 | core-pool-size-max = 100 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /public/javascripts/CommentBox.js: -------------------------------------------------------------------------------- 1 | var React = require('./react'), 2 | CommentList = require('./CommentList'), 3 | CommentForm = require('./CommentForm'); 4 | 5 | var CommentBox = function(backend) { 6 | return React.createClass({ 7 | getInitialState: function() { 8 | return {data: this.props.data || []}; 9 | }, 10 | handleCommentSubmit: function(comment) { 11 | backend.handleCommentSubmit({ 12 | data: JSON.stringify(comment), 13 | success: function(data) { 14 | this.setState({data: data}); 15 | }.bind(this) 16 | }); 17 | }, 18 | loadCommentsFromServer: function() { 19 | backend.loadCommentsFromServer({ 20 | success: function(data) { 21 | this.setState({data: data}); 22 | }.bind(this), 23 | error: function(xhr, status, err) { 24 | console.error(status, err.toString()); 25 | }.bind(this) 26 | }); 27 | }, 28 | componentWillMount: function() { 29 | if (!this.props.onServerSide) { 30 | this.loadCommentsFromServer(); 31 | setInterval(this.loadCommentsFromServer, 2000); 32 | } 33 | }, 34 | render: function() { 35 | return ( 36 | React.DOM.div({ 37 | id: 'commentBox', 38 | className: 'commentBox', 39 | children: [ 40 | React.DOM.h1({children: 'Comments'}), 41 | CommentList({data: this.state.data}), 42 | CommentForm({onCommentSubmit: this.handleCommentSubmit}) 43 | ] 44 | }) 45 | ); 46 | } 47 | }); 48 | }; 49 | 50 | module.exports = CommentBox; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | play-react 2 | ========== 3 | 4 | Proof of concept of running react.js on the server side with play 5 | 6 | [Details](http://yanns.github.io/blog/2014/03/15/server-side-rendering-for-javascript-reactjs-framework/) 7 | 8 | Rendering on the Client Side 9 | ---------------------------- 10 | 11 | The page under [/clientSide](http://play-react.herokuapp.com/clientSide) suffers from a "Flash Of No-Data Content". 12 | To display some data, the browser must: 13 | 1. load the page and the html, displaying html without data 14 | 2. make an ajax request to the server 15 | 3. wait for an anwser 16 | 4. display the data 17 | 18 | This sample application simulates a slow AJAX response to make this flash visible. 19 | 20 | Pre rendering on the Server Side 21 | -------------------------------- 22 | 23 | The page under [/serverSide](http://play-react.herokuapp.com/serverSide) pre-renders the html on the server side 24 | and displays the page already with data. 25 | 26 | The changes on the client side are then applied dynamically with the normal client side JavaScript 27 | ReactJS code. 28 | 29 | The pre-render the ReactJS components on the server side, the following libraries are used: 30 | - [/serverSide](http://play-react.herokuapp.com/serverSide) uses [trireme](https://github.com/apigee/trireme) provides a Node API on the JVM with Rhino 31 | - [/serverSide2](http://play-react.herokuapp.com/serverSide2) uses [js-engine](https://github.com/typesafehub/js-engine) that itself uses [trireme](https://github.com/apigee/trireme) behind [Akka](http://akka.io/) actors 32 | - /serverSideJavax uses [js-engine](https://github.com/typesafehub/js-engine) that itself uses the Javax engine 33 | - /serverSideNode uses [js-engine](https://github.com/typesafehub/js-engine) that itself uses NodeJS 34 | 35 | Pre rendering on the Server Side and streaming the page 36 | ------------------------------------------------------- 37 | 38 | The pre-rendered page waits for the data before sending any bytes to the browser. 39 | To optimize this, another version [/serverSideStream](http://play-react.herokuapp.com/serverSideStream) sends the first bytes immediately, to let the browser load the CSS / JavaScript, and then sends the rest of the body when available. 40 | For this, I used the Facebook’s BigPipe concept as presented in the [talk “Building composable, streaming, testable Play apps” from Yevgeniy Brikman](http://de.slideshare.net/brikis98/composable-and-streamable-play-apps) 41 | 42 | TODOs: 43 | ------ 44 | 45 | - use nodejs styles modules for the client side libraries -> bind [browserify](http://browserify.org/) or similar tool 46 | 47 | -------------------------------------------------------------------------------- /public/javascripts/output.js: -------------------------------------------------------------------------------- 1 | // for the time, simply copy / paste from all node modules 2 | // in the future, integrate Browserify and something similar 3 | var Comment = React.createClass({ 4 | render: function() { 5 | return ( 6 | React.DOM.div({ 7 | className: 'commentAuthor', 8 | children: [ 9 | React.DOM.h1({className: 'commentAuthor', children: this.props.author}), 10 | this.props.children 11 | ] 12 | }) 13 | ); 14 | } 15 | }); 16 | 17 | var CommentList = React.createClass({ 18 | render: function() { 19 | var commentsNodes = this.props.data.map(function(comment) { 20 | return Comment({author: comment.author, children: comment.text}) 21 | }); 22 | return ( 23 | React.DOM.div({ 24 | className: 'commentList', 25 | children: commentsNodes 26 | }) 27 | ); 28 | } 29 | }); 30 | 31 | var CommentForm = React.createClass({ 32 | handleSubmit: function() { 33 | var author = this.refs.author.getDOMNode().value.trim(); 34 | var text = this.refs.text.getDOMNode().value.trim(); 35 | if (!text || !author) { 36 | return false; 37 | } 38 | this.props.onCommentSubmit({author: author, text: text}); 39 | this.refs.author.getDOMNode().value = ''; 40 | this.refs.text.getDOMNode().value = ''; 41 | return false; 42 | }, 43 | render: function() { 44 | return ( 45 | React.DOM.form({ 46 | className: 'commentForm', 47 | onSubmit: this.handleSubmit, 48 | children: [ 49 | React.DOM.input({type: 'text', ref:'author', placeholder: 'Your name'}), 50 | React.DOM.input({type: 'text', ref:'text', placeholder: 'Say something...'}), 51 | React.DOM.input({type: 'submit', value: 'Post'}) 52 | ] 53 | }) 54 | ); 55 | } 56 | }); 57 | 58 | var CommentBox = function(backend) { 59 | return React.createClass({ 60 | getInitialState: function() { 61 | return {data: this.props.data || []}; 62 | }, 63 | handleCommentSubmit: function(comment) { 64 | backend.handleCommentSubmit({ 65 | data: JSON.stringify(comment), 66 | success: function(data) { 67 | this.setState({data: data}); 68 | }.bind(this) 69 | }); 70 | }, 71 | loadCommentsFromServer: function() { 72 | backend.loadCommentsFromServer({ 73 | success: function(data) { 74 | this.setState({data: data}); 75 | }.bind(this), 76 | error: function(xhr, status, err) { 77 | console.error(status, err.toString()); 78 | }.bind(this) 79 | }); 80 | }, 81 | componentWillMount: function() { 82 | if (!this.props.onServerSide) { 83 | this.loadCommentsFromServer(); 84 | setInterval(this.loadCommentsFromServer, 2000); 85 | } 86 | }, 87 | render: function() { 88 | return ( 89 | React.DOM.div({ 90 | className: 'commentBox', 91 | children: [ 92 | React.DOM.h1({children: 'Comments'}), 93 | CommentList({data: this.state.data}), 94 | CommentForm({onCommentSubmit: this.handleCommentSubmit}) 95 | ] 96 | }) 97 | ); 98 | } 99 | }); 100 | }; 101 | -------------------------------------------------------------------------------- /app/controllers/Application.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import java.io.{ByteArrayOutputStream, File} 4 | 5 | import akka.actor.Props 6 | import akka.util.Timeout 7 | import com.typesafe.jse.Engine.JsExecutionResult 8 | import com.typesafe.jse.{Engine, Node, JavaxEngine, Trireme} 9 | import io.apigee.trireme.core._ 10 | import play.api.Play.current 11 | import play.api._ 12 | import play.api.libs.concurrent.Akka 13 | import play.api.libs.concurrent.Execution.Implicits._ 14 | import play.api.libs.json.Json 15 | import play.api.mvc._ 16 | import play.twirl.api.Html 17 | import ui.HtmlStream 18 | 19 | import scala.concurrent.Promise 20 | import scala.concurrent.duration._ 21 | 22 | 23 | object Application extends Controller { 24 | 25 | def index = Action { 26 | Ok(views.html.explanation()) 27 | } 28 | 29 | def clientSide = Action { 30 | Ok(views.html.index()) 31 | } 32 | 33 | private def initialData() = Comments.CommentRepository.getComments map { comments => 34 | Json.stringify(Json.toJson(comments)) 35 | } 36 | 37 | // with trireme directly 38 | def serverSide = Action.async { 39 | initialData flatMap { data => 40 | val serverside = Play.getFile("public/javascripts/serverside.js") 41 | val stdout = new ByteArrayOutputStream() 42 | val env = new NodeEnvironment() 43 | val sandbox = new Sandbox() 44 | sandbox.setStdout(stdout) 45 | val script = env.createScript("serverside.js", new File(serverside.toURI), Array(data)) 46 | script.setSandbox(sandbox) 47 | val htmlResult = Promise[Result]() 48 | script.execute().setListener(new ScriptStatusListener() { 49 | override def onComplete(script: NodeScript, status: ScriptStatus): Unit = { 50 | val result = stdout.toString("UTF-8") 51 | htmlResult.success(Ok(views.html.index(Html(result)))) 52 | } 53 | }) 54 | htmlResult.future 55 | } 56 | } 57 | 58 | def serverSideJavax = serverSideWithJsEngine(JavaxEngine.props()) 59 | 60 | // with js-engine 61 | def serverSideTrireme = serverSideWithJsEngine(Trireme.props()) 62 | 63 | // with node 64 | def serverSideNode = serverSideWithJsEngine(Node.props()) 65 | 66 | private def serverSideWithJsEngine(jsEngine: Props) = Action.async { request => 67 | import akka.pattern.ask 68 | 69 | val serverside = Play.getFile("public/javascripts/serverside.js") 70 | implicit val timeout = Timeout(5.seconds) 71 | val engine = Akka.system.actorOf(jsEngine, s"engine-${request.id}") 72 | 73 | for { 74 | data <- initialData() 75 | result <- (engine ? Engine.ExecuteJs( 76 | source = new File(serverside.toURI), 77 | args = List(data), 78 | timeout = timeout.duration 79 | )).mapTo[JsExecutionResult] 80 | } yield { 81 | Ok(views.html.index(Html(new String(result.output.toArray, "UTF-8")))) 82 | } 83 | } 84 | 85 | def serverSideStream = Action { request => 86 | import akka.pattern.ask 87 | import ui.HtmlStreamImplicits._ 88 | 89 | 90 | val serverside = Play.getFile("public/javascripts/serverside.js") 91 | implicit val timeout = Timeout(5.seconds) 92 | val engine = Akka.system.actorOf(Trireme.props(), s"engine-${request.id}") 93 | 94 | val prerendererHtml = for { 95 | data <- initialData() 96 | result <- (engine ? Engine.ExecuteJs( 97 | source = new File(serverside.toURI), 98 | args = List(data), 99 | timeout = timeout.duration 100 | )).mapTo[JsExecutionResult] 101 | } yield { 102 | Html(new String(result.output.toArray, "UTF-8")) 103 | } 104 | 105 | val prerendererHtmlStream = HtmlStream(prerendererHtml) 106 | Ok.chunked(views.stream.main(prerendererHtmlStream)) 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /app/ui/HtmlStream.scala: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import play.api.http.{ContentTypeOf, Writeable} 4 | import play.api.libs.concurrent.Execution.Implicits._ 5 | import play.api.libs.iteratee.{Enumeratee, Enumerator, Iteratee} 6 | import play.api.mvc.{Codec, Result} 7 | import play.mvc.Results.Chunks 8 | import play.mvc.Results.Chunks.Out 9 | import play.twirl.api.{Appendable, Format, Html, HtmlFormat} 10 | 11 | import scala.collection.immutable.Seq 12 | import scala.concurrent.Future 13 | 14 | /** 15 | * A custom Appendable that lets us have .scala.stream templates instead of .scala.html. These templates can mix Html 16 | * markup with Enumerators that contain Html markup. We add this class as a custom template type in build.sbt. 17 | */ 18 | case class HtmlStream(enumerator: Enumerator[Html]) extends Appendable[HtmlStream] { 19 | def +=(other: HtmlStream): HtmlStream = andThen(other) 20 | 21 | def andThen(other: HtmlStream): HtmlStream = HtmlStream(enumerator.andThen(other.enumerator)) 22 | } 23 | 24 | /** 25 | * Companion object for HtmlStream that contains convenient factory and composition methods. 26 | */ 27 | object HtmlStream { 28 | 29 | /** 30 | * Create an HtmlStream from a String 31 | */ 32 | def apply(text: String): HtmlStream = { 33 | apply(Html(text)) 34 | } 35 | 36 | /** 37 | * Create an HtmlStream from Html 38 | */ 39 | def apply(html: Html): HtmlStream = { 40 | HtmlStream(Enumerator(html)) 41 | } 42 | 43 | /** 44 | * Create an HtmlStream from a Future that will eventually contain Html 45 | */ 46 | def apply(eventuallyHtml: Future[Html]): HtmlStream = { 47 | flatten(eventuallyHtml.map(apply)) 48 | } 49 | 50 | /** 51 | * Create an HtmlStream from the body of the SimpleResult. 52 | */ 53 | def fromResult(result: Result): HtmlStream = { 54 | HtmlStream(result.body.map(bytes => Html(new String(bytes, "UTF-8")))) 55 | } 56 | 57 | /** 58 | * Create an HtmlStream from a the body of a Future[SimpleResult]. 59 | */ 60 | def fromResult(result: Future[Result]): HtmlStream = { 61 | flatten(result.map(fromResult)) 62 | } 63 | 64 | /** 65 | * Interleave multiple HtmlStreams together. Interleaving is done based on whichever HtmlStream next has input ready, 66 | * if multiple have input ready, the order is undefined. 67 | */ 68 | def interleave(streams: HtmlStream*): HtmlStream = { 69 | HtmlStream(Enumerator.interleave(streams.map(_.enumerator))) 70 | } 71 | 72 | /** 73 | * Create an HtmlStream from a Future that will eventually contain an HtmlStream. 74 | */ 75 | def flatten(eventuallyStream: Future[HtmlStream]): HtmlStream = { 76 | HtmlStream(Enumerator.flatten(eventuallyStream.map(_.enumerator))) 77 | } 78 | 79 | /** 80 | * Java API. Provides a convenience method for interleaving streams from a Java controller that doesn't rely on 81 | * scala's Seq 82 | */ 83 | def interleave(streams: java.util.List[HtmlStream]): HtmlStream = { 84 | import scala.collection.JavaConverters._ 85 | HtmlStream(Enumerator.interleave(streams.asScala.map(_.enumerator))) 86 | } 87 | 88 | /** 89 | * Java API. Creates a Chunks object that can be returned from a Java controller to stream out the HtmlStream. 90 | */ 91 | def toChunks(stream: HtmlStream): Chunks[Html] = { 92 | val utf8 = Codec.javaSupported("utf-8") 93 | 94 | new Chunks[Html](Writeable.writeableOf_Content(utf8, ContentTypeOf.contentTypeOf_Html(utf8))) { 95 | def onReady(out: Out[Html]) { 96 | stream.enumerator.run(Iteratee.foreach { html => 97 | if (!html.toString().isEmpty) { 98 | out.write(html) 99 | } 100 | }).onComplete(_ => out.close()) 101 | } 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * A custom Format that lets us have .scala.stream templates instead of .scala.html. These templates can mix Html 108 | * markup with Enumerators that contain Html markup. 109 | */ 110 | object HtmlStreamFormat extends Format[HtmlStream] { 111 | 112 | override def raw(text: String): HtmlStream = 113 | HtmlStream(text) 114 | 115 | override def escape(text: String): HtmlStream = 116 | raw(HtmlFormat.escape(text).body) 117 | 118 | override def empty: HtmlStream = raw("") 119 | 120 | override def fill(elements: Seq[HtmlStream]): HtmlStream = { 121 | 122 | // combine the elements together in the right order 123 | 124 | val enums = elements.map(_.enumerator) 125 | val allEnums = enums.foldLeft(Enumerator.empty[Html]){ case (acc, e) => acc andThen e } 126 | HtmlStream(allEnums) 127 | } 128 | } 129 | 130 | /** 131 | * Useful implicits when working with HtmlStreams 132 | */ 133 | object HtmlStreamImplicits { 134 | 135 | // Implicit conversion so HtmlStream can be passed directly to Ok.feed and Ok.chunked 136 | implicit def toEnumerator(stream: HtmlStream): Enumerator[Html] = { 137 | // Skip empty chunks, as these mean EOF in chunked encoding 138 | stream.enumerator.through(Enumeratee.filter(!_.body.isEmpty)) 139 | } 140 | } -------------------------------------------------------------------------------- /public/javascripts/react.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React v0.9.0 3 | * 4 | * Copyright 2013-2014 Facebook, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var t;"undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.React=e()}}(function(){return function e(t,n,o){function r(a,s){if(!n[a]){if(!t[a]){var u="function"==typeof require&&require;if(!s&&u)return u(a,!0);if(i)return i(a,!0);throw new Error("Cannot find module '"+a+"'")}var c=n[a]={exports:{}};t[a][0].call(c.exports,function(e){var n=t[a][1][e];return r(n?n:e)},c,c.exports,e,t,n,o)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;a8));var w=!1;E.canUseDOM&&(w=D("input")&&(!("documentMode"in document)||document.documentMode>9));var A={get:function(){return S.get.call(this)},set:function(e){N=""+e,S.set.call(this,e)}},k={eventTypes:I,extractEvents:function(e,t,o,r){var i,a;if(n(t)?_?i=s:a=u:x(t)?w?i=d:(i=h,a=f):m(t)&&(i=v),i){var c=i(e,t,o);if(c){var l=R.getPooled(I.change,c,r);return C.accumulateTwoPhaseDispatches(l),l}}a&&a(e,t,o)}};t.exports=k},{"./EventConstants":14,"./EventPluginHub":16,"./EventPropagators":19,"./ExecutionEnvironment":20,"./ReactUpdates":68,"./SyntheticEvent":75,"./isEventSupported":107,"./isTextInputElement":109,"./keyOf":113}],5:[function(e,t){"use strict";var n=0,o={createReactRootIndex:function(){return n++}};t.exports=o},{}],6:[function(e,t){"use strict";function n(e){switch(e){case g.topCompositionStart:return C.compositionStart;case g.topCompositionEnd:return C.compositionEnd;case g.topCompositionUpdate:return C.compositionUpdate}}function o(e,t){return e===g.topKeyDown&&t.keyCode===h}function r(e,t){switch(e){case g.topKeyUp:return-1!==f.indexOf(t.keyCode);case g.topKeyDown:return t.keyCode!==h;case g.topKeyPress:case g.topMouseDown:case g.topBlur:return!0;default:return!1}}function i(e){this.root=e,this.startSelection=c.getSelection(e),this.startValue=this.getText()}var a=e("./EventConstants"),s=e("./EventPropagators"),u=e("./ExecutionEnvironment"),c=e("./ReactInputSelection"),l=e("./SyntheticCompositionEvent"),p=e("./getTextContentAccessor"),d=e("./keyOf"),f=[9,13,27,32],h=229,m=u.canUseDOM&&"CompositionEvent"in window,v=!m||"documentMode"in document&&document.documentMode>8,g=a.topLevelTypes,y=null,C={compositionEnd:{phasedRegistrationNames:{bubbled:d({onCompositionEnd:null}),captured:d({onCompositionEndCapture:null})},dependencies:[g.topBlur,g.topCompositionEnd,g.topKeyDown,g.topKeyPress,g.topKeyUp,g.topMouseDown]},compositionStart:{phasedRegistrationNames:{bubbled:d({onCompositionStart:null}),captured:d({onCompositionStartCapture:null})},dependencies:[g.topBlur,g.topCompositionStart,g.topKeyDown,g.topKeyPress,g.topKeyUp,g.topMouseDown]},compositionUpdate:{phasedRegistrationNames:{bubbled:d({onCompositionUpdate:null}),captured:d({onCompositionUpdateCapture:null})},dependencies:[g.topBlur,g.topCompositionUpdate,g.topKeyDown,g.topKeyPress,g.topKeyUp,g.topMouseDown]}};i.prototype.getText=function(){return this.root.value||this.root[p()]},i.prototype.getData=function(){var e=this.getText(),t=this.startSelection.start,n=this.startValue.length-this.startSelection.end;return e.substr(t,e.length-n-t)};var E={eventTypes:C,extractEvents:function(e,t,a,u){var c,p;if(m?c=n(e):y?r(e,u)&&(c=C.compositionEnd):o(e,u)&&(c=C.compositionStart),v&&(y||c!==C.compositionStart?c===C.compositionEnd&&y&&(p=y.getData(),y=null):y=new i(t)),c){var d=l.getPooled(c,a,u);return p&&(d.data=p),s.accumulateTwoPhaseDispatches(d),d}}};t.exports=E},{"./EventConstants":14,"./EventPropagators":19,"./ExecutionEnvironment":20,"./ReactInputSelection":50,"./SyntheticCompositionEvent":73,"./getTextContentAccessor":103,"./keyOf":113}],7:[function(e,t){"use strict";function n(e,t,n){var o=e.childNodes;o[n]!==t&&(t.parentNode===e&&e.removeChild(t),n>=o.length?e.appendChild(t):e.insertBefore(t,o[n]))}var o,r=e("./Danger"),i=e("./ReactMultiChildUpdateTypes"),a=e("./getTextContentAccessor"),s=a();o="textContent"===s?function(e,t){e.textContent=t}:function(e,t){for(;e.firstChild;)e.removeChild(e.firstChild);if(t){var n=e.ownerDocument||document;e.appendChild(n.createTextNode(t))}};var u={dangerouslyReplaceNodeWithMarkup:r.dangerouslyReplaceNodeWithMarkup,updateTextContent:o,processUpdates:function(e,t){for(var a,s=null,u=null,c=0;a=e[c];c++)if(a.type===i.MOVE_EXISTING||a.type===i.REMOVE_NODE){var l=a.fromIndex,p=a.parentNode.childNodes[l],d=a.parentID;s=s||{},s[d]=s[d]||[],s[d][l]=p,u=u||[],u.push(p)}var f=r.dangerouslyRenderMarkup(t);if(u)for(var h=0;ht)}var o=e("./DOMProperty"),r=e("./escapeTextForBrowser"),i=e("./memoizeStringOnly"),a=i(function(e){return r(e)+'="'}),s={createMarkupForID:function(e){return a(o.ID_ATTRIBUTE_NAME)+r(e)+'"'},createMarkupForProperty:function(e,t){if(o.isStandardName[e]){if(n(e,t))return"";var i=o.getAttributeName[e];return o.hasBooleanValue[e]?r(i):a(i)+r(t)+'"'}return o.isCustomAttribute(e)?null==t?"":a(e)+r(t)+'"':null},setValueForProperty:function(e,t,r){if(o.isStandardName[t]){var i=o.getMutationMethod[t];if(i)i(e,r);else if(n(t,r))this.deleteValueForProperty(e,t);else if(o.mustUseAttribute[t])e.setAttribute(o.getAttributeName[t],""+r);else{var a=o.getPropertyName[t];o.hasSideEffects[t]&&e[a]===r||(e[a]=r)}}else o.isCustomAttribute(t)&&(null==r?e.removeAttribute(o.getAttributeName[t]):e.setAttribute(t,""+r))},deleteValueForProperty:function(e,t){if(o.isStandardName[t]){var n=o.getMutationMethod[t];if(n)n(e,void 0);else if(o.mustUseAttribute[t])e.removeAttribute(o.getAttributeName[t]);else{var r=o.getPropertyName[t],i=o.getDefaultValueForProperty(e.nodeName,t);o.hasSideEffects[t]&&e[r]===i||(e[r]=i)}}else o.isCustomAttribute(t)&&e.removeAttribute(t)}};t.exports=s},{"./DOMProperty":8,"./escapeTextForBrowser":94,"./memoizeStringOnly":114}],10:[function(e,t){"use strict";function n(e){return e.substring(1,e.indexOf(" "))}var o=e("./ExecutionEnvironment"),r=e("./createNodesFromMarkup"),i=e("./emptyFunction"),a=e("./getMarkupWrap"),s=e("./invariant"),u=/^(<[^ \/>]+)/,c="data-danger-index",l={dangerouslyRenderMarkup:function(e){s(o.canUseDOM);for(var t,l={},p=0;pu;u++){var l=s[u];if(l){var p=l.extractEvents(e,t,o,r);p&&(a=i(a,p))}}return a},enqueueEvents:function(e){e&&(c=i(c,e))},processEventQueue:function(){var e=c;c=null,a(e,l),s(!c)},__purge:function(){u={}},__getListenerBank:function(){return u}};t.exports=d},{"./EventPluginRegistry":17,"./EventPluginUtils":18,"./ExecutionEnvironment":20,"./accumulate":84,"./forEachAccumulated":96,"./invariant":106,"./isEventSupported":107}],17:[function(e,t){"use strict";function n(){if(a)for(var e in s){var t=s[e],n=a.indexOf(e);if(i(n>-1),!u.plugins[n]){i(t.extractEvents),u.plugins[n]=t;var r=t.eventTypes;for(var c in r)i(o(r[c],t,c))}}}function o(e,t,n){i(!u.eventNameDispatchConfigs[n]),u.eventNameDispatchConfigs[n]=e;var o=e.phasedRegistrationNames;if(o){for(var a in o)if(o.hasOwnProperty(a)){var s=o[a];r(s,t,n)}return!0}return e.registrationName?(r(e.registrationName,t,n),!0):!1}function r(e,t,n){i(!u.registrationNameModules[e]),u.registrationNameModules[e]=t,u.registrationNameDependencies[e]=t.eventTypes[n].dependencies}var i=e("./invariant"),a=null,s={},u={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},injectEventPluginOrder:function(e){i(!a),a=Array.prototype.slice.call(e),n()},injectEventPluginsByName:function(e){var t=!1;for(var o in e)if(e.hasOwnProperty(o)){var r=e[o];s[o]!==r&&(i(!s[o]),s[o]=r,t=!0)}t&&n()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return u.registrationNameModules[t.registrationName]||null;for(var n in t.phasedRegistrationNames)if(t.phasedRegistrationNames.hasOwnProperty(n)){var o=u.registrationNameModules[t.phasedRegistrationNames[n]];if(o)return o}return null},_resetEventPlugins:function(){a=null;for(var e in s)s.hasOwnProperty(e)&&delete s[e];u.plugins.length=0;var t=u.eventNameDispatchConfigs;for(var n in t)t.hasOwnProperty(n)&&delete t[n];var o=u.registrationNameModules;for(var r in o)o.hasOwnProperty(r)&&delete o[r]}};t.exports=u},{"./invariant":106}],18:[function(e,t){"use strict";function n(e){return e===h.topMouseUp||e===h.topTouchEnd||e===h.topTouchCancel}function o(e){return e===h.topMouseMove||e===h.topTouchMove}function r(e){return e===h.topMouseDown||e===h.topTouchStart}function i(e,t){var n=e._dispatchListeners,o=e._dispatchIDs;if(Array.isArray(n))for(var r=0;r1){for(var r=Array(n),i=0;n>i;i++)r[i]=arguments[i+1];this.props.children=r}},mountComponent:function(e,t,n){a(!this.isMounted());var o=this.props;null!=o.ref&&r.addComponentAsRefTo(this,o.ref,this._owner),this._rootNodeID=e,this._lifeCycleState=c.MOUNTED,this._mountDepth=n},unmountComponent:function(){a(this.isMounted());var e=this.props;null!=e.ref&&r.removeComponentAsRefFrom(this,e.ref,this._owner),l.unmountIDFromEnvironment(this._rootNodeID),this._rootNodeID=null,this._lifeCycleState=c.UNMOUNTED},receiveComponent:function(e,t){a(this.isMounted()),this._pendingOwner=e._owner,this._pendingProps=e.props,this._performUpdateIfNecessary(t)},performUpdateIfNecessary:function(){var e=l.ReactReconcileTransaction.getPooled();e.perform(this._performUpdateIfNecessary,this,e),l.ReactReconcileTransaction.release(e)},_performUpdateIfNecessary:function(e){if(null!=this._pendingProps){var t=this.props,n=this._owner;this.props=this._pendingProps,this._owner=this._pendingOwner,this._pendingProps=null,this.updateComponent(e,t,n)}},updateComponent:function(e,t,n){var o=this.props;(this._owner!==n||o.ref!==t.ref)&&(null!=t.ref&&r.removeComponentAsRefFrom(this,t.ref,n),null!=o.ref&&r.addComponentAsRefTo(this,o.ref,this._owner))},mountComponentIntoNode:function(e,t,n){var o=l.ReactReconcileTransaction.getPooled();o.perform(this._mountComponentIntoNode,this,e,t,o,n),l.ReactReconcileTransaction.release(o)},_mountComponentIntoNode:function(e,t,n,o){var r=this.mountComponent(e,n,0);l.mountImageIntoNode(r,t,o)},isOwnedBy:function(e){return this._owner===e},getSiblingByRef:function(e){var t=this._owner;return t&&t.refs?t.refs[e]:null}})};t.exports=l},{"./ReactComponentEnvironment":28,"./ReactCurrentOwner":31,"./ReactOwner":57,"./ReactUpdates":68,"./invariant":106,"./keyMirror":112,"./merge":115}],27:[function(e,t){"use strict";var n=e("./ReactDOMIDOperations"),o=e("./ReactMarkupChecksum"),r=e("./ReactMount"),i=e("./ReactPerf"),a=e("./ReactReconcileTransaction"),s=e("./getReactRootElementInContainer"),u=e("./invariant"),c=1,l=9,p={Mixin:{getDOMNode:function(){return u(this.isMounted()),r.getNode(this._rootNodeID)}},ReactReconcileTransaction:a,BackendIDOperations:n,unmountIDFromEnvironment:function(e){r.purgeID(e)},mountImageIntoNode:i.measure("ReactComponentBrowserEnvironment","mountImageIntoNode",function(e,t,n){if(u(t&&(t.nodeType===c||t.nodeType===l)),n){if(o.canReuseMarkup(e,s(t)))return;u(t.nodeType!==l)}u(t.nodeType!==l);var r=t.parentNode;if(r){var i=t.nextSibling;r.removeChild(t),t.innerHTML=e,i?r.insertBefore(t,i):r.appendChild(t)}else t.innerHTML=e})};t.exports=p},{"./ReactDOMIDOperations":36,"./ReactMarkupChecksum":52,"./ReactMount":53,"./ReactPerf":58,"./ReactReconcileTransaction":64,"./getReactRootElementInContainer":102,"./invariant":106}],28:[function(e,t){"use strict";var n=e("./ReactComponentBrowserEnvironment"),o=n;t.exports=o},{"./ReactComponentBrowserEnvironment":27}],29:[function(e,t){"use strict";function n(e,t){for(var n in t)t.hasOwnProperty(n)&&E("function"==typeof t[n]) 19 | }function o(e,t){var n=I[t];N.hasOwnProperty(t)&&E(n===P.OVERRIDE_BASE),e.hasOwnProperty(t)&&E(n===P.DEFINE_MANY||n===P.DEFINE_MANY_MERGED)}function r(e){var t=e._compositeLifeCycleState;E(e.isMounted()||t===T.MOUNTING),E(t!==T.RECEIVING_STATE),E(t!==T.UNMOUNTING)}function i(e,t){E(!l(t)),E(!p.isValidComponent(t));var n=e.componentConstructor,r=n.prototype;for(var i in t){var a=t[i];if(t.hasOwnProperty(i))if(o(r,i),O.hasOwnProperty(i))O[i](e,a);else{var s=i in I,d=i in r,f=a&&a.__reactDontBind,h="function"==typeof a,m=h&&!s&&!d&&!f;m?(r.__reactAutoBindMap||(r.__reactAutoBindMap={}),r.__reactAutoBindMap[i]=a,r[i]=a):r[i]=d?I[i]===P.DEFINE_MANY_MERGED?u(r[i],a):c(r[i],a):a}}}function a(e,t){if(t)for(var n in t){var o=t[n];if(!t.hasOwnProperty(n)||!o)return;var r=n in e,i=o;if(r){var a=e[n],s=typeof a,u=typeof o;E("function"===s&&"function"===u),i=c(a,o)}e[n]=i,e.componentConstructor[n]=i}}function s(e,t){return E(e&&t&&"object"==typeof e&&"object"==typeof t),x(t,function(t,n){E(void 0===e[n]),e[n]=t}),e}function u(e,t){return function(){var n=e.apply(this,arguments),o=t.apply(this,arguments);return null==n?o:null==o?n:s(n,o)}}function c(e,t){return function(){e.apply(this,arguments),t.apply(this,arguments)}}function l(e){return e instanceof Function&&"componentConstructor"in e&&e.componentConstructor instanceof Function}var p=e("./ReactComponent"),d=e("./ReactContext"),f=e("./ReactCurrentOwner"),h=e("./ReactErrorUtils"),m=e("./ReactOwner"),v=e("./ReactPerf"),g=e("./ReactPropTransferer"),y=e("./ReactPropTypeLocations"),C=(e("./ReactPropTypeLocationNames"),e("./ReactUpdates")),E=e("./invariant"),M=e("./keyMirror"),R=e("./merge"),D=e("./mixInto"),x=e("./objMap"),b=e("./shouldUpdateReactComponent"),P=M({DEFINE_ONCE:null,DEFINE_MANY:null,OVERRIDE_BASE:null,DEFINE_MANY_MERGED:null}),I={mixins:P.DEFINE_MANY,statics:P.DEFINE_MANY,propTypes:P.DEFINE_MANY,contextTypes:P.DEFINE_MANY,childContextTypes:P.DEFINE_MANY,getDefaultProps:P.DEFINE_MANY_MERGED,getInitialState:P.DEFINE_MANY_MERGED,getChildContext:P.DEFINE_MANY_MERGED,render:P.DEFINE_ONCE,componentWillMount:P.DEFINE_MANY,componentDidMount:P.DEFINE_MANY,componentWillReceiveProps:P.DEFINE_MANY,shouldComponentUpdate:P.DEFINE_ONCE,componentWillUpdate:P.DEFINE_MANY,componentDidUpdate:P.DEFINE_MANY,componentWillUnmount:P.DEFINE_MANY,updateComponent:P.OVERRIDE_BASE},O={displayName:function(e,t){e.componentConstructor.displayName=t},mixins:function(e,t){if(t)for(var n=0;n",this.tagName=e.toUpperCase()}var i=e("./CSSPropertyOperations"),a=e("./DOMProperty"),s=e("./DOMPropertyOperations"),u=e("./ReactComponent"),c=e("./ReactEventEmitter"),l=e("./ReactMount"),p=e("./ReactMultiChild"),d=e("./ReactPerf"),f=e("./escapeTextForBrowser"),h=e("./invariant"),m=e("./keyOf"),v=e("./merge"),g=e("./mixInto"),y=c.deleteListener,C=c.listenTo,E=c.registrationNameModules,M={string:!0,number:!0},R=m({style:null}),D=1;r.Mixin={mountComponent:d.measure("ReactDOMComponent","mountComponent",function(e,t,o){return u.Mixin.mountComponent.call(this,e,t,o),n(this.props),this._createOpenTagMarkupAndPutListeners(t)+this._createContentMarkup(t)+this._tagClose}),_createOpenTagMarkupAndPutListeners:function(e){var t=this.props,n=this._tagOpen;for(var r in t)if(t.hasOwnProperty(r)){var a=t[r];if(null!=a)if(E[r])o(this._rootNodeID,r,a,e);else{r===R&&(a&&(a=t.style=v(t.style)),a=i.createMarkupForStyles(a));var u=s.createMarkupForProperty(r,a);u&&(n+=" "+u)}}var c=s.createMarkupForID(this._rootNodeID);return n+" "+c+">"},_createContentMarkup:function(e){var t=this.props.dangerouslySetInnerHTML;if(null!=t){if(null!=t.__html)return t.__html}else{var n=M[typeof this.props.children]?this.props.children:null,o=null!=n?null:this.props.children;if(null!=n)return f(n);if(null!=o){var r=this.mountChildren(o,e);return r.join("")}}return""},receiveComponent:function(e,t){n(e.props),u.Mixin.receiveComponent.call(this,e,t)},updateComponent:d.measure("ReactDOMComponent","updateComponent",function(e,t,n){u.Mixin.updateComponent.call(this,e,t,n),this._updateDOMProperties(t,e),this._updateDOMChildren(t,e)}),_updateDOMProperties:function(e,t){var n,r,i,s=this.props;for(n in e)if(!s.hasOwnProperty(n)&&e.hasOwnProperty(n))if(n===R){var c=e[n];for(r in c)c.hasOwnProperty(r)&&(i=i||{},i[r]="")}else E[n]?y(this._rootNodeID,n):(a.isStandardName[n]||a.isCustomAttribute(n))&&u.BackendIDOperations.deletePropertyByID(this._rootNodeID,n);for(n in s){var l=s[n],p=e[n];if(s.hasOwnProperty(n)&&l!==p)if(n===R)if(l&&(l=s.style=v(l)),p){for(r in p)p.hasOwnProperty(r)&&!l.hasOwnProperty(r)&&(i=i||{},i[r]="");for(r in l)l.hasOwnProperty(r)&&p[r]!==l[r]&&(i=i||{},i[r]=l[r])}else i=l;else E[n]?o(this._rootNodeID,n,l,t):(a.isStandardName[n]||a.isCustomAttribute(n))&&u.BackendIDOperations.updatePropertyByID(this._rootNodeID,n,l)}i&&u.BackendIDOperations.updateStylesByID(this._rootNodeID,i)},_updateDOMChildren:function(e,t){var n=this.props,o=M[typeof e.children]?e.children:null,r=M[typeof n.children]?n.children:null,i=e.dangerouslySetInnerHTML&&e.dangerouslySetInnerHTML.__html,a=n.dangerouslySetInnerHTML&&n.dangerouslySetInnerHTML.__html,s=null!=o?null:e.children,c=null!=r?null:n.children,l=null!=o||null!=i,p=null!=r||null!=a;null!=s&&null==c?this.updateChildren(null,t):l&&!p&&this.updateTextContent(""),null!=r?o!==r&&this.updateTextContent(""+r):null!=a?i!==a&&u.BackendIDOperations.updateInnerHTMLByID(this._rootNodeID,a):null!=c&&this.updateChildren(c,t)},unmountComponent:function(){this.unmountChildren(),c.deleteAllListeners(this._rootNodeID),u.Mixin.unmountComponent.call(this)}},g(r,u.Mixin),g(r,r.Mixin),g(r,p.Mixin),t.exports=r},{"./CSSPropertyOperations":3,"./DOMProperty":8,"./DOMPropertyOperations":9,"./ReactComponent":26,"./ReactEventEmitter":46,"./ReactMount":53,"./ReactMultiChild":55,"./ReactPerf":58,"./escapeTextForBrowser":94,"./invariant":106,"./keyOf":113,"./merge":115,"./mixInto":118}],35:[function(e,t){"use strict";var n=e("./ReactCompositeComponent"),o=e("./ReactDOM"),r=e("./ReactEventEmitter"),i=e("./EventConstants"),a=o.form,s=n.createClass({displayName:"ReactDOMForm",render:function(){return this.transferPropsTo(a(null,this.props.children))},componentDidMount:function(){r.trapBubbledEvent(i.topLevelTypes.topReset,"reset",this.getDOMNode()),r.trapBubbledEvent(i.topLevelTypes.topSubmit,"submit",this.getDOMNode())}});t.exports=s},{"./EventConstants":14,"./ReactCompositeComponent":29,"./ReactDOM":32,"./ReactEventEmitter":46}],36:[function(e,t){"use strict";var n,o=e("./CSSPropertyOperations"),r=e("./DOMChildrenOperations"),i=e("./DOMPropertyOperations"),a=e("./ReactMount"),s=e("./ReactPerf"),u=e("./invariant"),c={dangerouslySetInnerHTML:"`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.",style:"`style` must be set using `updateStylesByID()`."},l={updatePropertyByID:s.measure("ReactDOMIDOperations","updatePropertyByID",function(e,t,n){var o=a.getNode(e);u(!c.hasOwnProperty(t)),null!=n?i.setValueForProperty(o,t,n):i.deleteValueForProperty(o,t)}),deletePropertyByID:s.measure("ReactDOMIDOperations","deletePropertyByID",function(e,t,n){var o=a.getNode(e);u(!c.hasOwnProperty(t)),i.deleteValueForProperty(o,t,n)}),updateStylesByID:s.measure("ReactDOMIDOperations","updateStylesByID",function(e,t){var n=a.getNode(e);o.setValueForStyles(n,t)}),updateInnerHTMLByID:s.measure("ReactDOMIDOperations","updateInnerHTMLByID",function(e,t){var o=a.getNode(e);if(void 0===n){var r=document.createElement("div");r.innerHTML=" ",n=""===r.innerHTML}n&&o.parentNode.replaceChild(o,o),n&&t.match(/^[ \r\n\t\f]/)?(o.innerHTML=""+t,o.firstChild.deleteData(0,1)):o.innerHTML=t}),updateTextContentByID:s.measure("ReactDOMIDOperations","updateTextContentByID",function(e,t){var n=a.getNode(e);r.updateTextContent(n,t)}),dangerouslyReplaceNodeWithMarkupByID:s.measure("ReactDOMIDOperations","dangerouslyReplaceNodeWithMarkupByID",function(e,t){var n=a.getNode(e);r.dangerouslyReplaceNodeWithMarkup(n,t)}),dangerouslyProcessChildrenUpdates:s.measure("ReactDOMIDOperations","dangerouslyProcessChildrenUpdates",function(e,t){for(var n=0;nl;l++){var f=c[l];if(f!==i&&f.form===i.form){var h=s.getID(f);u(h);var m=p[h];u(m),m.setState({checked:!1})}}}return t}});t.exports=d},{"./AutoFocusMixin":1,"./DOMPropertyOperations":9,"./LinkedValueUtils":21,"./ReactCompositeComponent":29,"./ReactDOM":32,"./ReactMount":53,"./invariant":106,"./merge":115}],39:[function(e,t){"use strict";var n=e("./ReactCompositeComponent"),o=e("./ReactDOM"),r=o.option,i=n.createClass({displayName:"ReactDOMOption",componentWillMount:function(){null!=this.props.selected},render:function(){return r(this.props,this.props.children)}});t.exports=i},{"./ReactCompositeComponent":29,"./ReactDOM":32}],40:[function(e,t){"use strict";function n(e,t){null!=e[t]&&u(e.multiple?Array.isArray(e[t]):!Array.isArray(e[t]))}function o(e,t){var n,o,r,i=e.props.multiple,a=null!=t?t:e.state.value,s=e.getDOMNode().options;if(i)for(n={},o=0,r=a.length;r>o;++o)n[""+a[o]]=!0;else n=""+a;for(o=0,r=s.length;r>o;o++){var u=i?n.hasOwnProperty(s[o].value):s[o].value===n;u!==s[o].selected&&(s[o].selected=u)}}var r=e("./AutoFocusMixin"),i=e("./LinkedValueUtils"),a=e("./ReactCompositeComponent"),s=e("./ReactDOM"),u=e("./invariant"),c=e("./merge"),l=s.select,p=a.createClass({displayName:"ReactDOMSelect",mixins:[r,i.Mixin],propTypes:{defaultValue:n,value:n},getInitialState:function(){return{value:this.props.defaultValue||(this.props.multiple?[]:"")}},componentWillReceiveProps:function(e){!this.props.multiple&&e.multiple?this.setState({value:[this.state.value]}):this.props.multiple&&!e.multiple&&this.setState({value:this.state.value[0]})},shouldComponentUpdate:function(){return!this._isChanging},render:function(){var e=c(this.props);return e.onChange=this._handleChange,e.value=null,l(e,this.props.children)},componentDidMount:function(){o(this,i.getValue(this))},componentDidUpdate:function(){var e=i.getValue(this);null!=e&&o(this,e)},_handleChange:function(e){var t,n=i.getOnChange(this);n&&(this._isChanging=!0,t=n.call(this,e),this._isChanging=!1);var o;if(this.props.multiple){o=[];for(var r=e.target.options,a=0,s=r.length;s>a;a++)r[a].selected&&o.push(r[a].value)}else o=e.target.value;return this.setState({value:o}),t}});t.exports=p},{"./AutoFocusMixin":1,"./LinkedValueUtils":21,"./ReactCompositeComponent":29,"./ReactDOM":32,"./invariant":106,"./merge":115}],41:[function(e,t){"use strict";function n(e){var t=document.selection,n=t.createRange(),o=n.text.length,r=n.duplicate();r.moveToElementText(e),r.setEndPoint("EndToStart",n);var i=r.text.length,a=i+o;return{start:i,end:a}}function o(e){var t=window.getSelection();if(0===t.rangeCount)return null;var n=t.anchorNode,o=t.anchorOffset,r=t.focusNode,i=t.focusOffset,a=t.getRangeAt(0),s=a.toString().length,u=a.cloneRange();u.selectNodeContents(e),u.setEnd(a.startContainer,a.startOffset);var c=u.toString().length,l=c+s,p=document.createRange();p.setStart(n,o),p.setEnd(r,i);var d=p.collapsed;return p.detach(),{start:d?l:c,end:d?c:l}}function r(e,t){var n,o,r=document.selection.createRange().duplicate();"undefined"==typeof t.end?(n=t.start,o=n):t.start>t.end?(n=t.end,o=t.start):(n=t.start,o=t.end),r.moveToElementText(e),r.moveStart("character",n),r.setEndPoint("EndToStart",r),r.moveEnd("character",o-n),r.select()}function i(e,t){var n=window.getSelection(),o=e[s()].length,r=Math.min(t.start,o),i="undefined"==typeof t.end?r:Math.min(t.end,o);if(!n.extend&&r>i){var u=i;i=r,r=u}var c=a(e,r),l=a(e,i);if(c&&l){var p=document.createRange();p.setStart(c.node,c.offset),n.removeAllRanges(),r>i?(n.addRange(p),n.extend(l.node,l.offset)):(p.setEnd(l.node,l.offset),n.addRange(p)),p.detach()}}var a=e("./getNodeForCharacterOffset"),s=e("./getTextContentAccessor"),u={getOffsets:function(e){var t=document.selection?n:o;return t(e)},setOffsets:function(e,t){var n=document.selection?r:i;n(e,t)}};t.exports=u},{"./getNodeForCharacterOffset":101,"./getTextContentAccessor":103}],42:[function(e,t){"use strict";var n=e("./AutoFocusMixin"),o=e("./DOMPropertyOperations"),r=e("./LinkedValueUtils"),i=e("./ReactCompositeComponent"),a=e("./ReactDOM"),s=e("./invariant"),u=e("./merge"),c=a.textarea,l=i.createClass({displayName:"ReactDOMTextarea",mixins:[n,r.Mixin],getInitialState:function(){var e=this.props.defaultValue,t=this.props.children;null!=t&&(s(null==e),Array.isArray(t)&&(s(t.length<=1),t=t[0]),e=""+t),null==e&&(e="");var n=r.getValue(this);return{initialValue:""+(null!=n?n:e),value:e}},shouldComponentUpdate:function(){return!this._isChanging},render:function(){var e=u(this.props),t=r.getValue(this);return s(null==e.dangerouslySetInnerHTML),e.defaultValue=null,e.value=null!=t?t:this.state.value,e.onChange=this._handleChange,c(e,this.state.initialValue)},componentDidUpdate:function(){var e=r.getValue(this);if(null!=e){var t=this.getDOMNode();o.setValueForProperty(t,"value",""+e)}},_handleChange:function(e){var t,n=r.getOnChange(this);return n&&(this._isChanging=!0,t=n.call(this,e),this._isChanging=!1),this.setState({value:e.target.value}),t}});t.exports=l},{"./AutoFocusMixin":1,"./DOMPropertyOperations":9,"./LinkedValueUtils":21,"./ReactCompositeComponent":29,"./ReactDOM":32,"./invariant":106,"./merge":115}],43:[function(e,t){"use strict";function n(){this.reinitializeTransaction()}var o=e("./ReactUpdates"),r=e("./Transaction"),i=e("./emptyFunction"),a=e("./mixInto"),s={initialize:i,close:function(){p.isBatchingUpdates=!1}},u={initialize:i,close:o.flushBatchedUpdates.bind(o)},c=[u,s];a(n,r.Mixin),a(n,{getTransactionWrappers:function(){return c}});var l=new n,p={isBatchingUpdates:!1,batchedUpdates:function(e,t){var n=p.isBatchingUpdates;p.isBatchingUpdates=!0,n?e(t):l.perform(e,null,t)}};t.exports=p},{"./ReactUpdates":68,"./Transaction":82,"./emptyFunction":93,"./mixInto":118}],44:[function(e,t){"use strict";function n(){o.EventEmitter.injectTopLevelCallbackCreator(d),o.EventPluginHub.injectEventPluginOrder(c),o.EventPluginHub.injectInstanceHandle(M),o.EventPluginHub.injectMount(R),o.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:b,EnterLeaveEventPlugin:l,ChangeEventPlugin:a,CompositionEventPlugin:u,MobileSafariClickEventPlugin:p,SelectEventPlugin:D}),o.DOM.injectComponentClasses({button:h,form:m,img:v,input:g,option:y,select:C,textarea:E,html:I(f.html),head:I(f.head),title:I(f.title),body:I(f.body)}),o.DOMProperty.injectDOMPropertyConfig(i),o.Updates.injectBatchingStrategy(P),o.RootIndex.injectCreateReactRootIndex(r.canUseDOM?s.createReactRootIndex:x.createReactRootIndex)}var o=e("./ReactInjection"),r=e("./ExecutionEnvironment"),i=e("./DefaultDOMPropertyConfig"),a=e("./ChangeEventPlugin"),s=e("./ClientReactRootIndex"),u=e("./CompositionEventPlugin"),c=e("./DefaultEventPluginOrder"),l=e("./EnterLeaveEventPlugin"),p=e("./MobileSafariClickEventPlugin"),d=e("./ReactEventTopLevelCallback"),f=e("./ReactDOM"),h=e("./ReactDOMButton"),m=e("./ReactDOMForm"),v=e("./ReactDOMImg"),g=e("./ReactDOMInput"),y=e("./ReactDOMOption"),C=e("./ReactDOMSelect"),E=e("./ReactDOMTextarea"),M=e("./ReactInstanceHandles"),R=e("./ReactMount"),D=e("./SelectEventPlugin"),x=e("./ServerReactRootIndex"),b=e("./SimpleEventPlugin"),P=e("./ReactDefaultBatchingStrategy"),I=e("./createFullPageComponent");t.exports={inject:n}},{"./ChangeEventPlugin":4,"./ClientReactRootIndex":5,"./CompositionEventPlugin":6,"./DefaultDOMPropertyConfig":11,"./DefaultEventPluginOrder":12,"./EnterLeaveEventPlugin":13,"./ExecutionEnvironment":20,"./MobileSafariClickEventPlugin":22,"./ReactDOM":32,"./ReactDOMButton":33,"./ReactDOMForm":35,"./ReactDOMImg":37,"./ReactDOMInput":38,"./ReactDOMOption":39,"./ReactDOMSelect":40,"./ReactDOMTextarea":42,"./ReactDefaultBatchingStrategy":43,"./ReactEventTopLevelCallback":48,"./ReactInjection":49,"./ReactInstanceHandles":51,"./ReactMount":53,"./SelectEventPlugin":69,"./ServerReactRootIndex":70,"./SimpleEventPlugin":71,"./createFullPageComponent":89}],45:[function(e,t){"use strict";var n={guard:function(e){return e}};t.exports=n},{}],46:[function(e,t){"use strict";function n(e){return null==e[C]&&(e[C]=g++,m[e[C]]={}),m[e[C]]}function o(e,t,n){a.listen(n,t,E.TopLevelCallbackCreator.createTopLevelCallback(e))}function r(e,t,n){a.capture(n,t,E.TopLevelCallbackCreator.createTopLevelCallback(e))}var i=e("./EventConstants"),a=e("./EventListener"),s=e("./EventPluginHub"),u=e("./EventPluginRegistry"),c=e("./ExecutionEnvironment"),l=e("./ReactEventEmitterMixin"),p=e("./ViewportMetrics"),d=e("./invariant"),f=e("./isEventSupported"),h=e("./merge"),m={},v=!1,g=0,y={topBlur:"blur",topChange:"change",topClick:"click",topCompositionEnd:"compositionend",topCompositionStart:"compositionstart",topCompositionUpdate:"compositionupdate",topContextMenu:"contextmenu",topCopy:"copy",topCut:"cut",topDoubleClick:"dblclick",topDrag:"drag",topDragEnd:"dragend",topDragEnter:"dragenter",topDragExit:"dragexit",topDragLeave:"dragleave",topDragOver:"dragover",topDragStart:"dragstart",topDrop:"drop",topFocus:"focus",topInput:"input",topKeyDown:"keydown",topKeyPress:"keypress",topKeyUp:"keyup",topMouseDown:"mousedown",topMouseMove:"mousemove",topMouseOut:"mouseout",topMouseOver:"mouseover",topMouseUp:"mouseup",topPaste:"paste",topScroll:"scroll",topSelectionChange:"selectionchange",topTouchCancel:"touchcancel",topTouchEnd:"touchend",topTouchMove:"touchmove",topTouchStart:"touchstart",topWheel:"wheel"},C="_reactListenersID"+String(Math.random()).slice(2),E=h(l,{TopLevelCallbackCreator:null,injection:{injectTopLevelCallbackCreator:function(e){E.TopLevelCallbackCreator=e}},setEnabled:function(e){d(c.canUseDOM),E.TopLevelCallbackCreator&&E.TopLevelCallbackCreator.setEnabled(e)},isEnabled:function(){return!(!E.TopLevelCallbackCreator||!E.TopLevelCallbackCreator.isEnabled())},listenTo:function(e,t){for(var a=t,s=n(a),c=u.registrationNameDependencies[e],l=i.topLevelTypes,p=0,d=c.length;d>p;p++){var h=c[p];if(!s[h]){var m=l[h];m===l.topWheel?f("wheel")?o(l.topWheel,"wheel",a):f("mousewheel")?o(l.topWheel,"mousewheel",a):o(l.topWheel,"DOMMouseScroll",a):m===l.topScroll?f("scroll",!0)?r(l.topScroll,"scroll",a):o(l.topScroll,"scroll",window):m===l.topFocus||m===l.topBlur?(f("focus",!0)?(r(l.topFocus,"focus",a),r(l.topBlur,"blur",a)):f("focusin")&&(o(l.topFocus,"focusin",a),o(l.topBlur,"focusout",a)),s[l.topBlur]=!0,s[l.topFocus]=!0):y[h]&&o(m,y[h],a),s[h]=!0}}},ensureScrollValueMonitoring:function(){if(!v){var e=p.refreshScrollValues;a.listen(window,"scroll",e),a.listen(window,"resize",e),v=!0}},eventNameDispatchConfigs:s.eventNameDispatchConfigs,registrationNameModules:s.registrationNameModules,putListener:s.putListener,getListener:s.getListener,deleteListener:s.deleteListener,deleteAllListeners:s.deleteAllListeners,trapBubbledEvent:o,trapCapturedEvent:r});t.exports=E},{"./EventConstants":14,"./EventListener":15,"./EventPluginHub":16,"./EventPluginRegistry":17,"./ExecutionEnvironment":20,"./ReactEventEmitterMixin":47,"./ViewportMetrics":83,"./invariant":106,"./isEventSupported":107,"./merge":115}],47:[function(e,t){"use strict";function n(e){o.enqueueEvents(e),o.processEventQueue()}var o=e("./EventPluginHub"),r=e("./ReactUpdates"),i={handleTopLevel:function(e,t,i,a){var s=o.extractEvents(e,t,i,a);r.batchedUpdates(n,s)}};t.exports=i},{"./EventPluginHub":16,"./ReactUpdates":68}],48:[function(e,t){"use strict";function n(e){var t=u.getID(e),n=s.getReactRootIDFromNodeID(t),o=u.findReactContainerForID(n),r=u.getFirstReactDOM(o);return r}function o(e,t,o){for(var r=u.getFirstReactDOM(c(t))||window,i=r;i;)o.ancestors.push(i),i=n(i);for(var s=0,l=o.ancestors.length;l>s;s++){r=o.ancestors[s];var p=u.getID(r)||"";a.handleTopLevel(e,r,p,t)}}function r(){this.ancestors=[]}var i=e("./PooledClass"),a=e("./ReactEventEmitter"),s=e("./ReactInstanceHandles"),u=e("./ReactMount"),c=e("./getEventTarget"),l=e("./mixInto"),p=!0;l(r,{destructor:function(){this.ancestors.length=0}}),i.addPoolingTo(r);var d={setEnabled:function(e){p=!!e},isEnabled:function(){return p},createTopLevelCallback:function(e){return function(t){if(p){var n=r.getPooled();try{o(e,t,n)}finally{r.release(n)}}}}};t.exports=d},{"./PooledClass":23,"./ReactEventEmitter":46,"./ReactInstanceHandles":51,"./ReactMount":53,"./getEventTarget":99,"./mixInto":118}],49:[function(e,t){"use strict";var n=e("./DOMProperty"),o=e("./EventPluginHub"),r=e("./ReactDOM"),i=e("./ReactEventEmitter"),a=e("./ReactPerf"),s=e("./ReactRootIndex"),u=e("./ReactUpdates"),c={DOMProperty:n.injection,EventPluginHub:o.injection,DOM:r.injection,EventEmitter:i.injection,Perf:a.injection,RootIndex:s.injection,Updates:u.injection};t.exports=c 20 | },{"./DOMProperty":8,"./EventPluginHub":16,"./ReactDOM":32,"./ReactEventEmitter":46,"./ReactPerf":58,"./ReactRootIndex":65,"./ReactUpdates":68}],50:[function(e,t){"use strict";function n(e){return r(document.documentElement,e)}var o=e("./ReactDOMSelection"),r=e("./containsNode"),i=e("./getActiveElement"),a={hasSelectionCapabilities:function(e){return e&&("INPUT"===e.nodeName&&"text"===e.type||"TEXTAREA"===e.nodeName||"true"===e.contentEditable)},getSelectionInformation:function(){var e=i();return{focusedElem:e,selectionRange:a.hasSelectionCapabilities(e)?a.getSelection(e):null}},restoreSelection:function(e){var t=i(),o=e.focusedElem,r=e.selectionRange;t!==o&&n(o)&&(a.hasSelectionCapabilities(o)&&a.setSelection(o,r),o.focus())},getSelection:function(e){var t;if("selectionStart"in e)t={start:e.selectionStart,end:e.selectionEnd};else if(document.selection&&"INPUT"===e.nodeName){var n=document.selection.createRange();n.parentElement()===e&&(t={start:-n.moveStart("character",-e.value.length),end:-n.moveEnd("character",-e.value.length)})}else t=o.getOffsets(e);return t||{start:0,end:0}},setSelection:function(e,t){var n=t.start,r=t.end;if("undefined"==typeof r&&(r=n),"selectionStart"in e)e.selectionStart=n,e.selectionEnd=Math.min(r,e.value.length);else if(document.selection&&"INPUT"===e.nodeName){var i=e.createTextRange();i.collapse(!0),i.moveStart("character",n),i.moveEnd("character",r-n),i.select()}else o.setOffsets(e,t)}};t.exports=a},{"./ReactDOMSelection":41,"./containsNode":86,"./getActiveElement":97}],51:[function(e,t){"use strict";function n(e){return d+e.toString(36)}function o(e,t){return e.charAt(t)===d||t===e.length}function r(e){return""===e||e.charAt(0)===d&&e.charAt(e.length-1)!==d}function i(e,t){return 0===t.indexOf(e)&&o(t,e.length)}function a(e){return e?e.substr(0,e.lastIndexOf(d)):""}function s(e,t){if(p(r(e)&&r(t)),p(i(e,t)),e===t)return e;for(var n=e.length+f,a=n;a=a;a++)if(o(e,a)&&o(t,a))i=a;else if(e.charAt(a)!==t.charAt(a))break;var s=e.substr(0,i);return p(r(s)),s}function c(e,t,n,o,r,u){e=e||"",t=t||"",p(e!==t);var c=i(t,e);p(c||i(e,t));for(var l=0,d=c?a:s,f=e;;f=d(f,t)){var m;if(r&&f===e||u&&f===t||(m=n(f,c,o)),m===!1||f===t)break;p(l++1){var t=e.indexOf(d,1);return t>-1?e.substr(0,t):e}return null},traverseEnterLeave:function(e,t,n,o,r){var i=u(e,t);i!==e&&c(e,i,n,o,!1,!0),i!==t&&c(i,t,n,r,!0,!1)},traverseTwoPhase:function(e,t,n){e&&(c("",e,t,n,!0,!1),c(e,"",t,n,!1,!0))},traverseAncestors:function(e,t,n){c("",e,t,n,!0,!1)},_getFirstCommonAncestorID:u,_getNextDescendantID:s,isAncestorIDOf:i,SEPARATOR:d};t.exports=m},{"./ReactRootIndex":65,"./invariant":106}],52:[function(e,t){"use strict";var n=e("./adler32"),o={CHECKSUM_ATTR_NAME:"data-react-checksum",addChecksumToMarkup:function(e){var t=n(e);return e.replace(">"," "+o.CHECKSUM_ATTR_NAME+'="'+t+'">')},canReuseMarkup:function(e,t){var r=t.getAttribute(o.CHECKSUM_ATTR_NAME);r=r&&parseInt(r,10);var i=n(e);return i===r}};t.exports=o},{"./adler32":85}],53:[function(e,t){"use strict";function n(e){var t=v(e);return t&&O.getID(t)}function o(e){var t=r(e);if(t)if(M.hasOwnProperty(t)){var n=M[t];n!==e&&(g(!s(n,t)),M[t]=e)}else M[t]=e;return t}function r(e){return e&&e.getAttribute&&e.getAttribute(E)||""}function i(e,t){var n=r(e);n!==t&&delete M[n],e.setAttribute(E,t),M[t]=e}function a(e){return M.hasOwnProperty(e)&&s(M[e],e)||(M[e]=O.findReactNodeByID(e)),M[e]}function s(e,t){if(e){g(r(e)===t);var n=O.findReactContainerForID(t);if(n&&m(n,e))return!0}return!1}function u(e){delete M[e]}function c(e){var t=M[e];return t&&s(t,e)?void(I=t):!1}function l(e){I=null,f.traverseAncestors(e,c);var t=I;return I=null,t}var p=e("./DOMProperty"),d=e("./ReactEventEmitter"),f=e("./ReactInstanceHandles"),h=e("./ReactPerf"),m=e("./containsNode"),v=e("./getReactRootElementInContainer"),g=e("./invariant"),y=e("./shouldUpdateReactComponent"),C=f.SEPARATOR,E=p.ID_ATTRIBUTE_NAME,M={},R=1,D=9,x={},b={},P=[],I=null,O={totalInstantiationTime:0,totalInjectionTime:0,useTouchEvents:!1,_instancesByReactRootID:x,scrollMonitor:function(e,t){t()},_updateRootComponent:function(e,t,n,o){var r=t.props;return O.scrollMonitor(n,function(){e.replaceProps(r,o)}),e},_registerComponent:function(e,t){g(t&&(t.nodeType===R||t.nodeType===D)),d.ensureScrollValueMonitoring();var n=O.registerContainer(t);return x[n]=e,n},_renderNewRootComponent:h.measure("ReactMount","_renderNewRootComponent",function(e,t,n){var o=O._registerComponent(e,t);return e.mountComponentIntoNode(o,t,n),e}),renderComponent:function(e,t,o){var r=x[n(t)];if(r){if(y(r,e))return O._updateRootComponent(r,e,t,o);O.unmountComponentAtNode(t)}var i=v(t),a=i&&O.isRenderedByReact(i),s=a&&!r,u=O._renderNewRootComponent(e,t,s);return o&&o.call(u),u},constructAndRenderComponent:function(e,t,n){return O.renderComponent(e(t),n)},constructAndRenderComponentByID:function(e,t,n){var o=document.getElementById(n);return g(o),O.constructAndRenderComponent(e,t,o)},registerContainer:function(e){var t=n(e);return t&&(t=f.getReactRootIDFromNodeID(t)),t||(t=f.createReactRootID()),b[t]=e,t},unmountComponentAtNode:function(e){var t=n(e),o=x[t];return o?(O.unmountComponentFromNode(o,e),delete x[t],delete b[t],!0):!1},unmountComponentFromNode:function(e,t){for(e.unmountComponent(),t.nodeType===D&&(t=t.documentElement);t.lastChild;)t.removeChild(t.lastChild)},findReactContainerForID:function(e){var t=f.getReactRootIDFromNodeID(e),n=b[t];return n},findReactNodeByID:function(e){var t=O.findReactContainerForID(e);return O.findComponentRoot(t,e)},isRenderedByReact:function(e){if(1!==e.nodeType)return!1;var t=O.getID(e);return t?t.charAt(0)===C:!1},getFirstReactDOM:function(e){for(var t=e;t&&t.parentNode!==t;){if(O.isRenderedByReact(t))return t;t=t.parentNode}return null},findComponentRoot:function(e,t){var n=P,o=0,r=l(t)||e;for(n[0]=r.firstChild,n.length=1;ot;t++){var o=e[t].component,r=e[t].callback;r.call(o)}e.length=0}},reset:function(){this._queue=null},destructor:function(){this.reset()}}),o.addPoolingTo(n),t.exports=n},{"./PooledClass":23,"./mixInto":118}],55:[function(e,t){"use strict";function n(e,t,n){f.push({parentID:e,parentNode:null,type:c.INSERT_MARKUP,markupIndex:h.push(t)-1,textContent:null,fromIndex:null,toIndex:n})}function o(e,t,n){f.push({parentID:e,parentNode:null,type:c.MOVE_EXISTING,markupIndex:null,textContent:null,fromIndex:t,toIndex:n})}function r(e,t){f.push({parentID:e,parentNode:null,type:c.REMOVE_NODE,markupIndex:null,textContent:null,fromIndex:t,toIndex:null})}function i(e,t){f.push({parentID:e,parentNode:null,type:c.TEXT_CONTENT,markupIndex:null,textContent:t,fromIndex:null,toIndex:null})}function a(){f.length&&(u.BackendIDOperations.dangerouslyProcessChildrenUpdates(f,h),s())}function s(){f.length=0,h.length=0}var u=e("./ReactComponent"),c=e("./ReactMultiChildUpdateTypes"),l=e("./flattenChildren"),p=e("./shouldUpdateReactComponent"),d=0,f=[],h=[],m={Mixin:{mountChildren:function(e,t){var n=l(e),o=[],r=0;this._renderedChildren=n;for(var i in n){var a=n[i];if(n.hasOwnProperty(i)){var s=this._rootNodeID+i,u=a.mountComponent(s,t,this._mountDepth+1);a._mountIndex=r,o.push(u),r++}}return o},updateTextContent:function(e){d++;var t=!0;try{var n=this._renderedChildren;for(var o in n)n.hasOwnProperty(o)&&this._unmountChildByName(n[o],o);this.setTextContent(e),t=!1}finally{d--,d||(t?s():a())}},updateChildren:function(e,t){d++;var n=!0;try{this._updateChildren(e,t),n=!1}finally{d--,d||(n?s():a())}},_updateChildren:function(e,t){var n=l(e),o=this._renderedChildren;if(n||o){var r,i=0,a=0;for(r in n)if(n.hasOwnProperty(r)){var s=o&&o[r],u=n[r];p(s,u)?(this.moveChild(s,a,i),i=Math.max(s._mountIndex,i),s.receiveComponent(u,t),s._mountIndex=a):(s&&(i=Math.max(s._mountIndex,i),this._unmountChildByName(s,r)),this._mountChildByNameAtIndex(u,r,a,t)),a++}for(r in o)!o.hasOwnProperty(r)||n&&n[r]||this._unmountChildByName(o[r],r)}},unmountChildren:function(){var e=this._renderedChildren;for(var t in e){var n=e[t];n.unmountComponent&&n.unmountComponent()}this._renderedChildren=null},moveChild:function(e,t,n){e._mountIndex>";t.exports=v},{"./ReactComponent":26,"./ReactPropTypeLocationNames":60,"./createObjectFrom":91,"./warning":126}],63:[function(e,t){"use strict";function n(){this.listenersToPut=[]}var o=e("./PooledClass"),r=e("./ReactEventEmitter"),i=e("./mixInto");i(n,{enqueuePutListener:function(e,t,n){this.listenersToPut.push({rootNodeID:e,propKey:t,propValue:n})},putListeners:function(){for(var e=0;e"+r(this.props.text)+""},receiveComponent:function(e){var t=e.props;t.text!==this.props.text&&(this.props.text=t.text,o.BackendIDOperations.updateTextContentByID(this._rootNodeID,t.text))}}),a.type=a,a.prototype.type=a,t.exports=a},{"./DOMPropertyOperations":9,"./ReactComponent":26,"./escapeTextForBrowser":94,"./mixInto":118}],68:[function(e,t){"use strict";function n(){c(p)}function o(e,t){n(),p.batchedUpdates(e,t)}function r(e,t){return e._mountDepth-t._mountDepth}function i(){l.sort(r);for(var e=0;e":">","<":"<",'"':""","'":"'","/":"/"},i=/[&><"'\/]/g;t.exports=o},{}],95:[function(e,t){"use strict";function n(e,t,n){var o=e;r(!o.hasOwnProperty(n)),null!=t&&(o[n]=t)}function o(e){if(null==e)return e;var t={};return i(e,n,t),t}var r=e("./invariant"),i=e("./traverseAllChildren");t.exports=o},{"./invariant":106,"./traverseAllChildren":125}],96:[function(e,t){"use strict";var n=function(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)};t.exports=n},{}],97:[function(e,t){function n(){try{return document.activeElement||document.body}catch(e){return document.body}}t.exports=n},{}],98:[function(e,t){"use strict";function n(e){return"key"in e?o[e.key]||e.key:r[e.which||e.keyCode]||"Unidentified"}var o={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},r={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"};t.exports=n},{}],99:[function(e,t){"use strict";function n(e){var t=e.target||e.srcElement||window;return 3===t.nodeType?t.parentNode:t}t.exports=n},{}],100:[function(e,t){function n(e){return r(!!i),p.hasOwnProperty(e)||(e="*"),a.hasOwnProperty(e)||(i.innerHTML="*"===e?"":"<"+e+">",a[e]=!i.firstChild),a[e]?p[e]:null}var o=e("./ExecutionEnvironment"),r=e("./invariant"),i=o.canUseDOM?document.createElement("div"):null,a={circle:!0,defs:!0,g:!0,line:!0,linearGradient:!0,path:!0,polygon:!0,polyline:!0,radialGradient:!0,rect:!0,stop:!0,text:!0},s=[1,'"],u=[1,"","
"],c=[3,"","
"],l=[1,"",""],p={"*":[1,"?
","
"],area:[1,"",""],col:[2,"","
"],legend:[1,"
","
"],param:[1,"",""],tr:[2,"","
"],optgroup:s,option:s,caption:u,colgroup:u,tbody:u,tfoot:u,thead:u,td:c,th:c,circle:l,defs:l,g:l,line:l,linearGradient:l,path:l,polygon:l,polyline:l,radialGradient:l,rect:l,stop:l,text:l};t.exports=n},{"./ExecutionEnvironment":20,"./invariant":106}],101:[function(e,t){"use strict";function n(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function o(e){for(;e;){if(e.nextSibling)return e.nextSibling;e=e.parentNode}}function r(e,t){for(var r=n(e),i=0,a=0;r;){if(3==r.nodeType){if(a=i+r.textContent.length,t>=i&&a>=t)return{node:r,offset:t-i};i=a}r=n(o(r))}}t.exports=r},{}],102:[function(e,t){"use strict";function n(e){return e?e.nodeType===o?e.documentElement:e.firstChild:null}var o=9;t.exports=n},{}],103:[function(e,t){"use strict";function n(){return!r&&o.canUseDOM&&(r="textContent"in document.createElement("div")?"textContent":"innerText"),r}var o=e("./ExecutionEnvironment"),r=null;t.exports=n},{"./ExecutionEnvironment":20}],104:[function(e,t){"use strict";function n(e){return e===window?{x:window.pageXOffset||document.documentElement.scrollLeft,y:window.pageYOffset||document.documentElement.scrollTop}:{x:e.scrollLeft,y:e.scrollTop}}t.exports=n},{}],105:[function(e,t){function n(e){return e.replace(o,"-$1").toLowerCase()}var o=/([A-Z])/g;t.exports=n},{}],106:[function(e,t){"use strict";var n=function(e){if(!e){var t=new Error("Minified exception occured; use the non-minified dev environment for the full error message and additional helpful warnings.");throw t.framesToPop=1,t}};t.exports=n},{}],107:[function(e,t){"use strict";function n(e,t){if(!r.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,i=n in document;if(!i){var a=document.createElement("div");a.setAttribute(n,"return;"),i="function"==typeof a[n]}return!i&&o&&"wheel"===e&&(i=document.implementation.hasFeature("Events.wheel","3.0")),i}var o,r=e("./ExecutionEnvironment");r.canUseDOM&&(o=document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("","")!==!0),t.exports=n},{"./ExecutionEnvironment":20}],108:[function(e,t){function n(e){return!(!e||!("undefined"!=typeof Node?e instanceof Node:"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName))}t.exports=n},{}],109:[function(e,t){"use strict";function n(e){return e&&("INPUT"===e.nodeName&&o[e.type]||"TEXTAREA"===e.nodeName)}var o={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};t.exports=n},{}],110:[function(e,t){function n(e){return o(e)&&3==e.nodeType}var o=e("./isNode");t.exports=n},{"./isNode":108}],111:[function(e,t){"use strict";function n(e){e||(e="");var t,n=arguments.length;if(n>1)for(var o=1;n>o;o++)t=arguments[o],t&&(e+=" "+t);return e}t.exports=n},{}],112:[function(e,t){"use strict";var n=e("./invariant"),o=function(e){var t,o={};n(e instanceof Object&&!Array.isArray(e));for(t in e)e.hasOwnProperty(t)&&(o[t]=t);return o};t.exports=o},{"./invariant":106}],113:[function(e,t){var n=function(e){var t;for(t in e)if(e.hasOwnProperty(t))return t;return null};t.exports=n},{}],114:[function(e,t){"use strict";function n(e){var t={};return function(n){return t.hasOwnProperty(n)?t[n]:t[n]=e.call(this,n)}}t.exports=n},{}],115:[function(e,t){"use strict";var n=e("./mergeInto"),o=function(e,t){var o={};return n(o,e),n(o,t),o};t.exports=o},{"./mergeInto":117}],116:[function(e,t){"use strict";var n=e("./invariant"),o=e("./keyMirror"),r=36,i=function(e){return"object"!=typeof e||null===e},a={MAX_MERGE_DEPTH:r,isTerminal:i,normalizeMergeArg:function(e){return void 0===e||null===e?{}:e},checkMergeArrayArgs:function(e,t){n(Array.isArray(e)&&Array.isArray(t))},checkMergeObjectArgs:function(e,t){a.checkMergeObjectArg(e),a.checkMergeObjectArg(t)},checkMergeObjectArg:function(e){n(!i(e)&&!Array.isArray(e))},checkMergeLevel:function(e){n(r>e)},checkArrayStrategy:function(e){n(void 0===e||e in a.ArrayStrategies)},ArrayStrategies:o({Clobber:!0,IndexByIndex:!0})};t.exports=a},{"./invariant":106,"./keyMirror":112}],117:[function(e,t){"use strict";function n(e,t){if(r(e),null!=t){r(t);for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])}}var o=e("./mergeHelpers"),r=o.checkMergeObjectArg;t.exports=n},{"./mergeHelpers":116}],118:[function(e,t){"use strict";var n=function(e,t){var n;for(n in t)t.hasOwnProperty(n)&&(e.prototype[n]=t[n])};t.exports=n},{}],119:[function(e,t){"use strict";function n(e,t,n){if(!e)return null;var o=0,r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.call(n,e[i],i,o++));return r}t.exports=n},{}],120:[function(e,t){"use strict";function n(e,t,n){if(!e)return null;var o=0,r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.call(n,i,e[i],o++));return r}t.exports=n},{}],121:[function(e,t){"use strict";function n(e){return r(o.isValidComponent(e)),e}var o=e("./ReactComponent"),r=e("./invariant");t.exports=n},{"./ReactComponent":26,"./invariant":106}],122:[function(e,t){"use strict";function n(e,t){if(e===t)return!0;var n;for(n in e)if(e.hasOwnProperty(n)&&(!t.hasOwnProperty(n)||e[n]!==t[n]))return!1;for(n in t)if(t.hasOwnProperty(n)&&!e.hasOwnProperty(n))return!1;return!0}t.exports=n},{}],123:[function(e,t){"use strict";function n(e,t){return e&&t&&e.constructor===t.constructor&&(e.props&&e.props.key)===(t.props&&t.props.key)&&e._owner===t._owner?!0:!1}t.exports=n},{}],124:[function(e,t){function n(e){var t=e.length;if(o(!Array.isArray(e)&&("object"==typeof e||"function"==typeof e)),o("number"==typeof t),o(0===t||t-1 in e),e.hasOwnProperty)try{return Array.prototype.slice.call(e)}catch(n){}for(var r=Array(t),i=0;t>i;i++)r[i]=e[i];return r}var o=e("./invariant");t.exports=n},{"./invariant":106}],125:[function(e,t){"use strict";function n(e){return d[e]}function o(e,t){return e&&e.props&&null!=e.props.key?i(e.props.key):t.toString(36)}function r(e){return(""+e).replace(f,n)}function i(e){return"$"+r(e)}function a(e,t,n){null!==e&&void 0!==e&&h(e,"",0,t,n)}var s=e("./ReactInstanceHandles"),u=e("./ReactTextComponent"),c=e("./invariant"),l=s.SEPARATOR,p=":",d={"=":"=0",".":"=1",":":"=2"},f=/[=.:]/g,h=function(e,t,n,r,a){var s=0;if(Array.isArray(e))for(var d=0;d