├── .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 |
4 |
5 |
6 |
7 |
8 |
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 | [](https://travis-ci.org/xored/scala-js-react)
4 | [](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 | Say
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 | {self.props.label}
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 | Small ,
64 | Button("small", "Small Button"),
65 | Button("small", "Small Button", primary = true)
66 | )
67 | }
68 | {
69 | ButtonToolbar(
70 | Large ,
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 | Focus
61 |
62 |
63 | {
64 | ContentEditable(ContentEditable.Props(), ref = "input-2")
65 | }
66 | Focus
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 | Say
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 |
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 | {self.props.todo.title}
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 Submit " - {
43 | @scalax val reactDom = Panel(Panel.Props(
44 | HelloWorld(HelloWorld.Props()),
45 | Submit
46 | ))
47 | val dom = renderToString(reactDom)
48 |
49 | assert(dom == "
Hello World! Submit ")
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 |
--------------------------------------------------------------------------------