├── .gitignore ├── .idea └── copyright │ └── xored_apache.xml ├── .travis.yml ├── LICENSE ├── README.md ├── project ├── ScalaJSReact.scala ├── build.properties └── plugins.sbt ├── scalajs-react-examples ├── children.html ├── css │ ├── base.css │ └── bg.png ├── export.html ├── hello.html ├── index.html ├── say.html ├── src │ └── main │ │ └── scala │ │ └── com │ │ └── xored │ │ └── scalajs │ │ └── react │ │ └── examples │ │ ├── App.scala │ │ ├── children │ │ └── Children.scala │ │ ├── export │ │ └── Export.scala │ │ ├── hello │ │ └── HelloMessage.scala │ │ ├── say │ │ └── Say.scala │ │ ├── timer │ │ └── Timer.scala │ │ └── todomvc │ │ ├── Todo.scala │ │ ├── TodoApp.scala │ │ └── TodoItem.scala ├── timer.html └── todo.html ├── scalajs-react-tests └── src │ └── test │ └── scala │ └── com │ └── xored │ └── scalajs │ └── react │ ├── ReactRenderSuite.scala │ └── comp │ ├── Hello.scala │ ├── HelloWorld.scala │ └── Panel.scala └── scalajs-react ├── build.sbt └── src └── main └── scala └── com └── xored └── scalajs └── react ├── React.scala ├── ReactDOM.scala ├── ReactSpec.scala ├── ThisLike.scala ├── TypedReactSpec.scala ├── event ├── SyntheticEvent.scala └── TypedSyntheticEvent.scala ├── export.scala ├── internal ├── ReactDOMBuffer.scala ├── ReactMetaData.scala └── ScalaxImpl.scala ├── interop ├── ReactComponent.scala └── ReactJS.scala ├── scalax.scala └── util ├── ClassName.scala ├── TypedEventListeners.scala ├── UUID.scala └── package.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | project/project 4 | project/target 5 | target 6 | 7 | *.swp 8 | *.swo 9 | *.jar 10 | 11 | # IDE 12 | 13 | /.classpath 14 | /.project 15 | /.settings 16 | 17 | /*.iml 18 | /.idea/workspace.xml 19 | /.idea/sbt.xml 20 | /.idea/libraries/ 21 | /.idea/modules.xml 22 | /.idea/vcs.xml 23 | /.idea/modules/ 24 | /.idea/dictionaries/ 25 | /.idea/copyright/profiles_settings.xml 26 | /.idea/.name 27 | /.idea/compiler.xml 28 | /.idea/copyright/profiles_settings.xml 29 | /.idea/encodings.xml 30 | /.idea/highlighting.xml 31 | /.idea/misc.xml 32 | /.idea/scopes/scope_settings.xml 33 | /.idea/uiDesigner.xml 34 | -------------------------------------------------------------------------------- /.idea/copyright/xored_apache.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.11.5 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | scala-js-react 2 | ============ 3 | [![Build Status](https://travis-ci.org/xored/scala-js-react.svg?branch=master)](https://travis-ci.org/xored/scala-js-react) 4 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xored/scala-js-react?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | Scala-js-react provides nice statically typed interface to [Facebook's React](http://facebook.github.io/react/). 7 | 8 | # Quickstart 9 | 10 | For detailed sbt configuration please refer to [scala.js documentation](http://www.scala-js.org/doc/). 11 | 12 | `build.sbt` 13 | ```scala 14 | enablePlugins(ScalaJSPlugin) 15 | 16 | scalaVersion := "2.11.5" 17 | 18 | libraryDependencies ++= Seq( 19 | "com.xored.scalajs" %%% "scalajs-react" % "0.3.3", 20 | compilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full) 21 | ) 22 | ``` 23 | 24 | `project/plugins.sbt` 25 | ```scala 26 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.0") 27 | ``` 28 | 29 | `project/build.properties` 30 | ``` 31 | sbt.version=0.13.7 32 | ``` 33 | 34 | # Features 35 | 36 | `scala-js-react` aims to support all [Facebook React](http://facebook.github.io/react/) features. 37 | 38 | ## `scalax` annotation 39 | 40 | While [Facebook React](http://facebook.github.io/react/) uses JSX, `scala-js-react` offers `scalax` support. 41 | Basically `scalax` allows to use Scala XML literals to build React DOM. 42 | 43 | React DOM allows to put objects into attribute values, while `scala.xml.UnprefixedAttribute` constructor takes `Seq[Node]` or `String`. 44 | So we have to transform methods before it typechecks. That is why `scalax` `scalax` uses macro annotations and requires macro paradise compiler plugin. 45 | 46 | ```scala 47 | object HelloMessage extends TypedReactSpec { 48 | 49 | case class State() 50 | case class Props(name: String) 51 | 52 | def getInitialState(self: This) = State() 53 | 54 | @scalax 55 | def render(self: This) = { 56 |
Hello {self.props.name}
57 | } 58 | } 59 | 60 | React.renderComponent( 61 | HelloMessage(HelloMessage.Props(name = "John")), 62 | mountNode 63 | ) 64 | ``` 65 | 66 | ## Internal State 67 | 68 | A component can maintain internal state. 69 | 70 | ```scala 71 | object Timer extends TypedReactSpec with TypedEventListeners { 72 | 73 | case class Props() 74 | case class State(secondsElapsed: Int, interval: Option[Int]) 75 | 76 | def getInitialState(self: This) = State(secondsElapsed = 0, interval = None) 77 | 78 | implicit class Closure(self: This) { 79 | import self._ 80 | 81 | val tick = () => { 82 | setState(state.copy(secondsElapsed = state.secondsElapsed + 1)) 83 | } 84 | } 85 | 86 | override def componentDidMount(self: This) = { 87 | val interval = window.setInterval(self.tick, 1000) 88 | 89 | self.setState(self.state.copy(interval = Some(interval))) 90 | } 91 | 92 | override def componentWillUnmount(self: This) = { 93 | self.state.interval.foreach(window.clearInterval) 94 | } 95 | 96 | @scalax 97 | def render(self: This) = { 98 |
Seconds Elapsed: {self.state.secondsElapsed}
99 | } 100 | } 101 | ``` 102 | 103 | ## Typed listeners and closures 104 | 105 | ```scala 106 | object Say extends TypedReactSpec with TypedEventListeners { 107 | 108 | case class Props() 109 | case class State(text: String) 110 | 111 | implicit class Closure(self: This) { 112 | import self._ 113 | 114 | val onChange = input.onChange(e => { 115 | setState(state.copy(text = e.target.value)) 116 | }) 117 | 118 | val onClick = button.onClick(e => { 119 | alert(state.text) 120 | }) 121 | } 122 | 123 | def getInitialState(self: This) = State(text = "") 124 | 125 | @scalax 126 | def render(self: This) = { 127 |
128 | 129 | 130 |
131 | } 132 | } 133 | ``` 134 | 135 | # How to run examples 136 | 137 | $ sbt "project scalajs-react-examples" fastOptJS 138 | $ open scalajs-react-examples/index.html 139 | 140 | # Copyright 141 | 142 | Copyright © 2014 Xored Software, Inc. 143 | 144 | Licensed under the Apache License, Version 2.0. 145 | 146 | -------------------------------------------------------------------------------- /project/ScalaJSReact.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Keys._ 3 | 4 | import org.scalajs.sbtplugin.ScalaJSPlugin 5 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 6 | 7 | object ScalaJSReact extends Build { 8 | 9 | val SCALA_VERSION = "2.11.5" 10 | 11 | val uTestVersion = "0.3.0" 12 | val scalajsDomVersion = "0.7.0" 13 | val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.0.2" 14 | val scalaReflect = "org.scala-lang" % "scala-reflect" % SCALA_VERSION 15 | val macroParadisePlugin = compilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full) 16 | val jasmine = "org.scala-js" %% "scalajs-jasmine-test-framework" % scalaJSVersion % "test" 17 | val reactjs = "org.webjars" % "react" % "0.11.0" / "react.js" commonJSName "React" 18 | 19 | val commonSettings = Seq( 20 | version := "0.3.4-SNAPSHOT", 21 | organization := "com.xored.scalajs", 22 | scalaVersion := SCALA_VERSION, 23 | licenses := Seq("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0.html")), 24 | homepage := Some(url("http://github.com/xored/scala-js-react/")), 25 | scalacOptions := Seq("-unchecked", "-deprecation", 26 | "-encoding", "utf8", "-feature", "-Yinline-warnings", 27 | "-language:implicitConversions", "-language:higherKinds") 28 | ) 29 | 30 | lazy val react = Project("scalajs-react", file("scalajs-react")) 31 | .enablePlugins(ScalaJSPlugin) 32 | .settings(commonSettings: _*) 33 | .settings( 34 | libraryDependencies ++= Seq( 35 | "org.scala-js" %%% "scalajs-dom" % scalajsDomVersion, 36 | scalaXml, 37 | scalaReflect, 38 | macroParadisePlugin 39 | ), 40 | jsDependencies += reactjs, 41 | test in Test := {} 42 | ) 43 | 44 | lazy val reactTests = Project("scalajs-react-tests", file("scalajs-react-tests")) 45 | .enablePlugins(ScalaJSPlugin) 46 | .settings(commonSettings: _*) 47 | .settings( 48 | libraryDependencies ++= Seq( 49 | "org.scala-js" %%% "scalajs-dom" % scalajsDomVersion, 50 | scalaXml, 51 | macroParadisePlugin, 52 | "com.lihaoyi" %%% "utest" % uTestVersion % "test" 53 | ), 54 | scalaJSStage := FastOptStage, 55 | testFrameworks += new TestFramework("utest.runner.Framework"), 56 | publishArtifact := false 57 | ) 58 | .dependsOn(react) 59 | 60 | lazy val examples = Project("scalajs-react-examples", file("scalajs-react-examples")) 61 | .enablePlugins(ScalaJSPlugin) 62 | .settings(commonSettings: _*) 63 | .settings( 64 | libraryDependencies ++= Seq( 65 | macroParadisePlugin 66 | ), 67 | skip in packageJSDependencies := false, // creates scalajs-react-examples-jsdeps.js 68 | test in Test := {} 69 | ) 70 | .dependsOn(react) 71 | 72 | val root = Project("root", file(".")) 73 | .settings(publishArtifact := false) 74 | .aggregate(react, reactTests, examples) 75 | 76 | resolvers += Resolver.sonatypeRepo("releases") 77 | 78 | } 79 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | 3 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.0") 2 | 3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "0.2.1") 4 | 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3") 6 | 7 | -------------------------------------------------------------------------------- /scalajs-react-examples/children.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Children 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /scalajs-react-examples/css/base.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | -ms-appearance: none; 18 | -o-appearance: none; 19 | appearance: none; 20 | } 21 | 22 | body { 23 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 24 | line-height: 1.4em; 25 | background: #eaeaea url('bg.png'); 26 | color: #4d4d4d; 27 | width: 550px; 28 | margin: 0 auto; 29 | -webkit-font-smoothing: antialiased; 30 | -moz-font-smoothing: antialiased; 31 | -ms-font-smoothing: antialiased; 32 | -o-font-smoothing: antialiased; 33 | font-smoothing: antialiased; 34 | } 35 | 36 | button, 37 | input[type="checkbox"] { 38 | outline: none; 39 | } 40 | 41 | #todoapp { 42 | background: #fff; 43 | background: rgba(255, 255, 255, 0.9); 44 | margin: 130px 0 40px 0; 45 | border: 1px solid #ccc; 46 | position: relative; 47 | border-top-left-radius: 2px; 48 | border-top-right-radius: 2px; 49 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 50 | 0 25px 50px 0 rgba(0, 0, 0, 0.15); 51 | } 52 | 53 | #todoapp:before { 54 | content: ''; 55 | border-left: 1px solid #f5d6d6; 56 | border-right: 1px solid #f5d6d6; 57 | width: 2px; 58 | position: absolute; 59 | top: 0; 60 | left: 40px; 61 | height: 100%; 62 | } 63 | 64 | #todoapp input::-webkit-input-placeholder { 65 | font-style: italic; 66 | } 67 | 68 | #todoapp input::-moz-placeholder { 69 | font-style: italic; 70 | color: #a9a9a9; 71 | } 72 | 73 | #todoapp h1 { 74 | position: absolute; 75 | top: -120px; 76 | width: 100%; 77 | font-size: 70px; 78 | font-weight: bold; 79 | text-align: center; 80 | color: #b3b3b3; 81 | color: rgba(255, 255, 255, 0.3); 82 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2); 83 | -webkit-text-rendering: optimizeLegibility; 84 | -moz-text-rendering: optimizeLegibility; 85 | -ms-text-rendering: optimizeLegibility; 86 | -o-text-rendering: optimizeLegibility; 87 | text-rendering: optimizeLegibility; 88 | } 89 | 90 | #header { 91 | padding-top: 15px; 92 | border-radius: inherit; 93 | } 94 | 95 | #header:before { 96 | content: ''; 97 | position: absolute; 98 | top: 0; 99 | right: 0; 100 | left: 0; 101 | height: 15px; 102 | z-index: 2; 103 | border-bottom: 1px solid #6c615c; 104 | background: #8d7d77; 105 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); 106 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 107 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 108 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); 109 | border-top-left-radius: 1px; 110 | border-top-right-radius: 1px; 111 | } 112 | 113 | #new-todo, 114 | .edit { 115 | position: relative; 116 | margin: 0; 117 | width: 100%; 118 | font-size: 24px; 119 | font-family: inherit; 120 | line-height: 1.4em; 121 | border: 0; 122 | outline: none; 123 | color: inherit; 124 | padding: 6px; 125 | border: 1px solid #999; 126 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 127 | -moz-box-sizing: border-box; 128 | -ms-box-sizing: border-box; 129 | -o-box-sizing: border-box; 130 | box-sizing: border-box; 131 | -webkit-font-smoothing: antialiased; 132 | -moz-font-smoothing: antialiased; 133 | -ms-font-smoothing: antialiased; 134 | -o-font-smoothing: antialiased; 135 | font-smoothing: antialiased; 136 | } 137 | 138 | #new-todo { 139 | padding: 16px 16px 16px 60px; 140 | border: none; 141 | background: rgba(0, 0, 0, 0.02); 142 | z-index: 2; 143 | box-shadow: none; 144 | } 145 | 146 | #main { 147 | position: relative; 148 | z-index: 2; 149 | border-top: 1px dotted #adadad; 150 | } 151 | 152 | label[for='toggle-all'] { 153 | display: none; 154 | } 155 | 156 | #toggle-all { 157 | position: absolute; 158 | top: -42px; 159 | left: -4px; 160 | width: 40px; 161 | text-align: center; 162 | /* Mobile Safari */ 163 | border: none; 164 | } 165 | 166 | #toggle-all:before { 167 | content: '»'; 168 | font-size: 28px; 169 | color: #d9d9d9; 170 | padding: 0 25px 7px; 171 | } 172 | 173 | #toggle-all:checked:before { 174 | color: #737373; 175 | } 176 | 177 | #todo-list { 178 | margin: 0; 179 | padding: 0; 180 | list-style: none; 181 | } 182 | 183 | #todo-list li { 184 | position: relative; 185 | font-size: 24px; 186 | border-bottom: 1px dotted #ccc; 187 | } 188 | 189 | #todo-list li:last-child { 190 | border-bottom: none; 191 | } 192 | 193 | #todo-list li.editing { 194 | border-bottom: none; 195 | padding: 0; 196 | } 197 | 198 | #todo-list li.editing .edit { 199 | display: block; 200 | width: 506px; 201 | padding: 13px 17px 12px 17px; 202 | margin: 0 0 0 43px; 203 | } 204 | 205 | #todo-list li.editing .view { 206 | display: none; 207 | } 208 | 209 | #todo-list li .toggle { 210 | text-align: center; 211 | width: 40px; 212 | /* auto, since non-WebKit browsers doesn't support input styling */ 213 | height: auto; 214 | position: absolute; 215 | top: 0; 216 | bottom: 0; 217 | margin: auto 0; 218 | /* Mobile Safari */ 219 | border: none; 220 | -webkit-appearance: none; 221 | -ms-appearance: none; 222 | -o-appearance: none; 223 | appearance: none; 224 | } 225 | 226 | #todo-list li .toggle:after { 227 | content: '✔'; 228 | /* 40 + a couple of pixels visual adjustment */ 229 | line-height: 43px; 230 | font-size: 20px; 231 | color: #d9d9d9; 232 | text-shadow: 0 -1px 0 #bfbfbf; 233 | } 234 | 235 | #todo-list li .toggle:checked:after { 236 | color: #85ada7; 237 | text-shadow: 0 1px 0 #669991; 238 | bottom: 1px; 239 | position: relative; 240 | } 241 | 242 | #todo-list li label { 243 | white-space: pre; 244 | word-break: break-word; 245 | padding: 15px 60px 15px 15px; 246 | margin-left: 45px; 247 | display: block; 248 | line-height: 1.2; 249 | -webkit-transition: color 0.4s; 250 | transition: color 0.4s; 251 | } 252 | 253 | #todo-list li.completed label { 254 | color: #a9a9a9; 255 | text-decoration: line-through; 256 | } 257 | 258 | #todo-list li .destroy { 259 | display: none; 260 | position: absolute; 261 | top: 0; 262 | right: 10px; 263 | bottom: 0; 264 | width: 40px; 265 | height: 40px; 266 | margin: auto 0; 267 | font-size: 22px; 268 | color: #a88a8a; 269 | -webkit-transition: all 0.2s; 270 | transition: all 0.2s; 271 | } 272 | 273 | #todo-list li .destroy:hover { 274 | text-shadow: 0 0 1px #000, 275 | 0 0 10px rgba(199, 107, 107, 0.8); 276 | -webkit-transform: scale(1.3); 277 | -ms-transform: scale(1.3); 278 | transform: scale(1.3); 279 | } 280 | 281 | #todo-list li .destroy:after { 282 | content: '✖'; 283 | } 284 | 285 | #todo-list li:hover .destroy { 286 | display: block; 287 | } 288 | 289 | #todo-list li .edit { 290 | display: none; 291 | } 292 | 293 | #todo-list li.editing:last-child { 294 | margin-bottom: -1px; 295 | } 296 | 297 | #footer { 298 | color: #777; 299 | padding: 0 15px; 300 | position: absolute; 301 | right: 0; 302 | bottom: -31px; 303 | left: 0; 304 | height: 20px; 305 | z-index: 1; 306 | text-align: center; 307 | } 308 | 309 | #footer:before { 310 | content: ''; 311 | position: absolute; 312 | right: 0; 313 | bottom: 31px; 314 | left: 0; 315 | height: 50px; 316 | z-index: -1; 317 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 318 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 319 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 320 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 321 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 322 | } 323 | 324 | #todo-count { 325 | float: left; 326 | text-align: left; 327 | } 328 | 329 | #filters { 330 | margin: 0; 331 | padding: 0; 332 | list-style: none; 333 | position: absolute; 334 | right: 0; 335 | left: 0; 336 | } 337 | 338 | #filters li { 339 | display: inline; 340 | } 341 | 342 | #filters li a { 343 | color: #83756f; 344 | margin: 2px; 345 | text-decoration: none; 346 | } 347 | 348 | #filters li a.selected { 349 | font-weight: bold; 350 | } 351 | 352 | #clear-completed { 353 | float: right; 354 | position: relative; 355 | line-height: 20px; 356 | text-decoration: none; 357 | background: rgba(0, 0, 0, 0.1); 358 | font-size: 11px; 359 | padding: 0 10px; 360 | border-radius: 3px; 361 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 362 | } 363 | 364 | #clear-completed:hover { 365 | background: rgba(0, 0, 0, 0.15); 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 367 | } 368 | 369 | #info { 370 | margin: 65px auto 0; 371 | color: #a6a6a6; 372 | font-size: 12px; 373 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 374 | text-align: center; 375 | } 376 | 377 | #info a { 378 | color: inherit; 379 | } 380 | 381 | /* 382 | Hack to remove background from Mobile Safari. 383 | Can't use it globally since it destroys checkboxes in Firefox and Opera 384 | */ 385 | 386 | @media screen and (-webkit-min-device-pixel-ratio:0) { 387 | #toggle-all, 388 | #todo-list li .toggle { 389 | background: none; 390 | } 391 | 392 | #todo-list li .toggle { 393 | height: 40px; 394 | } 395 | 396 | #toggle-all { 397 | top: -56px; 398 | left: -15px; 399 | width: 65px; 400 | height: 41px; 401 | -webkit-transform: rotate(90deg); 402 | -ms-transform: rotate(90deg); 403 | transform: rotate(90deg); 404 | -webkit-appearance: none; 405 | appearance: none; 406 | } 407 | } 408 | 409 | .hidden { 410 | display: none; 411 | } 412 | 413 | hr { 414 | margin: 20px 0; 415 | border: 0; 416 | border-top: 1px dashed #C5C5C5; 417 | border-bottom: 1px dashed #F7F7F7; 418 | } 419 | 420 | .learn a { 421 | font-weight: normal; 422 | text-decoration: none; 423 | color: #b83f45; 424 | } 425 | 426 | .learn a:hover { 427 | text-decoration: underline; 428 | color: #787e7e; 429 | } 430 | 431 | .learn h3, 432 | .learn h4, 433 | .learn h5 { 434 | margin: 10px 0; 435 | font-weight: 500; 436 | line-height: 1.2; 437 | color: #000; 438 | } 439 | 440 | .learn h3 { 441 | font-size: 24px; 442 | } 443 | 444 | .learn h4 { 445 | font-size: 18px; 446 | } 447 | 448 | .learn h5 { 449 | margin-bottom: 0; 450 | font-size: 14px; 451 | } 452 | 453 | .learn ul { 454 | padding: 0; 455 | margin: 0 0 30px 25px; 456 | } 457 | 458 | .learn li { 459 | line-height: 20px; 460 | } 461 | 462 | .learn p { 463 | font-size: 15px; 464 | font-weight: 300; 465 | line-height: 1.3; 466 | margin-top: 0; 467 | margin-bottom: 0; 468 | } 469 | 470 | .quote { 471 | border: none; 472 | margin: 20px 0 60px 0; 473 | } 474 | 475 | .quote p { 476 | font-style: italic; 477 | } 478 | 479 | .quote p:before { 480 | content: '“'; 481 | font-size: 50px; 482 | opacity: .15; 483 | position: absolute; 484 | top: -20px; 485 | left: 3px; 486 | } 487 | 488 | .quote p:after { 489 | content: '”'; 490 | font-size: 50px; 491 | opacity: .15; 492 | position: absolute; 493 | bottom: -42px; 494 | right: 3px; 495 | } 496 | 497 | .quote footer { 498 | position: absolute; 499 | bottom: -40px; 500 | right: 0; 501 | } 502 | 503 | .quote footer img { 504 | border-radius: 3px; 505 | } 506 | 507 | .quote footer a { 508 | margin-left: 5px; 509 | vertical-align: middle; 510 | } 511 | 512 | .speech-bubble { 513 | position: relative; 514 | padding: 10px; 515 | background: rgba(0, 0, 0, .04); 516 | border-radius: 5px; 517 | } 518 | 519 | .speech-bubble:after { 520 | content: ''; 521 | position: absolute; 522 | top: 100%; 523 | right: 30px; 524 | border: 13px solid transparent; 525 | border-top-color: rgba(0, 0, 0, .04); 526 | } 527 | 528 | .learn-bar > .learn { 529 | position: absolute; 530 | width: 272px; 531 | top: 8px; 532 | left: -300px; 533 | padding: 10px; 534 | border-radius: 5px; 535 | background-color: rgba(255, 255, 255, .6); 536 | -webkit-transition-property: left; 537 | transition-property: left; 538 | -webkit-transition-duration: 500ms; 539 | transition-duration: 500ms; 540 | } 541 | 542 | @media (min-width: 899px) { 543 | .learn-bar { 544 | width: auto; 545 | margin: 0 0 0 300px; 546 | } 547 | 548 | .learn-bar > .learn { 549 | left: 8px; 550 | } 551 | 552 | .learn-bar #todoapp { 553 | width: 550px; 554 | margin: 130px auto 40px auto; 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /scalajs-react-examples/css/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xored/scala-js-react/b213a6f5e2704d1c0293cab5e0146923525eed58/scalajs-react-examples/css/bg.png -------------------------------------------------------------------------------- /scalajs-react-examples/export.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Export 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /scalajs-react-examples/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /scalajs-react-examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Scala-js-react Examples

6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /scalajs-react-examples/say.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /scalajs-react-examples/src/main/scala/com/xored/scalajs/react/examples/App.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.examples 2 | 3 | import org.scalajs.dom._ 4 | import com.xored.scalajs.react._ 5 | import scala.scalajs.js.annotation.JSExport 6 | import com.xored.scalajs.react.examples.todomvc.TodoApp 7 | import com.xored.scalajs.react.examples.timer.Timer 8 | import com.xored.scalajs.react.examples.hello.HelloMessage 9 | import com.xored.scalajs.react.examples.say.Say 10 | import com.xored.scalajs.react.examples.children.Children 11 | import com.xored.scalajs.react.examples.export.Export 12 | 13 | @JSExport("App") 14 | object App { 15 | 16 | @JSExport 17 | def todoApp(parent: HTMLElement) = { 18 | React.renderComponent( 19 | TodoApp(TodoApp.Props()), 20 | parent 21 | ) 22 | } 23 | 24 | @JSExport 25 | def timer(parent: HTMLElement) = { 26 | React.renderComponent( 27 | Timer(Timer.Props()), 28 | parent 29 | ) 30 | } 31 | 32 | @JSExport 33 | def hello(parent: HTMLElement) = { 34 | React.renderComponent( 35 | HelloMessage(HelloMessage.Props("Jack")), 36 | parent 37 | ) 38 | } 39 | 40 | @JSExport 41 | def say(parent: HTMLElement) = { 42 | React.renderComponent( 43 | Say(Say.Props()), 44 | parent 45 | ) 46 | } 47 | 48 | @JSExport 49 | def export(parent: HTMLElement) = { 50 | React.renderComponent( 51 | Export(Export.Props()), 52 | parent 53 | ) 54 | } 55 | 56 | @JSExport 57 | def children(parent: HTMLElement) = { 58 | Children.main(parent) 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /scalajs-react-examples/src/main/scala/com/xored/scalajs/react/examples/children/Children.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.examples.children 2 | 3 | import com.xored.scalajs.react._ 4 | import org.scalajs.dom._ 5 | import com.xored.scalajs.react.util.ClassName 6 | 7 | object Children { 8 | 9 | object Button extends TypedReactSpec { 10 | case class State() 11 | case class Props(size: String, label: String, primary: Boolean) 12 | 13 | /** 14 | * Convenience method to create component 15 | */ 16 | def apply(size: String, label: String, primary: Boolean): ComponentType = 17 | apply(Props(size, label, primary)) 18 | 19 | /** 20 | * Convenience method to create component 21 | */ 22 | def apply(size: String, label: String): ComponentType = 23 | apply(Props(size, label, primary = false)) 24 | 25 | def getInitialState(self: This) = State() 26 | 27 | @scalax 28 | def render(self: This) = { 29 | val className = ClassName( 30 | s"button-${self.props.size}" -> true, 31 | s"button-primary" -> self.props.primary) 32 | 33 | 34 | } 35 | } 36 | 37 | object ButtonToolbar extends TypedReactSpec { 38 | case class Props(buttons: ReactDOM*) 39 | case class State() 40 | 41 | def getInitialState(self: This) = State() 42 | 43 | /** 44 | * Convenience method to create component 45 | */ 46 | def apply(buttons: ReactDOM*): ComponentType = 47 | apply(Props(buttons: _*)) 48 | 49 | @scalax 50 | def render(self: This) = { 51 |
52 | { 53 | self.props.buttons 54 | } 55 |
56 | } 57 | } 58 | 59 | def main(parent: HTMLElement) = { 60 | @scalax val buttons =
61 | { 62 | ButtonToolbar( 63 | , 64 | Button("small", "Small Button"), 65 | Button("small", "Small Button", primary = true) 66 | ) 67 | } 68 | { 69 | ButtonToolbar( 70 | , 71 | Button("large", "Large Button"), 72 | Button("large", "Large Button", primary = true) 73 | ) 74 | } 75 |
76 | 77 | React.renderComponent(buttons, document.body) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /scalajs-react-examples/src/main/scala/com/xored/scalajs/react/examples/export/Export.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.examples.export 2 | 3 | import com.xored.scalajs.react._ 4 | import com.xored.scalajs.react.export._ 5 | import com.xored.scalajs.react.util.TypedEventListeners 6 | import scala.scalajs.js 7 | 8 | object ContentEditable extends TypedReactSpec { 9 | case class Props() 10 | case class State() 11 | 12 | def getInitialState(self: This) = State() 13 | 14 | override def exports() = export1("focus", focus) :: super.exports() 15 | 16 | def focus(self: This): Unit = { 17 | self.refs("input").getDOMNode().focus() 18 | } 19 | 20 | @scalax 21 | def render(self: This) = { 22 | val style = Map("width" -> "100px", "background" -> "#efef00", "float" -> "left") 23 |
24 | } 25 | } 26 | 27 | trait Focusable extends js.Object { 28 | def focus(): Unit = js.native 29 | } 30 | 31 | object Focusable { 32 | def cast(ref: ReactDOMRef): Option[Focusable] = { 33 | if (ref.hasOwnProperty("focus")) Some(ref.asInstanceOf[Focusable]) 34 | else None 35 | } 36 | } 37 | 38 | object Export extends TypedReactSpec with TypedEventListeners { 39 | case class Props() 40 | case class State() 41 | 42 | def getInitialState(self: This) = State() 43 | 44 | implicit class Closure(self: This) { 45 | def focus(ref: String) = button.onClick(e => { 46 | val elem = self.refs(ref) 47 | Focusable.cast(elem).foreach(_.focus()) 48 | }) 49 | } 50 | 51 | @scalax 52 | def render(self: This) = { 53 | val margin = Map("margin" -> "10px") 54 | 55 |
56 |
57 | { 58 | ContentEditable(ContentEditable.Props(), ref = "input-1") 59 | } 60 | 61 |
62 |
63 | { 64 | ContentEditable(ContentEditable.Props(), ref = "input-2") 65 | } 66 | 67 |
68 |
69 | } 70 | } 71 | -------------------------------------------------------------------------------- /scalajs-react-examples/src/main/scala/com/xored/scalajs/react/examples/hello/HelloMessage.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.examples.hello 2 | 3 | import com.xored.scalajs.react._ 4 | 5 | object HelloMessage extends TypedReactSpec { 6 | 7 | case class State() 8 | case class Props(name: String) 9 | 10 | def getInitialState(self: This) = State() 11 | 12 | @scalax 13 | def render(self: This) = { 14 |
Hello {self.props.name}
15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scalajs-react-examples/src/main/scala/com/xored/scalajs/react/examples/say/Say.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.examples.say 2 | 3 | import com.xored.scalajs.react._ 4 | import org.scalajs.dom._ 5 | import com.xored.scalajs.react.util.TypedEventListeners 6 | 7 | object Say extends TypedReactSpec with TypedEventListeners { 8 | 9 | case class Props() 10 | case class State(text: String) 11 | 12 | implicit class Closure(self: This) { 13 | import self._ 14 | 15 | val onChange = input.onChange(e => { 16 | setState(state.copy(text = e.target.value)) 17 | }) 18 | 19 | val onClick = button.onClick(e => { 20 | alert(state.text) 21 | }) 22 | } 23 | 24 | def getInitialState(self: This) = State(text = "") 25 | 26 | @scalax 27 | def render(self: This) = { 28 |
29 | 30 | 31 |
32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scalajs-react-examples/src/main/scala/com/xored/scalajs/react/examples/timer/Timer.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.examples.timer 2 | 3 | import com.xored.scalajs.react._ 4 | import org.scalajs.dom._ 5 | 6 | object Timer extends TypedReactSpec { 7 | 8 | case class Props() 9 | case class State(secondsElapsed: Int, interval: Option[Int]) 10 | 11 | def getInitialState(self: This) = State(secondsElapsed = 0, interval = None) 12 | 13 | implicit class Closure(self: This) { 14 | import self._ 15 | 16 | val tick = () => { 17 | setState(state.copy(secondsElapsed = state.secondsElapsed + 1)) 18 | } 19 | } 20 | 21 | override def componentDidMount(self: This) = { 22 | val interval = window.setInterval(self.tick, 1000) 23 | 24 | self.setState(self.state.copy(interval = Some(interval))) 25 | } 26 | 27 | override def componentWillUnmount(self: This) = { 28 | self.state.interval.foreach(window.clearInterval) 29 | } 30 | 31 | @scalax 32 | def render(self: This) = { 33 |
Seconds Elapsed: {self.state.secondsElapsed}
34 | } 35 | } 36 | -------------------------------------------------------------------------------- /scalajs-react-examples/src/main/scala/com/xored/scalajs/react/examples/todomvc/Todo.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.examples.todomvc 2 | 3 | case class Todo(uuid: String, title: String, completed: Boolean) 4 | 5 | -------------------------------------------------------------------------------- /scalajs-react-examples/src/main/scala/com/xored/scalajs/react/examples/todomvc/TodoApp.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.examples.todomvc 2 | 3 | import com.xored.scalajs.react._ 4 | import com.xored.scalajs.react.util._ 5 | import org.scalajs.dom._ 6 | 7 | import scala.scalajs.js.annotation.JSExport 8 | import org.scalajs.dom.extensions.KeyCode 9 | 10 | @JSExport("TodoApp") 11 | object TodoApp extends TypedReactSpec with TypedEventListeners { 12 | 13 | case class Props() 14 | 15 | case class State(text: String, editingTodoUuid: Option[String], todos: Vector[Todo]) 16 | 17 | def getInitialState(self: This) = State( 18 | text = "", 19 | editingTodoUuid = None, 20 | todos = Vector()) 21 | 22 | implicit class Closure(self: This) { 23 | 24 | import self._ 25 | 26 | val onChange = input.onChange(e => { 27 | setState(state.copy(text = e.target.value)) 28 | }) 29 | 30 | val onKeyPress = input.onKeyPress(e => { 31 | if (e.keyCode == KeyCode.enter) { 32 | e.preventDefault() 33 | 34 | val todo = Todo(uuid = java.util.UUID.randomUUID().toString, title = state.text, completed = false) 35 | 36 | setState(state.copy( 37 | text = "", 38 | todos = todo +: state.todos 39 | )) 40 | } 41 | }) 42 | 43 | def onDestroy(todo: Todo) = { 44 | setState(state.copy(todos = state.todos.diff(Seq(todo)))) 45 | } 46 | 47 | def onCancel() = { 48 | setState(state.copy(editingTodoUuid = None)) 49 | } 50 | 51 | def onToggle(todo: Todo) = { 52 | val idx = state.todos.indexOf(todo) 53 | val newTodos = state.todos.updated(idx, todo.copy(completed = !todo.completed)) 54 | 55 | setState(state.copy(todos = newTodos)) 56 | } 57 | 58 | def onSave(todo: Todo, value: String) = { 59 | val idx = state.todos.indexOf(todo) 60 | val newTodos = state.todos.updated(idx, todo.copy(title = value)) 61 | 62 | setState(state.copy(todos = newTodos, editingTodoUuid = None)) 63 | } 64 | 65 | def onEdit(todo: Todo, callback: () => Unit) = { 66 | setState(state.copy(editingTodoUuid = Some(todo.uuid)), callback) 67 | } 68 | 69 | val clearCompleted = () => { 70 | val newTodos = state.todos.filterNot(_.completed) 71 | setState(state.copy(todos = newTodos)) 72 | } 73 | 74 | val toggleAll = () => { 75 | val newTodos = state.todos.map { 76 | case todo if !todo.completed => todo.copy(completed = true) 77 | case x => x 78 | } 79 | 80 | setState(state.copy(todos = newTodos)) 81 | } 82 | } 83 | 84 | @scalax 85 | def render(self: This) = { 86 | val activeTodoCount = self.state.todos.count(!_.completed) 87 | 88 | // Use onKeyUp temporarily because onKeyPress does not work on Firefox with current version of react 89 | // See https://github.com/facebook/react/pull/1956 90 | 91 |
92 | 101 |
102 | 107 | 108 | 125 |
126 | { 127 | if (self.state.todos.nonEmpty) { 128 | 134 | } 135 | } 136 |
137 | } 138 | } 139 | -------------------------------------------------------------------------------- /scalajs-react-examples/src/main/scala/com/xored/scalajs/react/examples/todomvc/TodoItem.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.examples.todomvc 2 | 3 | import com.xored.scalajs.react._ 4 | import com.xored.scalajs.react.util._ 5 | import org.scalajs.dom._ 6 | 7 | import org.scalajs.dom.extensions.KeyCode 8 | 9 | object TodoItem extends TypedReactSpec with TypedEventListeners { 10 | 11 | case class Props( 12 | todo: Todo, 13 | editing: Boolean, 14 | onDestroy: () => Unit, 15 | onCancel: () => Unit, 16 | onToggle: () => Unit, 17 | onEdit: ( () => Unit ) => Unit, 18 | onSave: String => Unit) 19 | 20 | case class State(editText: String) 21 | 22 | def getInitialState(self: This) = State(self.props.todo.title) 23 | 24 | val editField = "editField" 25 | 26 | implicit class Closure(self: This) { 27 | 28 | import self._ 29 | 30 | val handleChange = input.onChange(e => { 31 | self.setState(state.copy(editText = e.target.value)) 32 | }) 33 | 34 | val handleEdit = element.onEvent(e => { 35 | props.onEdit(() => { 36 | val node = self.refs(editField).getDOMNode().asInstanceOf[HTMLInputElement] 37 | node.focus() 38 | node.setSelectionRange(node.value.length, node.value.length) 39 | }) 40 | 41 | e.preventDefault() 42 | }) 43 | 44 | val handleSubmit = element.onEvent(e => { 45 | val value = self.state.editText.trim() 46 | 47 | if (value.nonEmpty) { 48 | props.onSave(value) 49 | setState(self.state.copy(editText = value)) 50 | } else { 51 | self.props.onDestroy() 52 | } 53 | 54 | e.preventDefault() 55 | }) 56 | 57 | val handleKeyDown = input.onKeyPress(e => { 58 | if (e.keyCode == KeyCode.escape) { 59 | setState(state.copy(editText = self.props.todo.title)) 60 | props.onCancel() 61 | } else if (e.keyCode == KeyCode.enter) { 62 | handleSubmit(e) 63 | } 64 | }) 65 | } 66 | 67 | override def key(props: This#Props) = props.todo.uuid 68 | 69 | @scalax 70 | def render(self: This) = { 71 | val className = ClassName( 72 | "complete" -> self.props.todo.completed, 73 | "editing" -> self.props.editing) 74 | 75 |
  • 76 |
    77 | 81 | 82 | 83 | 84 |
    85 | 91 | 92 |
  • 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /scalajs-react-examples/timer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Timer 6 | 7 | 8 |
    9 |
    10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /scalajs-react-examples/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TodoMVC 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /scalajs-react-tests/src/test/scala/com/xored/scalajs/react/ReactRenderSuite.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react 2 | 3 | import scala.scalajs.js 4 | import com.xored.scalajs.react.comp._ 5 | import utest._ 6 | 7 | object ReactRenderSuite extends TestSuite { 8 | // TODO use React.addons.TestUtils and jsdom 9 | 10 | def renderToString(dom: ReactDOM) = React.renderComponentToStaticMarkup(dom) 11 | 12 | val tests = TestSuite { 13 | "React#renderComponent" - { 14 | "render " - { 15 | val reactDom = HelloWorld(HelloWorld.Props()) 16 | val dom = renderToString(reactDom) 17 | 18 | assert(dom == "

    Hello World!

    ") 19 | } 20 | 21 | "render " - { 22 | val reactDom = Hello(Hello.Props("Jack")) 23 | val dom = renderToString(reactDom) 24 | 25 | assert(dom == "

    Hello Jack!

    ") 26 | } 27 | 28 | "render

    Hello World

    " - { 29 | @scalax val reactDom =

    Hello World!

    30 | val dom = renderToString(reactDom) 31 | 32 | assert(dom == "

    Hello World!

    ") 33 | } 34 | 35 | "render
    " - { 36 | @scalax val reactDom =
    {HelloWorld(HelloWorld.Props())}
    37 | val dom = renderToString(reactDom) 38 | 39 | assert(dom == "

    Hello World!

    ") 40 | } 41 | 42 | "render " - { 43 | @scalax val reactDom = Panel(Panel.Props( 44 | HelloWorld(HelloWorld.Props()), 45 | 46 | )) 47 | val dom = renderToString(reactDom) 48 | 49 | assert(dom == "

    Hello World!

    ") 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scalajs-react-tests/src/test/scala/com/xored/scalajs/react/comp/Hello.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.comp 2 | 3 | import com.xored.scalajs.react._ 4 | 5 | object Hello extends TypedReactSpec { 6 | case class Props(name: String) 7 | case class State() 8 | 9 | def getInitialState(self: This) = State() 10 | 11 | @scalax 12 | def render(self: This) = { 13 |

    Hello {self.props.name}!

    14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /scalajs-react-tests/src/test/scala/com/xored/scalajs/react/comp/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.comp 2 | 3 | import com.xored.scalajs.react._ 4 | 5 | object HelloWorld extends TypedReactSpec { 6 | case class Props() 7 | case class State() 8 | 9 | def getInitialState(self: This) = State() 10 | 11 | @scalax 12 | def render(self: This) = { 13 |

    Hello World!

    14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scalajs-react-tests/src/test/scala/com/xored/scalajs/react/comp/Panel.scala: -------------------------------------------------------------------------------- 1 | package com.xored.scalajs.react.comp 2 | 3 | import com.xored.scalajs.react._ 4 | 5 | object Panel extends TypedReactSpec { 6 | case class State() 7 | case class Props(children: ReactDOM*) 8 | 9 | def getInitialState(self: This) = State() 10 | 11 | @scalax 12 | def render(self: This) = { 13 |
    14 | { 15 | self.props.children 16 | } 17 |
    18 | } 19 | } 20 | -------------------------------------------------------------------------------- /scalajs-react/build.sbt: -------------------------------------------------------------------------------- 1 | import SonatypeKeys._ 2 | 3 | sonatypeSettings 4 | 5 | pomExtra := { 6 | 7 | scm:git:github.com/xored/scala-js-react.git 8 | scm:git:git@github.com:scala-js-react.git 9 | https://github.com/xored/scala-js-react 10 | {"release-0.3.3"} 11 | 12 | 13 | 14 | kanterov 15 | Gleb Kanterov 16 | gleb@kanterov.ru 17 | 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/React.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react 18 | 19 | import com.xored.scalajs.react.interop._ 20 | import org.scalajs.dom._ 21 | import scala.scalajs.js 22 | 23 | object React { 24 | 25 | val DOM = ReactDOM 26 | 27 | def renderComponent[C <: ReactDOM](dom: C, container: HTMLElement): C = { 28 | ReactJS.renderComponent(dom, container) 29 | } 30 | 31 | def renderComponentToString(dom: ReactDOM): String = { 32 | ReactJS.renderComponentToString(dom) 33 | } 34 | 35 | def renderComponentToStaticMarkup(dom: ReactDOM): String = { 36 | ReactJS.renderComponentToStaticMarkup(dom) 37 | } 38 | 39 | def createClass(spec: ReactSpec): ReactComponentClass[spec.type#This#State, spec.type#This#Props] = { 40 | val exports = spec.exports() 41 | val reactSpec = js.Dictionary(exports.map(x => (x.name, x.value)): _*) 42 | 43 | ReactJS.createClass(reactSpec) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/ReactDOM.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react 18 | 19 | import scala.scalajs.js 20 | import org.scalajs.dom._ 21 | import com.xored.scalajs.react.interop.ReactJS 22 | 23 | trait ReactDOM extends HTMLElement 24 | 25 | trait ReactDOMRef extends js.Object { 26 | def getDOMNode(): ReactDOM = js.native // we need brackets for interop! 27 | } 28 | 29 | object ReactDOM { 30 | def selectDynamic(name: String, props: Any, args: js.Any*): ReactDOM = { 31 | js.Dynamic.global.React.DOM.selectDynamic(name).apply(props.asInstanceOf[js.Any] +: args: _*).asInstanceOf[ReactDOM] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/ReactSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react 18 | 19 | import scala.scalajs.js 20 | 21 | import scala.language.experimental.macros 22 | import com.xored.scalajs.react.interop._ 23 | import com.xored.scalajs.react.export._ 24 | 25 | trait ReactSpec { 26 | type This <: ThisLike 27 | type ComponentType = ReactComponent[This#State, This#Props] 28 | 29 | private[this] lazy val reactClass = React.createClass(this) 30 | 31 | def exports(): List[Export] = List( 32 | export1("render", render), 33 | export1("componentDidMount", componentDidMount), 34 | export1("componentWillMount", componentWillMount), 35 | export1("componentWillUnmount", componentWillUnmount), 36 | export2("componentWillReceiveProps", componentWillReceiveProps), 37 | export3("componentDidUpdate", componentDidUpdate), 38 | export3("shouldComponentUpdate", shouldComponentUpdate), 39 | export1("getInitialState", getInitialState) 40 | ) 41 | 42 | implicit def thisReader: JSBridgeFrom[This] 43 | 44 | implicit def reactPropsBridge: JSBridge[This#Props] 45 | 46 | implicit def reactStateBridge: JSBridge[This#State] 47 | 48 | def apply[T](props: This#Props, ref: Any = ())(implicit e: JSBridgeTo[This#Props]): ComponentType = { 49 | val dict = e(props).asInstanceOf[js.Dictionary[Any]] // anything js.Any could be cast to js.Dictionary 50 | 51 | js.UndefOr.any2undefOrA(key(props)).foreach( 52 | value => dict.update(ThisLike.REACT_KEY, value)) 53 | 54 | js.UndefOr.any2undefOrA(ref).foreach( 55 | value => dict.update(ThisLike.REACT_REF, value)) 56 | 57 | reactClass(dict) 58 | } 59 | 60 | def key(props: This#Props): Any = () 61 | 62 | def render(self: This): ReactDOM 63 | 64 | def componentDidMount(self: This): Unit = {} 65 | 66 | def componentDidUpdate(self: This, prevProps: This#Props, prevState: This#State): Unit = {} 67 | 68 | def componentWillMount(self: This): Unit = {} 69 | 70 | def componentWillUnmount(self: This): Unit = {} 71 | 72 | def componentWillReceiveProps(self: This, nextProps: This#Props): Unit = {} 73 | 74 | def shouldComponentUpdate(self: This, nextProps: This#Props, nextState: This#State): Boolean = { 75 | self.props != nextProps || self.state != nextState 76 | } 77 | 78 | def getInitialState(self: This): This#State 79 | } 80 | 81 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/ThisLike.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react 18 | 19 | trait ThisLike { 20 | type Props 21 | type State 22 | 23 | def props: Props 24 | def state: State 25 | def refs(key: String): ReactDOMRef 26 | def isMounted: Boolean 27 | 28 | def setState(newState: State): Unit 29 | def setState(newState: State, callback: () => Unit): Unit 30 | 31 | def forceUpdate(): Unit 32 | } 33 | 34 | object ThisLike { 35 | val REACT_KEY = "key" 36 | val REACT_REF = "ref" 37 | } 38 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/TypedReactSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react 18 | 19 | import scala.scalajs.js 20 | 21 | object TypedThisLike { 22 | val STATE_KEY = "__scalajs_react_state" 23 | val PROPS_KEY = "__scalajs_react_props" 24 | val COMPONENT_ID_KEY = "__scalajs_react_component_id" 25 | 26 | def reactPropsBridge[T] = new JSBridge[T] { 27 | def apply(props: T) = { 28 | js.Dictionary( 29 | (PROPS_KEY, props.asInstanceOf[js.Any]), 30 | (COMPONENT_ID_KEY, java.util.UUID.randomUUID().toString) 31 | ) 32 | } 33 | 34 | def apply(value: js.Any) = { 35 | val dynamic = value.asInstanceOf[js.Dynamic] 36 | dynamic.selectDynamic(PROPS_KEY).asInstanceOf[T] 37 | } 38 | } 39 | 40 | def reactStateBridge[T] = new JSBridge[T] { 41 | def apply(state: T): js.Any = { 42 | js.Dictionary( 43 | (STATE_KEY, state.asInstanceOf[js.Any]) 44 | ) 45 | } 46 | 47 | def apply(value: js.Any) = { 48 | val dynamic = value.asInstanceOf[js.Dynamic] 49 | dynamic.selectDynamic(STATE_KEY).asInstanceOf[T] 50 | } 51 | } 52 | } 53 | 54 | class TypedThisLike(self: js.Any) extends ThisLike { 55 | 56 | import TypedThisLike._ 57 | 58 | private val dynamic = self.asInstanceOf[js.Dynamic] 59 | 60 | def props = dynamic.props.selectDynamic(PROPS_KEY).asInstanceOf[Props] 61 | 62 | def state = dynamic.state.selectDynamic(STATE_KEY).asInstanceOf[State] 63 | 64 | def componentId = dynamic.props.selectDynamic(COMPONENT_ID_KEY).toString 65 | 66 | def refs(key: String) = dynamic.refs.selectDynamic(key).asInstanceOf[ReactDOMRef] 67 | 68 | def isMounted: Boolean = dynamic.isMounted().asInstanceOf[Boolean] 69 | 70 | def setState(newState: State) { 71 | setState(newState, () => {}) 72 | } 73 | 74 | def setState(newState: State, callback: () => Unit) { 75 | val dict = js.Dictionary((STATE_KEY, newState)) 76 | dynamic.setState(dict, js.Any.fromFunction0(callback)) 77 | } 78 | 79 | def forceUpdate(): Unit = { 80 | dynamic.forceUpdate() 81 | } 82 | } 83 | 84 | trait TypedReactSpec extends ReactSpec { 85 | spec => 86 | 87 | type State 88 | type Props 89 | 90 | implicit final val reactStateBridge = TypedThisLike.reactStateBridge[State] 91 | implicit final val reactPropsBridge = TypedThisLike.reactPropsBridge[Props] 92 | 93 | implicit final val thisReader = new JSBridgeFrom[This] { 94 | def apply(value: js.Any) = { 95 | val dynamic = value.asInstanceOf[js.Dynamic] 96 | new TypedThisLike(dynamic) { 97 | type State = spec.type#State 98 | type Props = spec.type#Props 99 | } 100 | } 101 | } 102 | 103 | final type This = TypedThisLike { 104 | type State = spec.type#State 105 | type Props = spec.type#Props 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/event/SyntheticEvent.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.event 18 | 19 | import org.scalajs.dom 20 | 21 | import scala.scalajs.js 22 | 23 | trait SyntheticEvent extends js.Object { 24 | val bubbles: Boolean = js.native 25 | val cancelable: Boolean = js.native 26 | 27 | val currentTarget: dom.EventTarget = js.native 28 | val target: dom.EventTarget = js.native 29 | val nativeEvent: dom.Event = js.native 30 | 31 | def preventDefault(): Unit = js.native 32 | def stopPropagation(): Unit = js.native 33 | 34 | val defaultPrevented: Boolean = js.native 35 | val eventPhase: Int = js.native 36 | val isTrusted: Boolean = js.native 37 | val timeStamp: js.Date = js.native 38 | val `type`: String = js.native 39 | } 40 | 41 | trait ClipboardEvent extends SyntheticEvent { 42 | val clipboardData: dom.DataTransfer = js.native 43 | } 44 | 45 | trait KeyboardEvent extends SyntheticEvent { 46 | val altKey: Boolean = js.native 47 | val ctrlKey: Boolean = js.native 48 | val metaKey: Boolean = js.native 49 | val shiftKey: Boolean = js.native 50 | 51 | val charCode: Int = js.native 52 | val key: String = js.native 53 | val keyCode: Int = js.native 54 | val locale: String = js.native 55 | val location: Int = js.native 56 | val repeat: Boolean = js.native 57 | val which: Int = js.native 58 | 59 | def getModifierState(keyArg: String): Boolean = js.native 60 | } 61 | 62 | trait FocusEvent extends SyntheticEvent { 63 | val relatedTarget: dom.EventTarget = js.native 64 | } 65 | 66 | trait FormEvent extends SyntheticEvent 67 | 68 | trait MouseEvent extends SyntheticEvent { 69 | val altKey: Boolean = js.native 70 | val ctrlKey: Boolean = js.native 71 | val metaKey: Boolean = js.native 72 | val shiftKey: Boolean = js.native 73 | 74 | val button: Int = js.native 75 | val buttons: Int = js.native 76 | 77 | val clientX: Int = js.native 78 | val clientY: Int = js.native 79 | val pageX: Int = js.native 80 | val pageY: Int = js.native 81 | val screenX: Int = js.native 82 | val screenY: Int = js.native 83 | 84 | val relatedTarget: dom.EventTarget = js.native 85 | } 86 | 87 | trait TouchEvent extends SyntheticEvent { 88 | val altKey: Boolean = js.native 89 | val ctrlKey: Boolean = js.native 90 | val metaKey: Boolean = js.native 91 | val shiftKey: Boolean = js.native 92 | 93 | val changedTouches: dom.TouchList = js.native 94 | val targetTouches: dom.TouchList = js.native 95 | val touches: dom.TouchList = js.native 96 | 97 | def getModifierState(keyArg: String): Boolean = js.native 98 | } 99 | 100 | trait UIEvent extends SyntheticEvent { 101 | val detail: Int = js.native 102 | val view: dom.Window = js.native 103 | } 104 | 105 | trait WheelEvent extends SyntheticEvent { 106 | val deltaMode: Int = js.native 107 | val deltaX: Double = js.native 108 | val deltaY: Double = js.native 109 | val deltaZ: Double = js.native 110 | } 111 | 112 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/event/TypedSyntheticEvent.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.event 18 | 19 | import org.scalajs.dom 20 | import org.scalajs.dom.HTMLInputElement 21 | 22 | import scala.scalajs.js 23 | 24 | trait TypedSyntheticEvent[+T <: dom.EventTarget] extends SyntheticEvent { 25 | override val target: T = js.native 26 | } 27 | 28 | trait TypedClipboardEvent[T <: dom.EventTarget] extends ClipboardEvent with TypedSyntheticEvent[T] 29 | trait TypedFocusEvent[T <: dom.EventTarget] extends FocusEvent with TypedSyntheticEvent[T] 30 | trait TypedFormEvent[T <: dom.EventTarget] extends FormEvent with TypedSyntheticEvent[T] 31 | trait TypedKeyboardEvent[T <: dom.EventTarget] extends KeyboardEvent with TypedSyntheticEvent[T] 32 | trait TypedMouseEvent[T <: dom.EventTarget] extends MouseEvent with TypedSyntheticEvent[T] 33 | trait TypedTouchEvent[T <: dom.EventTarget] extends TouchEvent with TypedSyntheticEvent[T] 34 | trait TypedUIEvent[T <: dom.EventTarget] extends UIEvent with TypedSyntheticEvent[T] 35 | trait TypedWheelEvent[T <: dom.EventTarget] extends WheelEvent with TypedSyntheticEvent[T] 36 | 37 | trait TypedInputFormEvent extends TypedFormEvent[HTMLInputElement] { 38 | val value: String = js.native 39 | } 40 | 41 | trait TypedCheckboxFormEvent extends TypedFormEvent[HTMLInputElement] { 42 | val checked: Boolean = js.native 43 | } 44 | 45 | trait TypedOptionFormEvent extends TypedFormEvent[HTMLInputElement] { 46 | val selected: Boolean = js.native 47 | } 48 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/export.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react 18 | 19 | import scala.scalajs.js 20 | import com.xored.scalajs.react.export._ 21 | 22 | object export extends ExportInstances with JSBridgeInstances { 23 | /** 24 | * Structure to describe export of value/method to react spec 25 | * 26 | * @param name name of property in react spec 27 | * @param value value 28 | */ 29 | case class Export(name: String, value: js.Any) 30 | } 31 | 32 | trait JSBridgeFrom[T] { 33 | def apply(value: js.Any): T 34 | } 35 | 36 | trait JSBridgeTo[T] { 37 | def apply(value: T): js.Any 38 | } 39 | 40 | trait JSBridge[T] extends JSBridgeFrom[T] with JSBridgeTo[T] 41 | 42 | /** 43 | * Instances to export functions with different arity 44 | */ 45 | trait ExportInstances { 46 | def export1[T, R](name: String, f: T => R)(implicit m: JSBridgeFrom[T], r: JSBridgeTo[R]): Export = { 47 | Export(name, js.ThisFunction.fromFunction1[js.Any, js.Any](self => r(f(m(self))))) 48 | } 49 | 50 | def export2[T, A, R](name: String, f: (T, A) => R)(implicit m: JSBridgeFrom[T], r: JSBridgeTo[R], ma: JSBridgeFrom[A]): Export = { 51 | Export(name, js.ThisFunction.fromFunction2[js.Any, js.Any, js.Any]((self, a) => r(f(m(self), ma(a))))) 52 | } 53 | 54 | def export3[T, A, B, R](name: String, f: (T, A, B) => R)(implicit m: JSBridgeFrom[T], r: JSBridgeTo[R], ma: JSBridgeFrom[A], mb: JSBridgeFrom[B]): Export = { 55 | Export(name, js.ThisFunction.fromFunction3[js.Any, js.Any, js.Any, js.Any]((self, a, b) => r(f(m(self), ma(a), mb(b))))) 56 | } 57 | } 58 | 59 | object ExportInstances extends ExportInstances 60 | 61 | /** 62 | * Default bridges 63 | */ 64 | trait JSBridgeInstances { 65 | implicit def reactAnyWriter[T <: js.Any]: JSBridgeTo[T] = new JSBridgeTo[T] { 66 | def apply(value: T) = value 67 | } 68 | 69 | implicit def reactUnitWriter: JSBridgeTo[Unit] = new JSBridgeTo[Unit] { 70 | def apply(value: Unit) = js.undefined 71 | } 72 | 73 | implicit def reactBooleanExport: JSBridgeTo[Boolean] = new JSBridgeTo[Boolean] { 74 | def apply(value: Boolean): js.Any = value 75 | } 76 | } 77 | 78 | object JSBridgeInstances extends JSBridgeInstances 79 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/internal/ReactDOMBuffer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.internal 18 | 19 | import scala.scalajs.js 20 | import com.xored.scalajs.react.ReactDOM 21 | 22 | class ReactDOMBuffer extends scala.collection.mutable.ArrayBuffer[ReactDOM] { 23 | 24 | private val EMPTY_STRING = js.Any.fromString("") 25 | 26 | def &+(o: Any): ReactDOMBuffer = { 27 | o match { 28 | case null | _: Unit | EMPTY_STRING => // ignore 29 | 30 | // just js.Object 31 | case n if n.getClass == null => super.+=(n.asInstanceOf[ReactDOM]) 32 | 33 | // in ScalaJS Long is iterable 34 | case x: scala.Long => super.+=(x.toString.asInstanceOf[ReactDOM]) 35 | 36 | case it: Iterator[_] => it foreach &+ 37 | case ns: Iterable[_] => this &+ ns.iterator 38 | case ns: Array[_] => this &+ ns.iterator 39 | case n => super.+=(n.asInstanceOf[ReactDOM]) 40 | } 41 | 42 | this 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/internal/ReactMetaData.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.internal 18 | 19 | import scala.scalajs.js 20 | 21 | trait ReactMetaData 22 | 23 | object ReactMetaData { 24 | 25 | def empty() = js.Dictionary.empty[js.Any] 26 | 27 | def apply(key: String, value: Option[(_) => _], md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 28 | value match { 29 | case Some(x) => apply(key, x, md) 30 | case None => md 31 | } 32 | } 33 | 34 | def apply(key: String, value: () => _, md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 35 | md.update(key, js.Any.fromFunction0(value)) 36 | md 37 | } 38 | 39 | def apply(key: String, value: ( _ ) => _, md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 40 | md.update(key, js.Any.fromFunction1(value)) 41 | md 42 | } 43 | 44 | def apply(key: String, value: (_, _) => _, md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 45 | md.update(key, js.Any.fromFunction2(value)) 46 | md 47 | } 48 | 49 | def apply(key: String, value: (_, _, _) => _, md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 50 | md.update(key, js.Any.fromFunction3(value)) 51 | md 52 | } 53 | 54 | def apply(key: String, value: String, md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 55 | md.update(key, value) 56 | md 57 | } 58 | 59 | def apply(key: String, value: Boolean, md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 60 | md.update(key, value) 61 | md 62 | } 63 | 64 | def apply(key: String, value: Double, md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 65 | md.update(key, value) 66 | md 67 | } 68 | 69 | def apply[T](key: String, value: Map[String, T], md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 70 | md.update(key, js.Dictionary.apply(value.toSeq: _*)) 71 | md 72 | } 73 | 74 | def apply[T](key: String, value: js.Dictionary[T], md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 75 | md.update(key, value) 76 | md 77 | } 78 | 79 | def apply(key: String, value: js.Dynamic, md: js.Dictionary[js.Any]): js.Dictionary[js.Any] = { 80 | md.update(key, value) 81 | md 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/internal/ScalaxImpl.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.internal 18 | 19 | import scala.reflect.macros.whitebox._ 20 | import scala.language.experimental.macros 21 | import scala.util.matching.Regex 22 | import scala.xml._ 23 | import com.xored.scalajs.react.ReactDOM 24 | import scala.collection.mutable 25 | import scala.annotation.StaticAnnotation 26 | 27 | object ScalaxImpl { 28 | 29 | def macroTransform(c: Context)(annottees: c.Expr[Any]*): c.Expr[ReactDOM] = { 30 | import c.universe._ 31 | 32 | val h = new ScalaxHelper[c.type](c) 33 | val newTrees = annottees.map(expr => h.transform(expr.tree)) 34 | 35 | val verbose = c.macroApplication match { 36 | case q"new $x($arg).macroTransform(..$y)" => c.eval(c.Expr[Boolean](arg)) 37 | case _ => false 38 | } 39 | 40 | if (verbose) { 41 | val msg = newTrees.mkString("\n\n\n") 42 | c.info(c.enclosingPosition, msg, force = true) 43 | } 44 | 45 | c.Expr[ReactDOM](q"..$newTrees") 46 | } 47 | 48 | def apply(c: Context)(elem: c.Expr[Elem]): c.Expr[ReactDOM] = { 49 | val h = new ScalaxHelper[c.type](c) 50 | val newTree = h.transform(elem.tree) 51 | 52 | val checked = c.typecheck(c.untypecheck(newTree)) 53 | c.Expr(checked) 54 | } 55 | } 56 | 57 | private class ScalaxHelper[C <: Context](val c: C) { 58 | 59 | import c.universe._ 60 | 61 | class HasType[T](implicit t: c.WeakTypeTag[T]) { 62 | 63 | def matches(tree: Tree) = { 64 | c.Expr(c.typecheck(tree, silent = true)).actualType =:= t.tpe || 65 | c.Expr(c.typecheck(tree, mode = c.TYPEmode, silent = true)).actualType =:= t.tpe 66 | } 67 | 68 | def unapply(tree: Tree): Option[Tree] = { 69 | if (matches(tree)) Some(tree) 70 | else None 71 | } 72 | } 73 | 74 | val hasNodeBufferType = new HasType[NodeBuffer] 75 | val scalaXmlNull = new HasType[scala.xml.Null.type] 76 | val hasMetaDataType = new HasType[MetaData] 77 | val hasElemType = new HasType[Elem] 78 | val hasTextType = new HasType[Text] 79 | val hasStringType = new HasType[String] 80 | val hasAttributeType = new HasType[UnprefixedAttribute] 81 | 82 | val ReactDOMBufferTpe = weakTypeOf[ReactDOMBuffer] 83 | val ReactDOMTpe = weakTypeOf[ReactDOM] 84 | 85 | val ReactDOMCompanionSym = symbolOf[ReactDOM].companion 86 | val ReactMetadataCompanionSym = symbolOf[ReactMetaData].companion 87 | 88 | def isWhitespace(str: String) = new Regex( """^\s*$""", "m").unapplySeq(str).isDefined 89 | 90 | def transform(tree: Tree): Tree = ElemTransformer.transform(tree) 91 | 92 | object ElemTransformer extends Transformer { 93 | override def transform(tree: Tree): Tree = tree match { 94 | 95 | case q"$mods val $name: $tpt = $tpe" if hasNodeBufferType.matches(tpe) => 96 | q"$mods val $name = new $ReactDOMBufferTpe()" 97 | 98 | case q"$mods var $name: $tpt = $tpe" if scalaXmlNull.matches(tpe) => 99 | q"$mods val $name = $ReactMetadataCompanionSym.empty()" 100 | 101 | case x@Select(_, _) if scalaXmlNull.matches(x) => 102 | q""" 103 | $ReactMetadataCompanionSym.empty() 104 | """ 105 | 106 | case q"new $tpe($prefixLit, $labelTree, $metadataIdent, $scope, $minimizeEmpty, ..$children)" if hasElemType.matches(tpe) => 107 | mkReactDOM(labelTree, transform(metadataIdent), children.map(transform)) 108 | 109 | case q"new $tpe($label, $node, ${md: TermName})" if hasAttributeType.matches(tpe) => 110 | val value = node match { 111 | case Apply(Select(New(hasTextType(_)), _), List(x)) => x 112 | case x@hasStringType(_) => x 113 | case x => x 114 | } 115 | 116 | q""" 117 | $ReactMetadataCompanionSym.apply($label, $value, $md) 118 | """ 119 | 120 | case q"new $tpe($lit)" if hasTextType.matches(tpe) => lit match { 121 | case Literal(Constant(const: String)) if isWhitespace(const) => q"""null""" 122 | case x => x 123 | } 124 | 125 | case x@TypeTree() if x.tpe != null && x.tpe =:= typeOf[Elem] => 126 | TypeTree(typeOf[ReactDOM]) 127 | 128 | case Ident(hasElemType(_)) => q"$ReactDOMTpe" 129 | 130 | case _ => super.transform(tree) 131 | } 132 | } 133 | 134 | def mkReactDOM(labelTree: Tree, metadata: Tree, children: List[Tree]): Tree = labelTree match { 135 | case Literal(Constant(label: String)) => 136 | q""" 137 | $ReactDOMCompanionSym.selectDynamic($labelTree, $metadata, ..$children) 138 | """ 139 | 140 | case x => 141 | c.error(x.pos, s"Can't make ReactDOM by ${showRaw(x)}") 142 | x 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/interop/ReactComponent.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.interop 18 | 19 | import scala.scalajs.js 20 | import com.xored.scalajs.react.ReactDOM 21 | 22 | trait ReactComponentClass[State, Props] extends js.Function1[js.Dictionary[Any], ReactComponent[State, Props]] 23 | 24 | trait ReactComponent[State, Props] extends ReactDOM { 25 | def setState(state: State): Unit = js.native 26 | } 27 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/interop/ReactJS.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.interop 18 | 19 | import scala.scalajs.js 20 | import js.annotation.JSName 21 | import org.scalajs.dom 22 | import com.xored.scalajs.react.ReactDOM 23 | 24 | /** 25 | * Interop with react.js 26 | */ 27 | @JSName("React") 28 | object ReactJS extends js.Object { 29 | def createClass[State, Props, T](spec: js.Dictionary[T]): ReactComponentClass[State, Props] = js.native 30 | 31 | def renderComponent[C <: ReactDOM](component: C, container: dom.HTMLElement): C = js.native 32 | 33 | def renderComponentToString(dom: ReactDOM): String = js.native 34 | 35 | def renderComponentToStaticMarkup(dom: ReactDOM): String = js.native 36 | 37 | def DOM: js.Dynamic = js.native 38 | } 39 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/scalax.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react 18 | 19 | import scala.xml.Elem 20 | import scala.language.experimental.macros 21 | import scala.annotation.StaticAnnotation 22 | 23 | import com.xored.scalajs.react.internal.ScalaxImpl 24 | 25 | object scalax { 26 | def apply(elem: Elem): ReactDOM = macro ScalaxImpl.apply 27 | } 28 | 29 | class scalax(verbose: Boolean = false) extends StaticAnnotation { 30 | def macroTransform(annottees: Any*): ReactDOM = macro ScalaxImpl.macroTransform 31 | } 32 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/util/ClassName.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.util 18 | 19 | object ClassName { 20 | def apply(classNameMap: (String, Boolean)*) = { 21 | for { 22 | (className, show) <- classNameMap if show 23 | } yield className 24 | }.mkString(" ") 25 | } 26 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/util/TypedEventListeners.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.util 18 | 19 | import com.xored.scalajs.react.event._ 20 | 21 | import scala.scalajs.js 22 | import org.scalajs.dom._ 23 | 24 | trait TypedEventListeners { 25 | 26 | trait ElementEventListeners[T <: EventTarget] { 27 | def onEvent(e: TypedSyntheticEvent[T] => Unit): js.Function1[TypedSyntheticEvent[T], Unit] = js.Any.fromFunction1(e) 28 | 29 | def onCopy(e: TypedClipboardEvent[T] => Unit): js.Function1[TypedClipboardEvent[T], Unit] = js.Any.fromFunction1(e) 30 | def onCut(e: TypedClipboardEvent[T] => Unit): js.Function1[TypedClipboardEvent[T], Unit] = js.Any.fromFunction1(e) 31 | def onPaste(e: TypedClipboardEvent[T] => Unit): js.Function1[TypedClipboardEvent[T], Unit] = js.Any.fromFunction1(e) 32 | 33 | def onKeyDown(e: TypedKeyboardEvent[T] => Unit): js.Function1[TypedKeyboardEvent[T], Unit] = js.Any.fromFunction1(e) 34 | def onKeyPress(e: TypedKeyboardEvent[T] => Unit): js.Function1[TypedKeyboardEvent[T], Unit] = js.Any.fromFunction1(e) 35 | def onKeyUp(e: TypedKeyboardEvent[T] => Unit): js.Function1[TypedKeyboardEvent[T], Unit] = js.Any.fromFunction1(e) 36 | 37 | def onFocus(e: TypedFocusEvent[T] => Unit): js.Function1[TypedFocusEvent[T], Unit] = js.Any.fromFunction1(e) 38 | def onBlur(e: TypedFocusEvent[T] => Unit): js.Function1[TypedFocusEvent[T], Unit] = js.Any.fromFunction1(e) 39 | 40 | def onClick(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 41 | def onDoubleClick(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 42 | def onDrag(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 43 | def onDragEnd(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 44 | def onDragEnter(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 45 | def onDragExit(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 46 | def onDragLeave(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 47 | def onDragOver(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 48 | def onDragStart(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 49 | def onDrop(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 50 | def onMouseDown(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 51 | def onMouseEnter(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 52 | def onMouseLeave(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 53 | def onMouseMove(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 54 | def onMouseOut(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 55 | def onMouseOver(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 56 | def onMouseUp(e: TypedMouseEvent[T] => Unit): js.Function1[TypedMouseEvent[T], Unit] = js.Any.fromFunction1(e) 57 | 58 | def onTouchCancel(e: TypedTouchEvent[T] => Unit): js.Function1[TypedTouchEvent[T], Unit] = js.Any.fromFunction1(e) 59 | def onTouchEnd(e: TypedTouchEvent[T] => Unit): js.Function1[TypedTouchEvent[T], Unit] = js.Any.fromFunction1(e) 60 | def onTouchMove(e: TypedTouchEvent[T] => Unit): js.Function1[TypedTouchEvent[T], Unit] = js.Any.fromFunction1(e) 61 | def onTouchStart(e: TypedTouchEvent[T] => Unit): js.Function1[TypedTouchEvent[T], Unit] = js.Any.fromFunction1(e) 62 | 63 | def onScroll(e: TypedUIEvent[T] => Unit): js.Function1[TypedUIEvent[T], Unit] = js.Any.fromFunction1(e) 64 | 65 | def onWheel(e: TypedWheelEvent[T] => Unit): js.Function1[TypedWheelEvent[T], Unit] = js.Any.fromFunction1(e) 66 | } 67 | 68 | object element extends ElementEventListeners[HTMLElement] 69 | 70 | object input extends ElementEventListeners[HTMLInputElement] { 71 | def onChange(e: TypedInputFormEvent => Unit): js.Function1[TypedInputFormEvent, Unit] = js.Any.fromFunction1(e) 72 | def onInput(e: TypedInputFormEvent => Unit): js.Function1[TypedInputFormEvent, Unit] = js.Any.fromFunction1(e) 73 | def onSubmit(e: TypedInputFormEvent => Unit): js.Function1[TypedInputFormEvent, Unit] = js.Any.fromFunction1(e) 74 | } 75 | 76 | object textarea extends ElementEventListeners[HTMLInputElement] { 77 | def onChange(e: TypedInputFormEvent => Unit): js.Function1[TypedInputFormEvent, Unit] = js.Any.fromFunction1(e) 78 | def onInput(e: TypedInputFormEvent => Unit): js.Function1[TypedInputFormEvent, Unit] = js.Any.fromFunction1(e) 79 | def onSubmit(e: TypedInputFormEvent => Unit): js.Function1[TypedInputFormEvent, Unit] = js.Any.fromFunction1(e) 80 | } 81 | 82 | object checkbox extends ElementEventListeners[HTMLInputElement] { 83 | def onChange(e: TypedCheckboxFormEvent => Unit): js.Function1[TypedCheckboxFormEvent, Unit] = js.Any.fromFunction1(e) 84 | def onInput(e: TypedCheckboxFormEvent => Unit): js.Function1[TypedCheckboxFormEvent, Unit] = js.Any.fromFunction1(e) 85 | def onSubmit(e: TypedCheckboxFormEvent => Unit): js.Function1[TypedCheckboxFormEvent, Unit] = js.Any.fromFunction1(e) 86 | } 87 | 88 | object option extends ElementEventListeners[HTMLInputElement] { 89 | def onChange(e: TypedOptionFormEvent => Unit): js.Function1[TypedOptionFormEvent, Unit] = js.Any.fromFunction1(e) 90 | def onInput(e: TypedOptionFormEvent => Unit): js.Function1[TypedOptionFormEvent, Unit] = js.Any.fromFunction1(e) 91 | def onSubmit(e: TypedOptionFormEvent => Unit): js.Function1[TypedOptionFormEvent, Unit] = js.Any.fromFunction1(e) 92 | } 93 | 94 | object form { 95 | def onChange(e: TypedFormEvent[HTMLFormElement] => Unit): js.Function1[TypedFormEvent[HTMLFormElement], Unit] = js.Any.fromFunction1(e) 96 | def onInput(e: TypedFormEvent[HTMLFormElement] => Unit): js.Function1[TypedFormEvent[HTMLFormElement], Unit] = js.Any.fromFunction1(e) 97 | def onSubmit(e: TypedFormEvent[HTMLFormElement] => Unit): js.Function1[TypedFormEvent[HTMLFormElement], Unit] = js.Any.fromFunction1(e) 98 | } 99 | 100 | object button extends ElementEventListeners[HTMLInputElement] 101 | 102 | } 103 | 104 | object TypedEventListeners extends TypedEventListeners 105 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/util/UUID.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react.util 18 | 19 | object UUID { 20 | @deprecated("use java.util.UUID.randomUUID()", "0.4.0") 21 | def apply(): String = java.util.UUID.randomUUID().toString() 22 | } 23 | -------------------------------------------------------------------------------- /scalajs-react/src/main/scala/com/xored/scalajs/react/util/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Xored Software, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.xored.scalajs.react 18 | 19 | import com.xored.scalajs.react.event.TypedSyntheticEvent 20 | import org.scalajs.dom 21 | 22 | import scala.scalajs.js 23 | 24 | package object util { 25 | 26 | @deprecated("use TypedSyntheticEvent", "0.3.2") 27 | type TypedEvent[+T <: dom.EventTarget] = TypedSyntheticEvent[T] 28 | 29 | implicit class RichDictionary[T](val dict: js.Dictionary[T]) { 30 | def += (kv: (String, T)): js.Dictionary[T] = { 31 | dict.update(kv._1, kv._2) 32 | dict 33 | } 34 | } 35 | } 36 | --------------------------------------------------------------------------------