Compare commits

..

198 Commits

Author SHA1 Message Date
Ben Alpert
d5b409002b Fix typo-ed "of"
(cherry picked from commit 65b2232f34)
2014-07-13 13:11:00 -07:00
Ben Alpert
9d4baa12a6 Make code in headers not giant
(cherry picked from commit 4a66260b2c)
2014-07-13 13:07:49 -07:00
Paul O’Shannessy
0606de0db7 0.11-rc blog post
(cherry picked from commit b8cc3f510a)
2014-07-13 13:07:49 -07:00
Paul O’Shannessy
13817753c6 Consolas > Courier New for monospace docs font [skip ci]
(cherry picked from commit ab690586d3)
2014-07-13 13:07:49 -07:00
Jonas Gebhardt
3f37e8e4ab fix link in community roundup #18
(cherry picked from commit a8e273f844)
2014-06-30 10:49:03 -07:00
Cheng Lou
fe1b554d9d [Docs] Fix tutorial line highlights, revert ajax in cb
(cherry picked from commit 66d6e3f391)
2014-06-30 01:08:07 -07:00
Ben Alpert
0ecf9f0599 Revert "Add --harmony option to live JSX compiler page"
This reverts commit 661bafb059.

(Depends on a newer version of JSXTransformer than the site currently has.)
2014-06-30 01:06:52 -07:00
Cheng Lou
acba66fe2e [Docs] Put tutorial up-to-date with the code
(cherry picked from commit 58a463f01c)

Conflicts:
	docs/docs/tutorial.md
2014-06-29 22:03:09 -07:00
Ben Alpert
661bafb059 Add --harmony option to live JSX compiler page
(cherry picked from commit ba67bf1b0d)
2014-06-29 22:00:12 -07:00
Cheng Lou
8f0c1ce7b1 [Blog] Update round-up #19 to include more credit
(cherry picked from commit 5ba3911929)
2014-06-27 10:05:57 -07:00
Ben Alpert
3ca2ab8960 Fix typo in blog post 2014-06-26 15:19:44 -07:00
Pieter Vanderwerff
06844c27a8 Change community roundup 19's React Bootstrap author to @stevoland
(cherry picked from commit deb47d5ecc)
2014-06-26 15:15:04 -07:00
Cheng Lou
b2ea0d0e29 {Blog] Community Round-up #19
(cherry picked from commit dbf41a55a4)
2014-06-26 14:03:01 -07:00
Paul O’Shannessy
90682ad1ee 2 docs fixes.
Fix 404 and extra slash in url

(cherry picked from commit 56263c44e8)
2014-06-24 15:13:25 -07:00
Paul O’Shannessy
617de48e53 Simple custom 404 page.
Github doesn't let us handle 404s within a project repository, so we
handle them at the organization level. In order to handle graceully for
specific projects, we're setting up redirects to projects' own 404.html.

(cherry picked from commit 2496757364)
2014-06-24 14:24:22 -07:00
Stefan Dombrowski
fd4143a198 Fix example for animation
* Highlight the correct lines
* Add missing text to button
* Remove unnecessary div

(cherry picked from commit c91f95a092)
2014-06-24 14:24:22 -07:00
Ben Alpert
224b6e5a6a Clarify refs and setState callback documentation
(cherry picked from commit 121b290899)
2014-06-24 14:24:22 -07:00
Ben Alpert
11707179da Fix tagtree.tv link
Fixes #1697.
(cherry picked from commit 00037b3ec2)
2014-06-24 14:24:22 -07:00
Kyle Mathews
bfecabf71a typo
(cherry picked from commit cfdc884582)
2014-06-24 14:24:22 -07:00
Ben Alpert
bcaebecce2 [docs] Point to renderComponent over setProps
(cherry picked from commit 2f61996ec3)
2014-06-24 14:24:22 -07:00
Ben Alpert
6055468d1c Be more specific with createClass, renderComponent
Closes #371.

(cherry picked from commit f1a5a4c58e)
2014-06-24 14:24:22 -07:00
Harshad Sabne
7ea026e19a Update 2014-03-21-react-v0.10.md
Fix link to React v0.10 RC
(cherry picked from commit 7172b1d5da)
2014-06-24 14:24:22 -07:00
Devon Blandin
f7e4667457 Update link to @petehunt's react-server-rendering GitHub repo
(cherry picked from commit d8a2f12498)
2014-06-24 14:24:22 -07:00
Ben Alpert
e88c0b1b1f Add callback to setProps docs
(cherry picked from commit 2d66fc4518)
2014-06-24 14:24:22 -07:00
enome
950ecd84ff Typo: ClosureScript -> ClojureScript 2014-05-29 13:22:04 -07:00
Cheng Lou
66b027233c [Blog] One-year anniversary post 2014-05-29 11:42:32 -07:00
Ben Alpert
c28cd1ae1a Update Simulate docs to reflect reality
cf. #1532, #1445.
2014-05-14 21:39:33 -07:00
Ben Alpert
321643a858 Add missing emitChange() to Flux docs 2014-05-10 17:50:46 -03:00
Yuriy Dybskiy
c29f1823a1 Update flux-todo-list.md
Fix broken link to todomvc-flux repo

@spicyj cla signed
2014-05-09 15:42:33 -03:00
fisherwebdev
424ebb5436 update Flux tutorial link 2014-05-08 20:03:36 -07:00
Vjeux
94c6396853 Flux blog post and tutorial 2014-05-09 01:32:19 +02:00
Paul O’Shannessy
f832b9ce59 Move tagtree image to docs/
Missed this in #1470
2014-05-08 11:53:22 -07:00
Paul O’Shannessy
d6c5058193 Title case on videos page 2014-05-08 11:47:25 -07:00
Rajiv Tirumalareddy
59c495511a Fixed incorrect usage of github api in example
more: https://developer.github.com/v3/gists/#detailed-gist-representation
2014-05-08 11:47:25 -07:00
Niklas Boström
ba717cfc62 Embed video from F8
Edited in githubs inline editor. Not tested!
2014-05-08 11:47:25 -07:00
hendrik swanepoel
9af5b6b1c3 Fixed a couple of issues with link to tagtree.tv video 2014-05-08 11:47:25 -07:00
hendrik swanepoel
35321a8908 link to tagtree 'Thinking in React' video 2014-05-08 11:47:25 -07:00
dschafer
d90046a104 Update jsfiddles in thinking-in-react to remove onSubmit from forms 2014-05-08 11:47:24 -07:00
Ben Alpert
d28467796c Move tooling info to Complementary Tools wiki
I moved the info that was here to the wiki page: https://github.com/facebook/react/wiki/Complementary-Tools; it doesn't need to live in the website any more.
2014-05-08 11:47:24 -07:00
Pete Hunt
cff284f291 Update videos.md 2014-05-08 11:47:24 -07:00
Daniel Gasienica
7818b9f81f Match setTimeout delay with documentation 2014-05-08 11:47:24 -07:00
Ben Alpert
76b2e87173 Fix preposition capitalization 2014-05-08 11:47:24 -07:00
Ben Alpert
a8aa901e1b Update refs example code to use onChange
Fixes #1408.

Test Plan: Copy each code snippet into jsbin; type in the text box successfully; click the button and see the input clear (and in the second example, get focused).
2014-05-08 11:47:24 -07:00
georgesisco
f45ab7c7f4 Update tutorial.md 2014-05-08 11:47:24 -07:00
George A Sisco III
383e385d51 Carry ajax error checking from step #13 forward to other ajax steps
The following steps also have an ajax function, but the 'error:' param
is gone after #13:
#14
#17
#19
#20
This may be superfluous, but it helped me find an error with something I
was doing - Namely, in my .json file, I had single line javascript
comments ("//") that I copied from the tutorial. I couldn't find the
issue on later steps, but was able to see my issue when the error
handler complained about an unexpected "/" in my file in step #13.
2014-05-08 11:47:24 -07:00
Cheng Lou
bb3916448e docs remove comment section 2014-05-08 11:47:24 -07:00
Marcin Kwiatkowski
5936173626 fix typo in 09.4-test-utils.md 2014-05-08 11:47:24 -07:00
Felix Kling
1400085fe3 Update 05-reusable-components.md
`any` also seems to accept primitive values, not only objects. And since we already have `React.PropType.object`, the description was confusing.
2014-05-08 11:47:24 -07:00
Christopher Chedeau
6d2dd794ee Document multiple ways to insert comments in JSX 2014-05-08 11:47:24 -07:00
Karl Mikkelsen
0247d9d625 Link to Events from Forms
closes #1372
2014-04-07 17:51:36 -07:00
Paul O’Shannessy
d160715b19 Move complementary tools to wiki for easier management. 2014-04-07 17:51:36 -07:00
Adam Krebs
5f6d46f696 Serve unminified react on docs site. Fixes #1359 2014-04-07 17:51:36 -07:00
Daniel Lo Nigro
e197768058 ReactJS.NET blog post
Closes #1354.
2014-04-04 13:23:45 -07:00
Ben Alpert
f893442591 Fix appearance of code blocks in lists 2014-04-04 12:53:36 -07:00
Ben Alpert
c14a26e573 Add acknowledgement to Christopher Aue 2014-04-03 07:35:23 -07:00
Paul O’Shannessy
6210dd3325 Fix link to pit of success 2014-03-28 14:42:55 -07:00
Josh Duck
1acdd51af3 Formatting
An extra "`" snuck in there.
2014-03-28 14:32:42 -07:00
Paul O’Shannessy
bf6fe2c194 [blog] The Road to 1.0 2014-03-28 13:59:43 -07:00
Paul O’Shannessy
e65085846f 0.10 release materials 2014-03-21 14:31:35 -07:00
Paul O’Shannessy
dedf0c20da 0.10.0 2014-03-21 13:34:02 -07:00
Paul O’Shannessy
12bdb8d24c shrinkwrap deps 2014-03-21 13:25:29 -07:00
Paul O’Shannessy
3b2d176f59 Update AUTHORS for 0.10 2014-03-21 13:21:20 -07:00
Pete Hunt
68da0eccbe Merge pull request #1282 from jeffbski/addCodewinds004Videos
add CodeWinds episode 4 w/Pete Hunt to videos
2014-03-21 12:54:48 +00:00
Jeff Barczewski
593889b87a add CodeWinds episode 4 w/Pete Hunt to videos
Added to videos.md
2014-03-21 07:41:06 -05:00
Paul O’Shannessy
732f4717a6 Merge pull request #1186 from chenglou/doc-toString-opt
[Docs] document optional param for renderComponentToString
2014-03-20 17:28:51 -07:00
Ben Newman
7807413256 Upgrade Commoner to v0.9.2 to silence deprecation warnings.
Closes #1278.
2014-03-20 14:43:47 -04:00
Ben Alpert
ec893833ee Merge pull request #1273 from wincent/typo-fix-02
Fix a minor typo in the `update()` docs
2014-03-19 15:37:30 -07:00
Greg Hurrell
5aaf4dbae9 Fix a minor typo in the update() docs 2014-03-19 15:33:43 -07:00
Paul O’Shannessy
ecea2918cf 0.10rc blog post 2014-03-18 22:15:16 -07:00
Paul O’Shannessy
7f24943e5a update version for 0.10rc 2014-03-18 22:09:32 -07:00
Paul O’Shannessy
0491d30e7c re-enable moved root warning 2014-03-18 22:03:25 -07:00
Paul O’Shannessy
55b0222596 Upgrade browserify 2014-03-18 21:33:39 -07:00
Paul O’Shannessy
b95fbbe4a2 Silence tests unsupported in PhantomJS.
These tests can still be run in the browser using `grunt test --debug`.

This is a repeat of 42f8d155f8. For posterity, we
do this because Phantom has a problem with Object.freeze and the test runner
can't do __DEV__ right (because we get rid of that in the build step).
2014-03-18 17:48:16 -07:00
Kunal Mehta
e505e47e01 Separate immutable project
This moves Immutable and Immutable object into the new `immutable` project.
2014-03-18 15:03:26 -07:00
Sebastian Markbage
e0c487649d Logging use of objects as maps for children
Let's start logging objects as maps for children. We may want to deprecate this
and replace it with ImmutableMap and Map data structures instead.

This should ideally be logged in the recursive function but since that loses the
scope of where these children are passed it's easier to start tracking them
here to get an idea of how frequently and where it's used.
2014-03-18 15:01:51 -07:00
Andrew Zich
22057ef61c don't try to use Object.prototype methods as transfer strategies in ReactPropTransferer.mergeProps
While looking up a detail of how `transferPropsTo()` works I noticed that we never check `TransferStrategies.hasOwnProperty(thisKey)` when merging props, just `newProps.hasOwnProperty(thisKey)` and a truthy test for `TransferStrategies[thisKey]`. This means that if our `newProps` has keys like `toString`, `valueOf`, or `constructor` etc. set, we will pull these functions off `TransferStrategies` and invoke them when merging props. In most cases this will just result in a failure to merge and some code without side effects being run but in the case of `valueOf` it will actually generate an exception.
2014-03-18 15:01:46 -07:00
Josh Duck
0278f01d95 Replace function expression with for loop
isCustomAttribute used an anonymous function in place of a for loop.
Swap it out so we're not allocating unnecessarily.
2014-03-18 14:57:08 -07:00
Kunal Mehta
0cec4af8d7 Sync latest Immutable changes 2014-03-18 14:57:04 -07:00
Paul O’Shannessy
8d495f3b6e Revert "Merge pull request #1234 from RReverser/pure-cjs"
This reverts commit 7987e6a51d, reversing
changes made to d88d479685.
2014-03-18 11:25:21 -07:00
Ben Newman
7987e6a51d Merge pull request #1234 from RReverser/pure-cjs
Switched from browserify to pure-cjs bundler.
2014-03-18 12:04:58 -04:00
Paul O’Shannessy
d88d479685 Merge pull request #1193 from spicyj/jsx-polygon
Add polygon tag to transform
2014-03-17 10:07:23 -07:00
Ingvar Stepanyan
3f3187c14f Renamed commojs task to bundle, added failing build if bundler failed. 2014-03-17 16:53:02 +02:00
Ben Alpert
c1443e92e6 Merge pull request #1226 from LilyJ/master
included link to React devtools in tooling-integration. fixes #791
2014-03-16 22:42:14 -07:00
Paul O’Shannessy
52e8f3fdb0 Merge pull request #1248 from asolove/svg-text-anchor
Add svg text-anchor attribute
2014-03-16 22:33:00 -07:00
Paul O’Shannessy
cdf4f07a15 Merge pull request #1229 from spicyj/no-jsx
Avoid JSX in ReactCSSTransitionGroup code
2014-03-16 22:31:24 -07:00
Paul O’Shannessy
6ff116e34b Merge pull request #1262 from vjeux/harmony-react-to
Add support for {harmony: true} to react-tools
2014-03-16 22:30:30 -07:00
Pete Hunt
d889a01caf Fix ref behavior for remounting
I'm thinking that setting up `this.refs` in `mountComponent` is better than `construct`. Followed the same pattern as `ReactComponent.Mixin` and nulling out
the value in `construct` and setting it to its initial value in `mountComponent`.
2014-03-16 22:01:54 -07:00
Pete Hunt
04f9887f0e More actionable error message for <tbody> and nested <p>
This bites people all of the time. Until we have a better solution, let's just make the error message more actionable (most people don't know how the DOM
gets unexpectedly mutated).
2014-03-16 22:01:34 -07:00
Sebastian Markbage
9b427a322f Fix some invalid uses of instances
Breaking this out of the other code mod.
2014-03-16 22:01:30 -07:00
Sebastian Markbage
7bbdcdba96 Reassign variable of rendered component
The component that gets passed into renderComponent isn't guaranteed to be the
instance that gets mounted. We want to clone the instance.

Unit tests need to reason about the mounted instance. The first code mod changes:

  ReactTestUtils.renderIntoDocument(<identifier>)

into

  <identifier> = ReactTestUtils.renderIntoDocument(<identifier>)

Using this scripts:

  scripts/bin/codemod -m -d ~/www --extensions js \
   '^(\s*)ReactTestUtils\.renderIntoDocument\(\s*([$a-zA-Z0-9_]+)\s*\)' \
   '\1\2 = ReactTestUtils.renderIntoDocument(\2)'

In the second case I do the same for React.renderComponent. However, there are
alot more unnecessary matches so I only codemod if the same identifier occurs
later in the file.

  scripts/bin/codemod -m -d ~/www --extensions js \
   '^(\s*)React.renderComponent\(\s*([$a-zA-Z0-9_]+)\s*?,(.*?\n?.*?\s\2\b)' \
   '\1\2 = React.renderComponent(\2,\3'

And one more for ReactMount.renderComponent used by internals.

  scripts/bin/codemod -m -d ~/www --extensions js \
   '^(\s*)ReactMount.renderComponent\(\s*([$a-zA-Z0-9_]+)\s*?,(.*?\n?.*?\s\2\b)' \
   '\1\2 = ReactMount.renderComponent(\2,\3'

This still matches many unnecessary cases where the second occurance of the
identifier is a redeclaration or comment. But this code mod doesn't hurt in
those cases.

Finally I have to do the same for:

  this.<identifier> = React.renderComponent(this.<identifier>,

This is a common pattern for production code but not tests. Some of these call
sites will likely break when we move to true descriptors.

  scripts/bin/codemod -m -d ~/www --extensions js \
   '^(\s*)React.renderComponent\((\s*)this\.([$a-zA-Z0-9\_\.]+)\s*?,' \
   '\1this.\3 = React.renderComponent(\2this.\3,'
2014-03-16 22:01:09 -07:00
Christopher Chedeau
5106b793f7 Add support for {harmony: true} to react-tools
```
require('react-tools').transform(code, {harmony: true});
```

now enables all the harmony es6 transforms that are supported.

This is modeled after https://github.com/facebook/react/blob/master/bin/jsx#L17-L23
2014-03-16 15:38:40 -07:00
Jonas Gebhardt
83e4ef16e6 Community Round-up #18 2014-03-14 16:56:12 -07:00
Paul O’Shannessy
308c9a0752 Merge pull request #1181 from lrowe/patch-5
omitClose for all void elements
2014-03-12 16:37:52 -07:00
Adam Solove
6a01752f3d Add svg text-anchor attribute. 2014-03-12 10:07:05 -04:00
Ben Alpert
6fd53815cd Merge pull request #1246 from ericflo/patch-1
Remove Shirtstarter as an example application.
2014-03-11 15:48:20 -07:00
Eric Florenzano
3efa02da91 Remove Shirtstarter as an example application.
We have unexpectedly had to shut Shirtstarter down, so it won't serve as a good React.js example any more unfortunately -- sorry for the documentation churn.
2014-03-11 15:45:14 -07:00
Jeff Morrison
d9af091244 Merge pull request #1242 from syranide/jsxcomfix
Unbreak JSX comment comma fix
2014-03-11 10:13:24 -07:00
Ingvar Stepanyan
25773ed1b3 CommonJS builder config to Grunt style for easier handling and readability. 2014-03-11 13:40:38 +00:00
Ingvar Stepanyan
9e224e615f Renamed browserify tasks/configs to cjs, updated pure-cjs to 1.9.0 for better build speed. 2014-03-11 13:26:36 +02:00
Cheng Lou
3ce2cd04e2 Merge pull request #1243 from jeffcarp/master
Remove unused variable in ballmer-peak
2014-03-10 21:29:35 -07:00
Jeff Carpenter
652f5aea28 Remove unused variable 2014-03-10 19:48:13 -07:00
Andreas Svensson
280ff2e5a7 Unbreak JSX comment comma fix 2014-03-11 00:31:59 +01:00
Pete Hunt
5ede7fb619 ReactDOMComponent optimization
Slight optimization to not do unneeded reconciles of DOM components
2014-03-10 15:32:20 -07:00
Sebastian Markbage
3ea3274ca4 Clone on mount
This is the first step towards descriptors. This will start cloning the
component when it's mounted instead of mounting the first instance.

This avoids an issue where a reference to the first instance can hang around
in props. Since a mounted component gets mutated, the descriptor changes.

We don't need to clone the props object itself. Mutating the shallow props
object of a child that's passed into you is already flawed. Those cases need to
use cloneWithProps. A props object is considered shallow frozen after it leaves
the render it was created in.
2014-03-10 15:28:54 -07:00
Paul O’Shannessy
2ca810fbf3 Merge pull request #1225 from spicyj/cprop
Fix removing DOM property with mapped name
2014-03-10 11:29:10 -07:00
Pete Hunt
d9dd9d5cb3 Merge pull request #1235 from fxbois/master
Update 08-tooling-integration.md
2014-03-08 20:15:45 -08:00
fxbois
9ffd70c688 Update 08-tooling-integration.md
emacs compatibility
2014-03-08 18:12:05 +01:00
Ingvar Stepanyan
3171436d97 Switched from browserify to pure-cjs bundler.
Optimizations and fix for JSXTransformer build.
Dropped dependency on emulation of Node.js native modules.
Added deamdify step for JSXTransformer build.
2014-03-08 16:33:58 +02:00
Jeff Morrison
9ce7ecc3d9 Merge pull request #1219 from syranide/jsxempty
Fix empty JSX expressions sometimes emitting erroneous commas
2014-03-07 21:23:55 -08:00
Paul O’Shannessy
9f9c8bcebf Merge pull request #1228 from spicyj/grunt-debug
Bring back `grunt test --debug`
2014-03-07 11:18:29 -08:00
Ben Alpert
ec54dcbd8f Avoid JSX in ReactCSSTransitionGroup code
Supposedly we want these to be plain JS.
2014-03-06 22:16:25 -08:00
Ben Alpert
9766ed5797 Bring back grunt test --debug 2014-03-06 22:11:55 -08:00
Ben Alpert
1d209248ef Remove obsolete __VERSION__ references 2014-03-06 20:33:06 -08:00
Ben Alpert
21e06196cd Fix removing DOM property with mapped name 2014-03-06 17:50:01 -08:00
Lily
4b56947560 included link to React devtools in tooling-integration. fixes #791 2014-03-06 13:00:48 -08:00
Paul O’Shannessy
554b677e60 Merge pull request #1180 from evanc/master
Test that Node is a function before using instanceof on it
2014-03-06 11:02:28 -08:00
Andreas Svensson
aa70419f9d Fix empty JSX expressions sometimes emitting erroneous commas 2014-03-06 13:01:03 +01:00
Cheng Lou
af1b63456e Merge pull request #1222 from spicyj/npm-dl
[docs] Add more words to downloads page
2014-03-05 20:23:10 -08:00
Ben Alpert
ac3051530a [docs] Add more words to downloads page 2014-03-05 20:16:50 -08:00
Paul O’Shannessy
c5f56f318a Merge pull request #1208 from passy/patch-3
Fix docstring typo for pure render mixin
2014-03-05 19:13:22 -08:00
Paul O’Shannessy
be2c185888 Merge pull request #1211 from spicyj/freeze
Add Object.freeze to polyfill list
2014-03-05 12:18:20 -08:00
Paul O’Shannessy
7d0e6c6c0b Merge pull request #1192 from spicyj/srcset
Add srcSet attribute
2014-03-04 21:39:46 -08:00
Jing Chen
13aa8d37e6 Allow falsy values in statics
We found that the component would break if we set any statics with the
value 0. It turns out @chenglou already ran into this with
d71736b3ed but the statics code was copied
earlier, and still has this falsy check. Made the same change, updated
the unittest.
2014-03-04 18:15:49 -08:00
Kunal Mehta
06f762da77 Add Immutable
As titled. First of 3 diffs to add Immutable, ImmutableMap, and ImmutableObject. No logic changes.
2014-03-04 18:14:47 -08:00
Kunal Mehta
4ad320dd13 LegacyImmutableObject module
This copies the old implementation of ImmutableObject into LegacyImmutableObject.
2014-03-04 18:14:12 -08:00
Ben Newman
521201f121 Merge pull request #1215 from benjamn/fix-cloneWithProps-test
Fix stale usage of emptyObject in cloneWithProps-test.
2014-03-04 13:27:59 -05:00
Ben Newman
41d30bb7de Re-require cloneWithProps as well, for consistency.
And don't mock `emptyObject`, since that might actually replace it with a
mutable/non-frozen object.
2014-03-04 13:12:59 -05:00
Ben Newman
9c87aef67f Fix stale usage of emptyObject in cloneWithProps-test.
After we run `require('mock-modules').dumpCache()`, the object exported by
the `emptyObject` module will no longer be identical to previously
exported objects, so tests like `expect(component.refs).toBe(emptyObject)`
will fail.

Note that this behavior only manifests itself in tests, because of course
we do not call `dumpCache` in production code.

We could consider storing the `emptyObject` globally to thwart the effects
of `dumpCache`, but it's more idiomatic simply to re-`require` the latest
version of `emptyObject`.
2014-03-04 12:59:55 -05:00
Ben Alpert
ec8b0d7fbf Add srcSet attribute
Chrome beta supports this now: http://blog.chromium.org/2014/02/chrome-34-responsive-images-and_9316.html.
2014-03-03 17:12:00 -08:00
Ben Alpert
e954a1c0d9 Add Object.freeze to polyfill list 2014-03-03 15:55:19 -08:00
Paul O’Shannessy
1d27770b40 Fix verion-check to look in right place for React.js 2014-03-03 15:35:10 -08:00
Paul O’Shannessy
4c4446d283 Sync more vendored modules 2014-03-03 15:34:14 -08:00
Pete Hunt
a8fc3b940d Move UI-thread-only browser modules to browser/ui/
This also deletes an unused module.
2014-03-03 15:07:11 -08:00
Pete Hunt
6666538316 Unbreak refs
If no refs are rendered, `this.refs` is undefined. This is bad since it deopts & is hard to look for. Instead we should make `this.refs` an immutable empty object.
2014-03-03 15:06:57 -08:00
Sebastian Markbage
620c1bc2ff Add more owner context to monitoring
Always include context and specifically include the component that was missing a key.
2014-03-03 15:06:39 -08:00
Cheng Lou
99dab49f92 Refactor rendering to string without checksum & React ID
Finalize API for rendering to static markup.
Instead of passing a boolean option to `renderComponentToString`, just use another method.
2014-03-03 15:06:27 -08:00
Pete Hunt
9b0534eb77 ReactRAFBatchingStrategy
This will go in React addons only for now until we figure out `pendingState`.
2014-03-03 15:06:14 -08:00
Sebastian Markbage
eee04b19e1 Add monitor module for logging instrumentation
This adds an instrumentation hook for logging so that we can monitor invalid API
usage before we're ready to issue a warning.

I took the opportunity to update some console.warns to use the warning module
instead. The remaining console.warns
will be replaced by the warning module after we've cleaned up the callsites.
2014-03-03 15:05:53 -08:00
Cheng Lou
f734083a17 Better name for server-side rendering without React ID & checksum
"staticMarkup" sounds way better than "noChecksumNoID".
Also avoids some double negations.
2014-03-03 15:05:35 -08:00
Christoph Pojer
3fe9f9f336 Fix ReactPropTypesTest
children should never be defined as propType, so changing the test to use an actual prop.
2014-03-03 15:05:26 -08:00
Jan Kassens
e39c19423a temporarily disable warning 2014-03-03 15:05:10 -08:00
Cheng Lou
6203e53d16 Fix IE8 disabled input throwing on focus
When IE8 focuses a disabled item, it throws
This makes sure the we're not focusing the item during selection restoration/autofocus when the element's disabled.
2014-03-03 15:04:34 -08:00
Ben Alpert
88a4a566ae Merge pull request #1209 from petehunt/thinking-in-react
Add thinking in react to the official docs + cleanup
2014-03-03 10:40:28 -08:00
petehunt
2f6656e3e9 fix 2014-03-03 10:39:41 -08:00
petehunt
a0ecf47242 Add thinking in react to the official docs 2014-03-03 10:37:33 -08:00
Cheng Lou
4b670a08fa Merge pull request #1207 from passy/patch-2
Add missing backtick in complementary-tools.md
2014-03-02 16:21:35 -08:00
Pascal Hartig
6b78cfb0f4 Fix docstring typo for pure render mixin 2014-03-02 22:18:10 +00:00
Pascal Hartig
8df5e55efd Add missing backtick in complementary-tools.md 2014-03-02 22:10:24 +00:00
Pete Hunt
237adacc3a Merge pull request #1204 from jmingov/patch-1
Updated React Version to 0.9.0
2014-03-01 17:01:12 -08:00
Pete Hunt
90e996324a Merge pull request #1203 from fson/jsx-integrations
Merge the lists of JSX integrations in the docs.
2014-03-01 14:24:58 -08:00
3boll
b2649dd73b Updated React Version 0.9.0
**Updated Url's:**

CDN:

    http://fb.me/react-0.9.0.js

    http://fb.me/JSXTransformer-0.9.0.js

Starter Kit

    http://facebook.github.io/react/downloads/react-0.9.0.zip
2014-03-01 21:12:17 +01:00
Ville Immonen
0217461d16 Merge the lists of JSX integrations in the docs.
There were these two lists of JSX tools at Complimentary Tools
and Tooling Integration, that were a bit out of sync with each other.

Bring them together and add a link to the former from the latter.
2014-03-01 16:50:40 +02:00
Pete Hunt
3c4f45fe45 Merge pull request #1200 from ericflo/patch-1
Add Shirtstarter to examples of production apps.
2014-02-28 13:42:28 -08:00
Eric Florenzano
6485e21956 Add Shirtstarter to examples of production apps.
Shirtstarter is 100% built on React.
2014-02-28 13:40:14 -08:00
Paul O’Shannessy
ae72e6ef91 Merge pull request #1082 from spicyj/class-false
Ensure className={false} turns into string 'false'
2014-02-27 16:41:41 -08:00
Ben Alpert
42444f6bb9 Add polygon tag to transform
Fixes #1144.
2014-02-27 12:32:24 -08:00
Cheng Lou
071d2a947c [Docs] document optional param for renderComponentToString 2014-02-26 15:11:53 -08:00
Ben Alpert
298a05517e Tweak propTypes examples for clarity in oneOfType 2014-02-26 15:03:47 -08:00
Laurence Rowe
cbfbb54c1d omitClose for all void elements
According to http://www.w3.org/TR/html5/syntax.html#void-elements the following elements should not specify end tags:

area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr
2014-02-25 20:03:16 -08:00
evanc
26179d2b79 Test that Node is a function before using instanceof on it 2014-02-25 17:55:31 -08:00
Pete Hunt
ba78edbed8 Update complementary-tools.md 2014-02-25 17:01:00 -08:00
Paul O’Shannessy
34b6707132 Merge pull request #1166 from chenglou/docs-examples-consolidate
[Docs] Consolidate the two examples sections
2014-02-25 16:45:01 -08:00
Paul O’Shannessy
7b773a6b3d make <hasOwnProperty/> transform correctly 2014-02-24 18:03:02 -08:00
Paul O’Shannessy
1dfc5c79f9 Merge pull request #1148 from spicyj/jsx-constructor
Make JSX transform not break on 'constructor' attr
2014-02-24 17:20:25 -08:00
Ben Alpert
04111d5228 Add acknowledgements page 2014-02-24 17:18:57 -08:00
Jonas Gebhardt
a6c1b91c7d fix typo in community round-up #17 2014-02-24 15:27:02 -08:00
Pete Hunt
f34f0d2912 Update README.md 2014-02-24 14:25:09 -08:00
Pete Hunt
46cae63d2c Merge pull request #1156 from chenglou/1154
Remove TodoMVCs from examples/
2014-02-24 14:24:12 -08:00
Jonas Gebhardt
5049fc6b05 Community Round-up # 17 2014-02-24 14:20:17 -08:00
Pete Hunt
8b1279e6b2 Merge pull request #1174 from spicyj/addons-update
Add update() to React.addons
2014-02-24 14:10:07 -08:00
Ben Alpert
854d1f7c1b Add update() to React.addons 2014-02-24 14:05:29 -08:00
Pete Hunt
ab2d59f8b0 [addons] update() immutability helper
Dealing with immutable data is hard. This provides a simple helper (ported from the IG codebase) that makes dealing with immutable JSON data easier. Designed to be familiar for people who use MongoDB.
2014-02-24 13:58:51 -08:00
Cheng Lou
25cafec4a9 [Docs] Consolidate the two examples sections
Also made the formatting more consistent between complementary-tools and
examples.
2014-02-23 18:58:28 -08:00
Ben Alpert
61c287c5ea [docs] Fix version in lifecycle argument note
Fixes #1163.
2014-02-23 16:49:27 -08:00
Pete Hunt
7bba8c3257 Merge pull request #1155 from petehunt/update-helper
Add update() docs
2014-02-21 15:58:39 -08:00
Cheng Lou
2f0507f730 Remove TodoMVCs from examples/
Fixes #1154.
Added a README to point to TodoMVC's site instead.
2014-02-21 14:31:54 -08:00
petehunt
989f6f987d Add update() docs 2014-02-21 13:07:51 -08:00
Ben Alpert
e2b006f9ae .dataTransfer not .dragTransfer 2014-02-21 12:13:21 -08:00
Ben Alpert
141ff66986 Merge pull request #1150 from andreypopp/update-react-router-component-link
docs: update react-router-component link
2014-02-21 11:30:26 -08:00
Andrey Popp
da0b34e945 docs: update link for react-async 2014-02-21 18:30:19 +04:00
Andrey Popp
cbce621570 docs: update react-router-component link 2014-02-21 15:51:27 +04:00
Ben Alpert
472be09ff6 Make JSX transform not break on 'constructor' attr 2014-02-20 21:44:58 -08:00
Ben Alpert
7144a9f241 Merge pull request #1146 from dlau/patch-1
incorrect include in css transition group example?
2014-02-20 19:13:14 -08:00
Daryl Lau
8aaf5fdbf4 incorrect include in css transition group example?
TransitionGroup maps to ReactTransitionGroup, shouldn't it be a ReactCSSTransitionGroup?
2014-02-20 18:26:56 -08:00
Ben Alpert
445611f3e6 Add html5shiv to polyfill docs
cf. #1030
2014-02-20 17:21:41 -08:00
Pete Hunt
b79a3cbd21 Break more browser deps in core
This is part of what Sebastian suggested. We require it into every component that explicitly needs it and inject it as a default mixin for composite components in
the browser environment. This change frees us up to inject everything in ReactComponent.
2014-02-20 16:55:17 -08:00
Jason Bonta
0790040aac update docblock
...for typechecking.
2014-02-20 16:55:01 -08:00
Cheng Lou
2900997b5f Fix transaction test comments
Probably got copy/pasted in.
2014-02-20 16:54:52 -08:00
Cheng Lou
7eb33ef176 simplify mountImageIntoNode's way of putting markup in innerHTML
Instead of removing the node from the document before changing its innerHTML, just do it directly.
The comment seems to be outdated for years. http://jsperf.com/detach-while-setting-innerhtml
2014-02-20 16:54:43 -08:00
Cheng Lou
5545887a48 Allow rendering markup string without checksum and React ID
This fixes https://github.com/facebook/react/pull/994.
Also fixes https://github.com/facebook/react/issues/1079.
Server-rendering without ID/checksum now works. Also won't call didMount.
2014-02-20 16:54:20 -08:00
Paul O’Shannessy
e29584b49b Update authors 2014-02-20 15:20:27 -08:00
Jeff Morrison
c43b3f406c Merge pull request #1143 from jeffmo/npm_test
Add support for `npm test`
2014-02-20 13:15:09 -08:00
jeffmo
a4d6796705 Add support for npm test 2014-02-20 13:10:05 -08:00
Ben Alpert
89d4d352e1 Tweak tutorial wording to be more accurate 2014-02-20 11:40:14 -08:00
Paul O’Shannessy
f3c1383af9 Add context deprecation to changelog, reformat 2014-02-20 10:32:21 -08:00
Shaun Trennery
c32e398a5c Addition of 'this.context' component breaking change
this.context on components is now reserved for internal use by React

closes #1141
2014-02-20 10:32:12 -08:00
Paul O’Shannessy
95edc396df version bump to 0.10.0-alpha 2014-02-19 22:53:29 -08:00
Ben Alpert
1de77f1fbe Ensure className={false} turns into string 'false'
Fixes inconsistency shown by http://jsfiddle.net/zpao/qeXLm/.
2014-02-19 13:47:35 -08:00
185 changed files with 5173 additions and 3858 deletions

View File

@@ -4,9 +4,11 @@ Christoph Pojer <christoph.pojer@gmail.com>
Christoph Pojer <christoph.pojer@gmail.com> <cpojer@fb.com>
Connor McSheffrey <c@conr.me> <connor.mcsheffrey@gmail.com>
Dan Schafer <dschafer@fb.com>
Evan Coonrod <evan@paloalto.com>
Fabio M. Costa <fabiomcosta@gmail.com> <fabs@fb.com>
Harry Hull <harry.hull1@gmail.com>
Ingvar Stepanyan <me@rreverser.com> <rreverser@ubuntu.rreverser.a4.internal.cloudapp.net>
Jaime Mingo <j.mingov@3boll.com>
Jason Bonta <jbonta@gmail.com> <jasonbonta@fb.com>
Jason Trill <jason@jasontrill.com>
Jeff Morrison <jeff@anafx.com> <Jeff@anafx.com>
@@ -18,6 +20,7 @@ Jordan Walke <jordojw@gmail.com> <jordanjcw@fb.com>
Josh Duck <josh@fb.com> <github@joshduck.com>
Jun Wu <quark@lihdd.net>
Keito Uchiyama <projects@keito.me> <keito@fb.com>
Kunal Mehta <k.mehta@berkeley.edu> <kunalm@fb.com>
Laurence Rowe <l@lrowe.co.uk> <laurence@lrowe.co.uk>
Martin Andert <mandert@gmail.com>
Michal Srb <xixixao@seznam.cz> xixixao <xixixao@seznam.cz>

12
AUTHORS
View File

@@ -1,3 +1,4 @@
Adam Solove <asolove@gmail.com>
Alan deLevie <adelevie@gmail.com>
Alex Zelenskiy <azelenskiy@fb.com>
Alexander Solovyov <alexander@solovyov.net>
@@ -25,15 +26,19 @@ Daniel Gasienica <dgasienica@zynga.com>
Daniel Lo Nigro <danlo@fb.com>
Daniel Miladinov <dmiladinov@wingspan.com>
Danny Ben-David <dannybd@fb.com>
Daryl Lau <daryl@weak.io>
David Hellsing <david@aino.se>
David Hu <davidhu91@gmail.com>
Dustin Getz <dgetz@wingspan.com>
Eric Clemmons <eric@smarterspam.com>
Eric Florenzano <floguy@gmail.com>
Eric Schoffstall <contra@wearefractal.com>
Evan Coonrod <evan@paloalto.com>
Fabio M. Costa <fabiomcosta@gmail.com>
Felipe Oliveira Carvalho <felipekde@gmail.com>
Felix Kling <fkling@fb.com>
Fernando Correia <fernando@servicero.com>
Greg Hurrell <glh@fb.com>
Greg Roodt <groodt@gmail.com>
Guido Bouman <m@guido.vc>
Harry Hull <harry.hull1@gmail.com>
@@ -42,6 +47,7 @@ Ian Obermiller <iano@fb.com>
Ingvar Stepanyan <me@rreverser.com>
Isaac Salier-Hellendag <isaac@fb.com>
Ivan Kozik <ivan@ludios.org>
Jaime Mingo <j.mingov@3boll.com>
Jakub Malinowski <jakubmal@gmail.com>
James Ide <ide@fb.com>
Jamie Wong <jamie.lf.wong@gmail.com>
@@ -51,9 +57,12 @@ Jared Forsyth <jared@jaredforsyth.com>
Jason Bonta <jbonta@gmail.com>
Jason Trill <jason@jasontrill.com>
Jean Lauliac <lauliacj@gmail.com>
Jeff Barczewski <jeff.barczewski@gmail.com>
Jeff Carpenter <gcarpenterv@gmail.com>
Jeff Morrison <jeff@anafx.com>
Jeffrey Lin <lin.jeffrey@gmail.com>
Jignesh Kakadiya <jigneshhk1992@gmail.com>
Jing Chen <jingc@fb.com>
Johannes Baiter <johannes.baiter@gmail.com>
John Watson <jwatson@fb.com>
Jonas Gebhardt <jonas@instagram.com>
@@ -66,6 +75,7 @@ Kit Randel <kit@nocturne.net.nz>
Kunal Mehta <k.mehta@berkeley.edu>
Laurence Rowe <l@lrowe.co.uk>
Levi McCallum <levi@levimccallum.com>
Lily <qvang.j@gmail.com>
Logan Allen <loganfynne@gmail.com>
Luigy Leon <luichi.19@gmail.com>
Mark Richardson <echo@fb.com>
@@ -103,7 +113,9 @@ Sundeep Malladi <sundeep.malladi@gmail.com>
Thomas Aylott <oblivious@subtlegradient.com>
Timothy Yung <yungsters@gmail.com>
Tom Occhino <tomocchino@gmail.com>
Ville Immonen <ville.immonen@iki.fi>
Vjeux <vjeuxx@gmail.com>
Wincent Colaiuta <win@wincent.com>
Zach Bruggeman <zbruggeman@me.com>
fxbois <fxbois@gmail.com>
imagentleman <imagentlemail@gmail.com>

View File

@@ -1,3 +1,27 @@
## 0.10.0 (March 21, 2014)
### React Core
#### New Features
* Added warnings to help migrate towards descriptors
* Made it possible to server render without React-related markup (`data-reactid`, `data-react-checksum`). This DOM will not be mountable by React. [Read the docs for `React.renderComponentToStaticMarkup`](http://facebook.github.io/react/docs/top-level-api.html#react.rendercomponenttostaticmarkup)
* Added support for more attributes:
* `srcSet` for `<img>` to specify images at different pixel ratios
* `textAnchor` for SVG
#### Bug Fixes
* Ensure all void elements dont insert a closing tag into the markup.
* Ensure `className={false}` behaves consistently
* Ensure `this.refs` is defined, even if no refs are specified.
### Addons
* `update` function to deal with immutable data. [Read the docs](http://facebook.github.io/react/docs/update.html)
### react-tools
* Added an option argument to `transform` function. The only option supported is `harmony`, which behaves the same as `jsx --harmony` on the command line. This uses the ES6 transforms from [jstransform](https://github.com/facebook/jstransform).
## 0.9.0 (February 20, 2014)
### React Core

View File

@@ -179,7 +179,13 @@ module.exports = function(grunt) {
'test:webdriver:phantomjs',
'coverage:parse'
]);
grunt.registerTask('test', ['build:test', 'build:basic', 'test:webdriver:phantomjs']);
grunt.registerTask('test', function() {
if (grunt.option('debug')) {
grunt.task.run('build:test', 'build:basic', 'connect:server:keepalive');
} else {
grunt.task.run('build:test', 'build:basic', 'test:webdriver:phantomjs');
}
});
grunt.registerTask('perf', ['build:perf', 'perf:webdriver:phantomjs']);
grunt.registerTask('npm:test', ['build', 'npm:pack']);

View File

@@ -40,12 +40,12 @@ The fastest way to get started is to serve JavaScript from the CDN (also availab
```html
<!-- The core React library -->
<script src="http://fb.me/react-0.8.0.js"></script>
<script src="http://fb.me/react-0.10.0.js"></script>
<!-- In-browser JSX transformer, remove when pre-compiling JSX. -->
<script src="http://fb.me/JSXTransformer-0.8.0.js"></script>
<script src="http://fb.me/JSXTransformer-0.10.0.js"></script>
```
We've also built a [starter kit](http://facebook.github.io/react/downloads/react-0.8.0.zip) which might be useful if this is your first time using React. It includes a webpage with an example of using React with live code.
We've also built a [starter kit](http://facebook.github.io/react/downloads/react-0.10.0.zip) which might be useful if this is your first time using React. It includes a webpage with an example of using React with live code.
If you'd like to use [bower](http://bower.io), it's as easy as:
@@ -87,6 +87,8 @@ We use grunt to automate many tasks. Run `grunt -h` to see a mostly complete lis
```sh
# Build and run tests with PhantomJS
grunt test
# Build and run tests in your browser
grunt test --debug
# Lint the code with JSHint
grunt lint
# Wipe out build directory

8
docs/404.md Normal file
View File

@@ -0,0 +1,8 @@
---
layout: single
title: Page Not Found
---
We couldn't find what you were looking for.
Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

View File

@@ -13,7 +13,7 @@ redcarpet:
pygments: true
name: React
markdown: redcarpet
react_version: 0.9.0
react_version: 0.10.0
description: A JavaScript library for building user interfaces
relative_permalinks: true
paginate: 5

View File

@@ -4,9 +4,9 @@
@import '_solarized';
@mixin code-typography {
font-family: 'source-code-pro', Menlo, 'Courier New', Consolas, monospace;
font-size: 13px;
line-height: 20px;
font-family: 'source-code-pro', Menlo, Consolas, 'Courier New', monospace;
font-size: 0.8em;
line-height: 1.5;
}
$skinnyContentWidth: 650px;
@@ -683,7 +683,12 @@ p code {
}
.highlight pre code {
font-size: inherit;
/* Respect line-height defined in <code> styles above */
display: block;
/* Cancel out styles for `li code` in case we have a <pre> within an <li>. */
background: none;
padding: 0;
}
.highlight pre .lineno {

View File

@@ -53,6 +53,8 @@
title: Test Utilities
- id: clone-with-props
title: Cloning Components
- id: update
title: Immutability Helpers
- title: Reference
items:
- id: top-level-api
@@ -71,3 +73,9 @@
title: Special Non-DOM Attributes
- id: reconciliation
title: Reconciliation
- title: Flux
items:
- id: flux-overview
title: Flux Overview
- id: flux-todo-list
title: Flux Todo List

View File

@@ -30,7 +30,7 @@
<![endif]-->
<script type="text/javascript" src="/react/js/codemirror.js"></script>
<script type="text/javascript" src="/react/js/javascript.js"></script>
<script type="text/javascript" src="/react/js/react.min.js"></script>
<script type="text/javascript" src="/react/js/react.js"></script>
<script type="text/javascript" src="/react/js/JSXTransformer.js"></script>
<script type="text/javascript" src="/react/js/live_editor.js"></script>
<script type="text/javascript" src="/react/js/showdown.js"></script>

View File

@@ -19,7 +19,5 @@ sectionid: docs
<a class="docs-next" href="/react/docs/{{ page.next }}">Next &rarr;</a>
{% endif %}
</div>
<div class="fb-comments" data-width="650" data-num-posts="10" data-href="{{ site.url }}{{ site.baseurl }}{{ page.url }}"></div>
</div>
</section>

View File

@@ -16,6 +16,5 @@ sectionid: blog
</div>
<div class="fb-like" data-send="true" data-width="650" data-show-faces="false"></div>
<div class="fb-comments" data-width="650" data-num-posts="10" data-href="{{ site.url }}{{ site.baseurl }}{{ page.url }}"></div>
</div>
</section>

View File

@@ -19,7 +19,5 @@ sectionid: tips
<a class="docs-next" href="/react/tips/{{ page.next }}">Next &rarr;</a>
{% endif %}
</div>
<div class="fb-comments" data-width="650" data-num-posts="10" data-href="{{ site.url }}{{ site.baseurl }}{{ page.url }}"></div>
</div>
</section>

View File

@@ -75,7 +75,7 @@ some pretty cool things with it:
- We've built internal prototypes that run React apps in a web worker and use
React to drive **native iOS views** via an Objective-C bridge.
- You can run React
[on the server](http://github.com/petehunt/react-server-rendering)
[on the server](http://github.com/petehunt/react-server-rendering-example)
for SEO, performance, code sharing and overall flexibility.
- Events behave in a consistent, standards-compliant way in all browsers
(including IE8) and automatically use

View File

@@ -22,7 +22,7 @@ We're looking forward to part 2!
## React vs. async DOM manipulation
Eliseu Monar ([@eliseumds](https://twitter.com/eliseumds))'s post "[ReactJS vs async concurrent rendering](http://eliseu.tk/post/77843550010/vitalbox-pchr-reactjs-vs-async-concurrent-rendering)" is a great example of how React quite literally renders a whole array of common web development work(arounds) obsolete.
Eliseu Monar ([@eliseumds](https://twitter.com/eliseumds))'s post "[ReactJS vs async concurrent rendering](http://eliseumds.tumblr.com/post/77843550010/vitalbox-pchr-reactjs-vs-async-concurrent-rendering)" is a great example of how React quite literally renders a whole array of common web development work(arounds) obsolete.

View File

@@ -8,13 +8,13 @@ author: Paul OShannessy
The release candidate is available for download from the CDN:
* **React**
Dev build with warnings: <http://fb.me/react-0.10.0-rc1.js>
Minified build for production: <http://fb.me/react-0.10.0-rc1.min.js>
* **React with Add-Ons**
Dev build with warnings: <http://fb.me/react-with-addons-0.10.0-rc1.js>
Minified build for production: <http://fb.me/react-with-addons-0.10.0-rc1.min.js>
* **In-Browser JSX transformer**
* **React**
Dev build with warnings: <http://fb.me/react-0.10.0-rc1.js>
Minified build for production: <http://fb.me/react-0.10.0-rc1.min.js>
* **React with Add-Ons**
Dev build with warnings: <http://fb.me/react-with-addons-0.10.0-rc1.js>
Minified build for production: <http://fb.me/react-with-addons-0.10.0-rc1.min.js>
* **In-Browser JSX transformer**
<http://fb.me/JSXTransformer-0.10.0-rc1.js>
We've also published version `0.10.0-rc1` of the `react` and `react-tools` packages on npm and the `react` package on bower.
@@ -53,9 +53,10 @@ The plan for v0.11 is that we will go fully to "descriptors". Method calls on th
### React Core
#### New Features
* Made it possible to server render without React-related markup (`data-reactid`, `data-react-checksum`) - `React.renderComponentToStaticMarkup`
* Added warnings to help migrate towards descriptors
* Made it possible to server render without React-related markup (`data-reactid`, `data-react-checksum`). This DOM will not be mountable by React. [Read the docs for `React.renderComponentToStaticMarkup`](/react/docs/top-level-api.html#react.rendercomponenttostaticmarkup)
* Added support for more attributes:
* `srcSet` for `<img>` to images at different pixel ratios
* `srcSet` for `<img>` to specify images at different pixel ratios
* `textAnchor` for SVG
#### Bug Fixes
@@ -65,7 +66,7 @@ The plan for v0.11 is that we will go fully to "descriptors". Method calls on th
### Addons
* `update` function to deal with immutable data.
* `update` function to deal with immutable data. [Read the docs](/react/docs/update.html)
### react-tools
* Added an option argument to `transform` function. The only option supported is `harmony`, which behaves the same as `jsx --harmony` on the command line. This uses the ES6 transforms from [jstransform](https://github.com/facebook/jstransform).

View File

@@ -0,0 +1,73 @@
---
title: React v0.10
layout: post
author: Paul OShannessy
---
Hot on the heels of the [release candidate earlier this week](/react/blog/2014/03/19/react-v0.10-rc1.html), we're ready to call v0.10 done. The only major issue we discovered had to do with the `react-tools` package, which has been updated. We've copied over the changelog from the RC with some small clarifying changes.
The release is available for download from the CDN:
* **React**
Dev build with warnings: <http://fb.me/react-0.10.0.js>
Minified build for production: <http://fb.me/react-0.10.0.min.js>
* **React with Add-Ons**
Dev build with warnings: <http://fb.me/react-with-addons-0.10.0.js>
Minified build for production: <http://fb.me/react-with-addons-0.10.0.min.js>
* **In-Browser JSX transformer**
<http://fb.me/JSXTransformer-0.10.0.js>
We've also published version `0.10.0` of the `react` and `react-tools` packages on npm and the `react` package on bower.
Please try these builds out and [file an issue on GitHub](https://github.com/facebook/react/issues/new) if you see anything awry.
## Clone On Mount
The main purpose of this release is to provide a smooth upgrade path as we evolve some of the implementation of core. In v0.9 we started warning in cases where you called methods on unmounted components. This is part of an effort to enforce the idea that the return value of a component (`React.DOM.div()`, `MyComponent()`) is in fact not a reference to the component instance React uses in the virtual DOM. The return value is instead a light-weight object that React knows how to use. Since the return value currently is a reference to the same object React uses internally, we need to make this transition in stages as many people have come to depend on this implementation detail.
In 0.10, were adding more warnings to catch a similar set of patterns. When a component is mounted we clone it and use that object for our internal representation. This allows us to capture calls you think youre making to a mounted component. Well forward them on to the right object, but also warn you that this is breaking. See “Access to the Mounted Instance” on [this page](http://fb.me/react-warning-descriptors). Most of the time you can solve your pattern by using refs.
Storing a reference to your top level component is a pattern touched upon on that page, but another examples that demonstrates what we see a lot of:
```js
// This is a common pattern. However instance here really refers to a
// "descriptor", not necessarily the mounted instance.
var instance = <MyComponent/>;
React.renderComponent(instance);
// ...
instance.setProps(...);
// The change here is very simple. The return value of renderComponent will be
// the mounted instance.
var instance = React.renderComponent(<MyComponent/>)
// ...
instance.setProps(...);
```
These warnings and method forwarding are only enabled in the development build. The production builds continue to work as they did in v0.9. We strongly encourage you to use the development builds to catch these warnings and fix the call sites.
The plan for v0.11 is that we will go fully to "descriptors". Method calls on the return value of `MyComponent()` will fail hard.
## Changelog
### React Core
#### New Features
* Added warnings to help migrate towards descriptors
* Made it possible to server render without React-related markup (`data-reactid`, `data-react-checksum`). This DOM will not be mountable by React. [Read the docs for `React.renderComponentToStaticMarkup`](/react/docs/top-level-api.html#react.rendercomponenttostaticmarkup)
* Added support for more attributes:
* `srcSet` for `<img>` to specify images at different pixel ratios
* `textAnchor` for SVG
#### Bug Fixes
* Ensure all void elements dont insert a closing tag into the markup.
* Ensure `className={false}` behaves consistently
* Ensure `this.refs` is defined, even if no refs are specified.
### Addons
* `update` function to deal with immutable data. [Read the docs](/react/docs/update.html)
### react-tools
* Added an option argument to `transform` function. The only option supported is `harmony`, which behaves the same as `jsx --harmony` on the command line. This uses the ES6 transforms from [jstransform](https://github.com/facebook/jstransform).

View File

@@ -0,0 +1,57 @@
---
title: The Road to 1.0
layout: post
author: Paul O'Shannessy
---
When we launched React last spring, we purposefully decided not to call it 1.0. It was production ready, but we had plans to evolve APIs and behavior as we saw how people were using React, both internally and externally. We've learned a lot over the past 9 months and we've thought a lot about what 1.0 will mean for React. A couple weeks ago, I outlined [several projects][projects] that we have planned to take us to 1.0 and beyond. Today I'm writing a bit more about them to give our users a better insight into our plans.
Our primary goal with 1.0 is to clarify our messaging and converge on an API that is aligned with our goals. In order to do that, we want to clean up bad patterns we've seen in use and really help enable developers write good code.
## Descriptors
The first part of this is what we're calling "descriptors". I talked about this briefly in our [v0.10 announcements][v0.10]. The goal here is to separate our virtual DOM representation from our use of it. Simply, this means the return value of a component (e.g. `React.DOM.div()`, `MyComponent()`) will be a simple object containing the information React needs to render. Currently the object returned is actually linked to React's internal representation of the component and even directly to the DOM element. This has enabled some bad patterns that are quite contrary to how we want people to use React. That's our failure.
We added some warnings in v0.9 to start migrating some of these bad patterns. With v0.10 we'll catch more. You'll see more on this soon as we expect to ship v0.11 with descriptors.
## API Cleanup
This is really connected to everything. We want to keep the API as simple as possible and help developers [fall into the pit of success][pitofsuccess]. Enabling bad patterns with bad APIs is not success.
## ES6
Before we even launched React publicly, members of the team were talking about how we could leverage ES6, namely classes, to improve the experience of creating React components. Calling `React.createClass(...)` isn't great. We don't quite have the right answer here yet, but we're close. We want to make sure we make this as simple as possible. It could look like this:
```js
class MyComponent extends React.Component {
render() {
...
}
}
```
There are other features of ES6 we're already using in core. I'm sure we'll see more of that. The `jsx` executable we ship with `react-tools` already supports transforming many parts of ES6 into code that will run on older browsers.
## Context
While we haven't documented `context`, it exists in some form in React already. It exists as a way to pass values through a tree without having to use props at every single point. We've seen this need crop up time and time again, so we want to make this as easy as possible. It's use has performance tradeoffs, and there are known weaknesses in our implementation, so we want to make sure this is a solid feature.
## Addons
As you may know, we ship a separate build of React with some extra features we called "addons". While this has served us fine, it's not great for our users. It's made testing harder, but also results in more cache misses for people using a CDN. The problem we face is that many of these "addons" need access to parts of React that we don't expose publicly. Our goal is to ship each addon on its own and let each hook into React as needed. This would also allow others to write and distribute "addons".
## Browser Support
As much as we'd all like to stop supporting older browsers, it's not always possible. Facebook still supports IE8. While React won't support IE8 forever, our goal is to have 1.0 support IE8. Hopefully we can continue to abstract some of these rough parts.
## Animations
Finding a way to define animations in a declarative way is a hard problem. We've been exploring the space for a long time. We've introduced some half-measures to alleviate some use cases, but the larger problem remains. While we'd like to make this a part of 1.0, realistically we don't think we'll have a good solution in place.
## Miscellaneous
There are several other things I listed on [our projects page][projects] that we're tracking. Some of them are internals and have no obvious outward effect (improve tests, repo separation, updated test runner). I encourage you to take a look.
[v0.10]: http://facebook.github.io/react/blog/2014/03/21/react-v0.10.html
[pitofsuccess]: http://blog.codinghorror.com/falling-into-the-pit-of-success/
[projects]: https://github.com/facebook/react/wiki/Projects

View File

@@ -0,0 +1,38 @@
---
title: "Use React and JSX in ASP.NET MVC"
layout: post
author: Daniel Lo Nigro
---
Today we're happy to announce the initial release of
[ReactJS.NET](http://reactjs.net/), which makes it easier to use React and JSX
in .NET applications, focusing specifically on ASP.NET MVC web applications.
It has several purposes:
- On-the-fly JSX to JavaScript compilation. Simply reference JSX files and they
will be compiled and cached server-side.
```html
<script src="@Url.Content("/Scripts/HelloWorld.jsx")"></script>
```
- JSX to JavaScript compilation via popular minification/combination libraries
(Cassette and ASP.NET Bundling and Minification). This is suggested for
production websites.
- Server-side component rendering to make your initial render super fast.
Even though we are focusing on ASP.NET MVC, ReactJS.NET can also be used in
Web Forms applications as well as non-web applications (for example, in build
scripts). ReactJS.NET currently only works on Microsoft .NET but we are working
on support for Linux and Mac OS X via Mono as well.
Installation
------------
ReactJS.NET is packaged in NuGet. Simply run `Install-Package React.Mvc4` in the
package manager console or search NuGet for "React" to install it.
[See the documentation](http://reactjs.net/docs) for more information. The
GitHub project contains
[a sample website](https://github.com/reactjs/React.NET/tree/master/src/React.Sample.Mvc4)
demonstrating all of the features.
Let us know what you think, and feel free to send through any feedback and
report bugs [on GitHub](https://github.com/reactjs/React.NET).

View File

@@ -0,0 +1,17 @@
---
title: "Flux: An Application Architecture for React"
layout: post
author: Bill Fisher and Jing Chen
---
We recently spoke at one of f8's breakout session about Flux, a data flow architecture that works well with React. Check out the video here:
<figure><iframe width="560" height="315" src="//www.youtube.com/embed/nYkdrAPrdcw?list=PLb0IAmt7-GS188xDYE-u1ShQmFFGbrk0v" frameborder="0" allowfullscreen></iframe></figure>
To summarize, Flux works well for us because the single directional data flow makes it easy to understand and modify an application as it becomes more complicated. We found that two-way data bindings lead to cascading updates, where changing one data model led to another data model updating, making it very difficult to predict what would change as the result of a single user interaction.
In Flux, the Dispatcher is a singleton that directs the flow of data and ensures that updates do not cascade. As an application grows, the Dispatcher becomes more vital, as it can also manage dependencies between stores by invoking the registered callbacks in a specific order.
When a user interacts with a React view, the view sends an action (usually represented as a JavaScript object with some fields) through the dispatcher, which notifies the various stores that hold the application's data and business logic. When the stores change state, they notify the views that something has updated. This works especially well with React's declarative model, which allows the stores to send updates without specifying how to transition views between states.
Flux is more of a pattern than a formal framework, so you can start using Flux immediately without a lot of new code. An [example of this architecture](https://github.com/facebook/react/tree/master/examples/todomvc-flux) is available, along with more [detailed documentation](http://facebook.github.io/react/docs/flux-overview.html) and a [tutorial](http://facebook.github.io/react/docs/flux-todo-list.html). Look for more examples to come in the future.

View File

@@ -0,0 +1,15 @@
---
title: "One Year of Open-Source React"
layout: post
author: Cheng Lou
---
Today marks the one-year open-source anniversary of React.
Its been a crazy ride. 2.3k commits and 1.5k issues and pull requests later, were approaching version 1.0 and nearing 7k Github stars, with big names such as Khan Academy, New York Times, and Airbnb (and naturally, Facebook and Instagram) using React in production, and many more developers blogging their success stories with it. The [roadmap](http://facebook.github.io/react/blog/2014/03/28/the-road-to-1.0.html) gives a glimpse into the future of the library; exciting stuff lies ahead!
Every success has its story. React was born out of our frustration at existing solutions for building UIs. When it was first suggested at Facebook, few people thought that functionally re-rendering everything and diffing the results could ever perform well. However, support grew after we built the first implementation and people wrote their first components. When we open-sourced React, the initial reception was [similarly skeptical](http://www.reddit.com/r/programming/comments/1fak87/react_facebooks_latest_javascript_client_library/). It challenges many pre-established conventions and received mostly disapproving first-impressions, intermingled with positive ones that often were votes of confidence in Facebooks engineering capabilities. On an open, competitive platform such as the web, it's been hard to convince people to try React. [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html), in particular, filtered out a huge chunk of potential early adopters.
Fast forward one year, React has strongly [grown in popularity](https://news.ycombinator.com/item?id=7489959). Special acknowledgments go to Khan Academy, the ClojureScript community, and established frameworks such as Ember and Angular for contributing to and debating on our work. We'd also like to thank all the [individual contributors](https://github.com/facebook/react/graphs/contributors) who have taken the time to help out over the past year. React, as a library and as a new paradigm on the web, wouldn't have gained as much traction without them. In the future, we will continue to try to set an example of what's possible to achieve when we rethink about current “best practices”.
Heres to another year!

View File

@@ -0,0 +1,55 @@
---
title: "Community Round-up #19"
layout: post
author: Cheng Lou
---
## React Meetups!
Ever wanted to find developers who also share the same interest in React than you? Recently, there has been a React Meetup in [San Francisco](http://www.meetup.com/ReactJS-San-Francisco/) (courtesy of [Telmate](http://www.telmate.com)), and one in [London](http://www.meetup.com/London-React-User-Group/) (courtesy of [Stuart Harris](http://www.meetup.com/London-React-User-Group/members/105837542/), [Cain Ullah](http://www.meetup.com/London-React-User-Group/members/15509971/) and [Zoe Merchant](http://www.meetup.com/London-React-User-Group/members/137058242/)). These two events have been big successes; a second one in London is [already planned](http://www.meetup.com/London-React-User-Group/events/191406572/).
If you don't live near San Francisco or London, why not start one in your community?
## Complementary Tools
In case you haven't seen it, we've consolidated the tooling solution around React on [this wiki page](https://github.com/facebook/react/wiki/Complementary-Tools). Some of the notable recent entries include:
- [Ryan Florence](https://github.com/rpflorence) and [Michael Jackson](https://github.com/mjackson)'s [react-nested-router](https://github.com/rpflorence/react-nested-router), which is a translation of the Ember router API to React.
- [Stephen J. Collings](https://github.com/stevoland)'s [react-bootstrap](https://github.com/react-bootstrap/react-bootstrap), which wraps the popular framework with a bit of React goodness. The [website](http://react-bootstrap.github.io/components.html) features live-editable demos.
- [Andrey Popp](https://github.com/andreypopp)'s [react-quickstart](https://github.com/andreypopp/react-quickstart), which gives you a quick template for server-side rendering and routing, among other features.
These are some of the links that often pop up on the #reactjs IRC channel. If you made something that you think deserves to be shown on the wiki, feel free to add it!
## React in Interesting Places
The core concepts React themselves is something very valuable that the community is exploring and pushing further. A year ago, we wouldn't have imagined something like [Bruce Hauman](http://rigsomelight.com)'s [Flappy Bird ClojureScript port](http://rigsomelight.com/2014/05/01/interactive-programming-flappy-bird-clojurescript.html), whose interactive programming has been made possible through React:
<iframe width="560" height="315" src="//www.youtube.com/embed/KZjFVdU8VLI" frameborder="0" allowfullscreen></iframe>
And don't forget [Pete Hunt](https://github.com/petehunt)'s Wolfenstein 3D rendering engine in React ([source code](https://github.com/petehunt/wolfenstein3D-react/blob/master/js/renderer.js#L183)). While it's nearly a year old, it's still a nice demo.
[![](/react/img/blog/wolfenstein_react.png)](http://www.petehunt.net/wolfenstein3D-react/wolf3d.html)
Give us a shoutout on IRC or [React Google Groups](https://groups.google.com/forum/#!forum/reactjs) if you've used React in some Interesting places.
## Even More People Using React
### Prismatic
[Prismatic](http://getprismatic.com/home) recently shrank their codebase fivefold with the help of React and its popular ClojureScript wrapper, [Om](https://github.com/swannodette/om). They detailed their very positive experience [here](http://blog.getprismatic.com/om-sweet-om-high-functional-frontend-engineering-with-clojurescript-and-react/).
> Finally, the state is normalized: each piece of information is represented in a single place. Since React ensures consistency between the DOM and the application data, the programmer can focus on ensuring that the state properly stays up to date in response to user input. If the application state is normalized, then this consistency is guaranteed by definition, completely avoiding the possibility of an entire class of common bugs.
### Adobe Brackets
[Kevin Dangoor](http://www.kevindangoor.com) works on [Brackets](http://brackets.io/?lang=en), the open-source code editor. After writing [his first impression on React](http://www.kevindangoor.com/2014/05/simplifying-code-with-react/), he followed up with another insightful [article](http://www.kevindangoor.com/2014/05/react-in-brackets/) on how to gradually make the code transition, how to preserve the editor's good parts, and how to tune Brackets' tooling around JSX.
> We dont need to switch to React everywhere, all at once. Its not a framework that imposes anything on the application structure. [...] Easy, iterative adoption is definitely something in Reacts favor for us.
### Storehouse
[Storehouse](https://www.storehouse.co) (Apple Design Award 2014)'s web presence is build with React. Here's [an example story](https://www.storehouse.co/stories/y2ad-mexico-city-clouds). Congratulations on the award!
### Vim Awesome
[Vim Awesome](http://vimawesome.com), an open-source Vim plugins directory built on React, was just launched. Be sure to [check out the source code](https://github.com/divad12/vim-awesome) if you're curious to see an example of how to build a small single-page React app.
## Random Tweets
<blockquote class="twitter-tweet" lang="en"><p>Spent 12 hours so far with <a href="https://twitter.com/hashtag/reactjs?src=hash">#reactjs</a>. Spent another 2 wondering why we&#39;ve been doing JS frameworks wrong until now. React makes me happy.</p>&mdash; Paul Irwin (@paulirwin) <a href="https://twitter.com/paulirwin/statuses/481263947589242882">June 24, 2014</a></blockquote>

View File

@@ -0,0 +1,147 @@
---
title: React v0.11 RC
layout: post
author: Paul OShannessy
---
It's that time again… we're just about ready to ship a new React release! v0.11 includes a wide array of bug fixes and features. We highlighted some of the most important changes below, along with the full changelog.
The release candidate is available for download from the CDN:
* **React**
Dev build with warnings: <http://fb.me/react-0.11.0-rc1.js>
Minified build for production: <http://fb.me/react-0.11.0-rc1.min.js>
* **React with Add-Ons**
Dev build with warnings: <http://fb.me/react-with-addons-0.11.0-rc1.js>
Minified build for production: <http://fb.me/react-with-addons-0.11.0-rc1.min.js>
* **In-Browser JSX transformer**
<http://fb.me/JSXTransformer-0.11.0-rc1.js>
We've also published version `0.11.0-rc1` of the `react` and `react-tools` packages on npm and the `react` package on bower.
Please try these builds out and [file an issue on GitHub](https://github.com/facebook/react/issues/new) if you see anything awry.
# Blog Post
## `getDefaultProps`
Starting in React 0.11, `getDefaultProps()` is called only once when `React.createClass()` is called, instead of each time a component is rendered. This means that `getDefaultProps()` can no longer vary its return value based on `this.props` and any objects will be shared across all instances. This change improves performance and will make it possible in the future to do PropTypes checks earlier in the rendering process, allowing us to give better error messages.
## Rendering to `null`
Since React's release, people have been using work arounds to "render nothing". Usually this means returning an empty `<div/>` or `<span/>`. Some people even got clever and started returning `<noscript/>` to avoid extraneous DOM nodes. We finally provided a "blessed" solution that allows developers to writing meaningful code. Returning `null` in an explicit indication to React that you do not want anything rendered. Behind the scenes we make this work with a `<noscript>` element, though in the future we hope to not put anything in the document. In the mean time, `<noscript>` elements do not affect layout in any way, so you can feel safe using `null` today!
```js
// Before
render: function() {
if (!this.state.visible) {
return <span/>;
}
// ...
}
// After
render: function() {
if (!this.state.visible) {
return null;
}
// ...
}
```
## JSX Namespacing
Another feature request we've been hearing for a long time is the ability to have namespaces in JSX. Given that JSX is just JavaScript, we didn't want to use XML namespacing. Instead we opted for a standard JS approach: object property access. Instead of assigning variables to access components stored in an object (such as a component library), you can now use the component directly as `<Namespace.Component/>`.
```js
// Before
var UI = require('UI');
var UILayout = UI.Layout;
var UIButton = UI.Button;
var UILabel = UI.Label;
render: function() {
return <UILayout><UIButton /><UILabel>text</UILabel></UILayout>;
}
// After
var UI = require('UI');
render: function() {
return <UI.Layout><UI.Button /><UI.Label>text</UI.Label></UI.Layout>;
}
```
## Improved keyboard event normalization
Keyboard events now contain a normalized `e.key` value according to the [DOM Level 3 Events spec](http://www.w3.org/TR/DOM-Level-3-Events/#keys-special), allowing you to write simpler key handling code that works in all browsers, such as:
```js
handleKeyDown: function(e) {
if (e.key === 'Enter') {
// Handle enter key
} else if (e.key === ' ') {
// Handle spacebar
} else if (e.key === 'ArrowLeft') {
// Handle left arrow
}
},
```
Keyboard and mouse events also now include a normalized `e.getModifierState()` that works consistently across browsers.
## Changelog
### React Core
#### Breaking Changes
* `getDefaultProps()` is now called once per class and shared across all instances
#### New Features
* Rendering to `null`
* Keyboard events include normalized `e.key` and `e.getModifierState()` properties
* New normalized `onBeforeInput` event
* `React.Children.count` has been added as a helper for counting the number of children
#### Bug Fixes
* Re-renders are batched in more cases
* Events: `e.view` properly normalized
* Added Support for more HTML attributes (`coords`, `crossOrigin`, `download`, `hrefLang`, `mediaGroup`, `muted`, `scrolling`, `shape`, `srcSet`, `start`, `useMap`)
* Improved SVG support
* Changing `className` on a mounted SVG component now works correctly
* Added support for elements `mask` and `tspan`
* Added support for attributes `dx`, `dy`, `fillOpacity`, `fontFamily`, `fontSize`, `markerEnd`, `markerMid`, `markerStart`, `opacity`, `patternContentUnits`, `patternUnits`, `preserveAspectRatio`, `strokeDasharray`, `strokeOpacity`
* CSS property names with vendor prefixes (`Webkit`, `ms`, `Moz`, `O`) are now handled properly
* Duplicate keys no longer cause a hard error; now a warning is logged (and only one of the children with the same key is shown)
* `img` event listeners are now unbound properly, preventing the error "Two valid but unequal nodes with the same `data-reactid`"
* Added explicit warning when missing polyfills
### React With Addons
* PureRenderMixin
* Perf: a new set of tools to help with performance analysis
* Update: New `$apply` command to transform values
* TransitionGroup bug fixes with null elements, Android
### React NPM Module
* Now includes the pre-built packages under `dist/`.
* `envify` is properly listed as a dependency instead of a peer dependency
### JSX
* Added support for namespaces, eg `<Components.Checkbox />`
* JSXTransformer
* Enable the same `harmony` features available in the command line with `<script type="text/jsx;harmony=true">`
* Scripts are downloaded in parallel for more speed. They are still executed in order (as you would expect with normal script tags)
* Fixed a bug preventing sourcemaps from working in Firefox
### React Tools Module
* Improved readme with usage and API information
* Improved ES6 transforms available with `--harmony` option
* Added `--source-map-inline` option to the `jsx` executable
* New `transformWithDetails` API which gives access to the raw sourcemap data

View File

@@ -124,4 +124,4 @@ We'd like to thank all of our contributors:
</ul>
</div>
In addition, we're grateful to [Jeff Barczewski](https://github.com/jeffbski) for allowing us to use the `react` package name on npm.
In addition, we're grateful to [Jeff Barczewski](https://github.com/jeffbski) for allowing us to use the [react](https://www.npmjs.org/package/react) package name on npm and to [Christopher Aue](http://christopheraue.net/) for letting us use the [reactjs.com](http://reactjs.com/) domain name and the [@reactjs](https://twitter.com/reactjs) username on Twitter.

View File

@@ -20,7 +20,7 @@ sectionid: blog
<div class="pagination">
{% if paginator.previous_page %}
<a href="/react/{{ paginator.previous_page_path }}" class="previous">
<a href="/react{{ paginator.previous_page_path }}" class="previous">
&laquo; Previous Page
</a>
{% endif %}

View File

@@ -161,10 +161,20 @@ var content = Container(null, window.isLoggedIn ? Nav(null) : Login(null));
### Comments
It's easy to add comments within your JSX; they're just JS expressions:
It's easy to add comments within your JSX; they're just JS expressions. You just need to be careful to put `{}` around the comments when you are within the children section of a tag.
```javascript
var content = <Container>{/* this is a comment */}<Nav /></Container>;
var content = (
<Nav>
{/* child comment, put {} around */}
<Person
/* multi
line
comment */
name={window.isLoggedIn ? window.name : ''} // end of line comment
/>
</Nav>
);
```

View File

@@ -61,7 +61,7 @@ React.createClass({
// shown if the prop isn't provided.
requiredFunc: React.PropTypes.func.isRequired,
// An object of any kind
// A value of any data type
requiredAny: React.PropTypes.any.isRequired,
// You can also specify a custom validator.

View File

@@ -9,6 +9,7 @@ next: working-with-the-browser.html
Form components such as `<input>`, `<textarea>`, and `<option>` differ from other native components because they can be mutated via user interactions. These components provide interfaces that make it easier to manage forms in response to user interactions.
For information on events on `<form>` see [Form Events](/react/docs/events.html#form-events).
## Interactive Props

View File

@@ -126,6 +126,7 @@ In addition to that philosophy, we've also taken the stance that we, as authors
`es5-sham.js`, also from [kriskowal's es5-shim](https://github.com/kriskowal/es5-shim), provides the following that React needs:
* `Object.create`
* `Object.freeze`
The unminified build of React needs the following from [paulmillr's console-polyfill](https://github.com/paulmillr/console-polyfill).

View File

@@ -15,7 +15,7 @@ Consider the case when you wish to tell an `<input />` element (that exists with
getInitialState: function() {
return {userInput: ''};
},
handleKeyUp: function(e) {
handleChange: function(e) {
this.setState({userInput: e.target.value});
},
clearAndFocusInput: function() {
@@ -26,11 +26,11 @@ Consider the case when you wish to tell an `<input />` element (that exists with
return (
<div>
<div onClick={this.clearAndFocusInput}>
Click To Focus and Reset
Click to Focus and Reset
</div>
<input
value={this.state.userInput}
onKeyUp={this.handleKeyUp}
onChange={this.handleChange}
/>
</div>
);
@@ -72,17 +72,19 @@ React supports a very special property that you can attach to any component that
It's as simple as:
**1.** Assign a `ref` attribute to anything returned from `render` such as:
1. Assign a `ref` attribute to anything returned from `render` such as:
```html
<input ref="myInput" />
```
```html
<input ref="myInput" />
```
**2.** In some other code (typically event handler code), access the **backing instance** via `this.refs` as in:
2. In some other code (typically event handler code), access the **backing instance** via `this.refs` as in:
```javascript
```javascript
this.refs.myInput
```
```
You can access the component's DOM node directly by calling `this.refs.myInput.getDOMNode()`.
## Completing the Example
@@ -91,23 +93,26 @@ It's as simple as:
getInitialState: function() {
return {userInput: ''};
},
handleKeyUp: function(e) {
handleChange: function(e) {
this.setState({userInput: e.target.value});
},
clearAndFocusInput: function() {
this.setState({userInput: ''}); // Clear the input
this.refs.theInput.getDOMNode().focus(); // Boom! Focused!
// Clear the input
this.setState({userInput: ''}, function() {
// This code executes after the component is re-rendered
this.refs.theInput.getDOMNode().focus(); // Boom! Focused!
});
},
render: function() {
return (
<div>
<div onClick={this.clearAndFocusInput}>
Click To Focus and Reset
Click to Focus and Reset
</div>
<input
ref="theInput"
value={this.state.userInput}
onKeyUp={this.handleKeyUp}
onChange={this.handleChange}
/>
</div>
);
@@ -132,4 +137,4 @@ Refs are a great way to send a message to a particular child instance in a way t
- *Never* access refs inside of any component's render method - or while any component's render method is even running anywhere in the call stack.
- If you want to preserve Google Closure Compiler Crushing resilience, make sure to never access as a property what was specified as a string. This means you must access using `this.refs['myRefString']` if your ref was defined as `ref="myRefString"`.
- If you have not programmed several apps with React, your first inclination is usually going to be to try to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where `state` should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. Placing the state there often eliminates any desire to use `ref`s to "make things happen" - instead, the data flow will usually accomplish your goal.
- If you have not programmed several apps with React, your first inclination is usually going to be to try to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where `state` should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. Placing the state there often eliminates any desire to use `ref`s to "make things happen" instead, the data flow will usually accomplish your goal.

View File

@@ -38,14 +38,4 @@ If you have [npm](http://npmjs.org/), you can simply run `npm install -g react-t
### Helpful Open-Source Projects
The open-source community has built tools that integrate JSX with several build systems. See [JSX integrations](/react/docs/complementary-tools.html#jsx-integrations) for the full list.
### Syntax Highlighting & Linting
* Many editors already include reasonable support for JSX (Vim, Emacs js2-mode).
* [JSX syntax highlighting](https://github.com/yungsters/sublime/blob/master/tmLanguage/JavaScript%20(JSX\).tmLanguage) is available for Sublime Text and other editors
that support `*.tmLanguage`.
* [web-mode.el](http://web-mode.org) is an autonomous emacs major mode that indents and highlights JSX
* Linting provides accurate line numbers after compiling without sourcemaps.
* Elements use standard scoping so linters can find usage of out-of-scope components.
The open-source community has built tools that integrate JSX with several editors and build systems. See [JSX integrations](https://github.com/facebook/react/wiki/Complementary-Tools#jsx-integrations) for the full list.

View File

@@ -11,7 +11,9 @@ next: animation.html
- [`ReactTransitions`](animation.html), for dealing with animations and transitions that are usually not simple to implement, such as before a component's removal.
- [`ReactLink`](two-way-binding-helpers.html), to simplify the coordination between user's form input data and and the component's state.
- [`classSet`](class-name-manipulation.html), for manipulating the DOM `class` string a bit more cleanly.
- [`classSet()`](class-name-manipulation.html), for manipulating the DOM `class` string a bit more cleanly.
- [`ReactTestUtils`](test-utils.html), simple helpers for writing test cases (unminified build only).
- [`cloneWithProps()`](clone-with-props.html), to make shallow copies of React components and change their props.
- [`update()`](update.html), a helper function that makes dealing with immutable data in JavaScript easier.
To get the add-ons, use `react-with-addons.js` (and its minified counterpart) rather than the common `react.js`.

View File

@@ -17,7 +17,7 @@ React provides a `ReactTransitionGroup` addon component as a low-level API for a
`ReactCSSTransitionGroup` is the interface to `ReactTransitions`. This is a simple element that wraps all of the components you are interested in animating. Here's an example where we fade list items in and out.
```javascript{22-24}
```javascript{30-32}
/** @jsx React.DOM */
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
@@ -33,7 +33,7 @@ var TodoList = React.createClass({
},
handleRemove: function(i) {
var newItems = this.state.items;
newItems.splice(i, 1)
newItems.splice(i, 1);
this.setState({items: newItems});
},
render: function() {
@@ -46,7 +46,7 @@ var TodoList = React.createClass({
}.bind(this));
return (
<div>
<div><button onClick={this.handleAdd} /></div>
<button onClick={this.handleAdd}>Add Item</button>
<ReactCSSTransitionGroup transitionName="example">
{items}
</ReactCSSTransitionGroup>

View File

@@ -12,17 +12,18 @@ next: clone-with-props.html
### Simulate
```javascript
Simulate.{eventName}({ReactComponent|DOMElement} element, object eventData)
Simulate.{eventName}(DOMElement element, object eventData)
```
Simulate an event dispatch on a React component instance or browser DOM node with optional `eventData` event data. **This is possibly the single most useful utility in `ReactTestUtils`.**
Simulate an event dispatch on a DOM node with optional `eventData` event data. **This is possibly the single most useful utility in `ReactTestUtils`.**
Example usage:
```javascript
React.addons.TestUtils.Simulate.click(myComponent);
React.addons.TestUtils.Simulate.change(myComponent);
React.addons.TestUtils.Simulate.keydown(myComponent, {key: "Enter"});
var node = this.refs.input.getDOMNode();
React.addons.TestUtils.Simulate.click(node);
React.addons.TestUtils.Simulate.change(node);
React.addons.TestUtils.Simulate.keyDown(node, {key: "Enter"});
```
`Simulate` has a method for every event that React understands.
@@ -94,7 +95,7 @@ Traverse all components in `tree` and accumulate all components where `test(comp
### scryRenderedDOMComponentsWithClass
```javascript
array scryRenderedDOMComponentsWithClass(ReactCompoennt tree, string className)
array scryRenderedDOMComponentsWithClass(ReactComponent tree, string className)
```
Finds all instance of components in the rendered tree that are DOM components with the class name matching `className`.

View File

@@ -4,6 +4,7 @@ title: Cloning Components
layout: docs
permalink: clone-with-props.html
prev: test-utils.html
next: update.html
---
In rare situations a component may want to change the props of a component that it doesn't own (like changing the `className` of a component passed as `this.props.children`). Other times it may want to make multiple copies of a component passed to it. `cloneWithProps()` makes this possible.

62
docs/docs/09.6-update.md Normal file
View File

@@ -0,0 +1,62 @@
---
id: update
title: Immutability Helpers
layout: docs
permalink: update.html
prev: clone-with-props.html
---
React lets you use whatever style of data management you want, including mutation. However, if you can use immutable data in performance-critical parts of your application it's easy to implement a fast `shouldComponentUpdate()` method to significantly speed up your app.
Dealing with immutable data in JavaScript is more difficult than in languages designed for it, like [Clojure](http://clojure.org/). However, we've provided a simple immutability helper, `update()`, that makes dealing with this type of data much easier, *without* fundamentally changing how your data is represented.
## The main idea
If you mutate data like this:
```javascript
myData.x.y.z = 7;
myData.a.b.push(9);
```
you have no way of determining which data has changed since the previous copy is destroyed. Instead, you need to create a new copy of `myData` and change only the parts of it that need to be changed. Then you can compare the old copy of `myData` with the new one in `shouldComponentUpdate()` using triple-equals:
```javascript
var newData = deepCopy(myData);
newData.x.y.z = 7;
newData.a.b.push(9);
```
Unfortunately, deep copies are expensive, and sometimes impossible. You can alleviate this by only copying objects that need to be changed and by reusing the objects that haven't changed. Unfortunately, in today's JavaScript this can be cumbersome:
```javascript
var newData = extend(myData, {
x: extend(myData.x, {
y: extend(myData.x.y, {z: 7}),
}),
a: extend(myData.a, {b: myData.a.b.concat(9)})
});
```
While this is fairly performant (since it only shallow copies `log n` objects and reuses the rest), it's a big pain to write. Look at all the repetition! This is not only annoying, but also provides a large surface area for bugs.
`update()` provides simple syntactic sugar around this pattern to make writing this code easier. This code becomes:
```javascript
var newData = React.addons.update(myData, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [7]}}
});
```
While the syntax takes a little getting used to (though it's inspired by [MongoDB's query language](http://docs.mongodb.org/manual/core/crud-introduction/#query)) there's no redundancy, it's statically analyzable and it's not much more typing than the mutative version.
The `$`-prefixed keys are called *directives*. The data structure they are "mutating" is called the *target*.
## Available directives
* `{$push: array}` `push()` all the items in `array` on the target
* `{$unshift: array}` `unshift()` all the items in `array` on the target
* `{$splice: array of arrays}` for each item in `array()` call `splice()` on the target with the parameters provided by the item.
* `{$set: any}` replace the target entirely
* `{$merge: object}` merge the keys of `object` with the target

View File

@@ -7,54 +7,4 @@ prev: videos.html
next: examples.html
---
React is a small library that does one thing well. Here's a list of tools we've found that work really well with React when building applications.
If you want your project on this list, or think one of these projects should be removed, [open an issue on GitHub](https://github.com/facebook/react/issues/new).
### JSX integrations
* **[jsxhint](https://npmjs.org/package/jsxhint)** [JSHint](http://jshint.com/) (linting) support.
* **[reactify](https://npmjs.org/package/reactify)** [Browserify](http://browserify.org/) transform.
* **[node-jsx](https://npmjs.org/package/node-jsx)** Native [Node](http://nodejs.org/) support.
* **[jsx-loader](https://npmjs.org/package/jsx-loader)** Loader for [webpack](http://webpack.github.io/).
* **[grunt-react](https://npmjs.org/package/grunt-react)** [GruntJS](http://gruntjs.com/) task.
* **[gulp-react](https://npmjs.org/package/gulp-react)** [GulpJS](http://gulpjs.com/) plugin.
* **[jsx-requirejs-plugin](https://github.com/philix/jsx-requirejs-plugin)** [RequireJS](http://requirejs.org/) plugin.
* **[react-meteor](https://github.com/benjamn/react-meteor)** [Meteor](http://www.meteor.com/) plugin.
* **[pyReact](https://github.com/facebook/react-python)** [Python](http://www.python.org/) bridge to JSX.
* **[react-rails](https://github.com/facebook/react-rails)** Ruby gem for using JSX with [Ruby on Rails](http://rubyonrails.org/).
### Full-stack starter kits
* **[react-quickstart](https://github.com/andreypopp/react-quickstart)** Quick-start template for `express`, `browserify`, `react-router-component` and `react-async` (**includes "isomorphic" server rendering**).
* **[generator-react-webpack](https://github.com/newtriks/generator-react-webpack)** [Yeoman](http://yeoman.io/) generator for React and Webpack.
* **[Genesis Skeleton](http://genesis-skeleton.com/)** Modern, opinionated, full-stack starter kit for rapid, streamlined application development (supports React).
* **[react-starter-template](https://github.com/johnthethird/react-starter-template)** Starter template with Gulp, Webpack and Bootstrap.
* **[react-brunch](https://npmjs.org/package/react-brunch)** [Brunch](http://brunch.io/) plugin.
* **[react-browserify-template](https://github.com/petehunt/react-browserify-template)** Quick-start with Browserify.
### Routing
* **[director](https://github.com/flatiron/director)** (For an example see [TodoMVC](https://github.com/tastejs/todomvc/blob/gh-pages/architecture-examples/react/js/app.jsx#L29)).
* **[Backbone](http://backbonejs.org/)** (For an example see [github-issues-viewer](https://github.com/jaredly/github-issues-viewer)).
* **[react-router](https://github.com/jaredly/react-router)** (Example coming soon).
* **[react-router-component](http://andreypopp.viewdocs.io/react-router-component)**
### Model management
* **[react.backbone](https://github.com/usepropeller/react.backbone)** Use [Backbone](http://backbonejs.org) models with React.
* **[cortex](https://github.com/mquan/cortex/)** A JavaScript library for centrally managing data with React.
* **[avers](https://github.com/wereHamster/avers)** A modern client-side model abstraction library.
### Data fetching
* **[react-async](http://andreypopp.viewdocs.io/react-async)** Adds a `getInitialStateAsync(cb)` method suitable for data fetching on both the client and the server.
* **[superagent](http://visionmedia.github.io/superagent/)** A lightweight "isomorphic" library for AJAX requests.
### UI components
* **[react-bootstrap](https://github.com/stevoland/react-bootstrap)** Bootstrap 3 components built with React.
* **[react-topcoat](https://github.com/plaxdan/react-topcoat)** Topcoat components built with React.
* **[react-lorem-component](https://github.com/martinandert/react-lorem-component)** Lorem Ipsum placeholder component.
* **[wingspan-forms](https://github.com/wingspan/wingspan-forms)** React library for dynamic forms & grids; widgets provided by KendoUI.
* **[react-translate-component](https://github.com/martinandert/react-translate-component)** React component for i18n.
This page has moved to the [GitHub wiki](https://github.com/facebook/react/wiki/Complementary-Tools).

View File

@@ -11,7 +11,6 @@ prev: complementary-tools.html
* **[Instagram.com](http://instagram.com/)** is 100% built on React, both public site and internal tools.
* **[Facebook.com](http://www.facebook.com/)**'s commenting interface, business management tools, [Lookback video editor](http://facebook.com/lookback/edit), page insights, and most, if not all, new JS development.
* **[Khan Academy](http://khanacademy.org/)** uses React for most new JS development.
* **[Shirtstarter](https://www.shirtstarter.com/)** is 100% built on React.
* **[Sberbank](http://sberbank.ru/moscow/ru/person/)**, Russia's number one bank, is built with React.
* **[The New York Times's 2014 Red Carpet Project](http://www.nytimes.com/interactive/2014/02/02/fashion/red-carpet-project.html?_r=0)** is built with React.

View File

@@ -0,0 +1,97 @@
---
id: flux-overview
title: Flux Application Architecture
layout: docs
next: flux-todo-list.html
---
Flux is the application architecture that Facebook uses for building client-side web applications. It complements React's composable view components by utilizing a unidirectional data flow. It's more of a pattern rather than a formal framework, and you can start using Flux immediately without a lot of new code.
<figure><iframe width="560" height="315" src="//www.youtube.com/embed/nYkdrAPrdcw?list=PLb0IAmt7-GS188xDYE-u1ShQmFFGbrk0v" frameborder="0" allowfullscreen></iframe></figure>
Flux applications have three major parts: the dispatcher, the stores, and the views (React components). These should not be confused with Model-View-Controller. Controllers do exist in a Flux application, but they are controller-views — views often found at the top of the hierarchy that retrieve data from the stores and pass this data down to their children. Additionally, actions — dispatcher helper methods — are often used to support a semantic dispatcher API. It can be useful to think of them as a fourth part of the Flux update cycle.
Flux eschews MVC in favor of a unidirectional data flow. When a user interacts with a React view, the view propagates an action through a central dispatcher, to the various stores that hold the application's data and business logic, which updates all of the views that are affected. This works especially well with React's declarative programming style, which allows the store to send updates without specifying how to transition views between states.
We originally set out to deal correctly with derived data: for example, we wanted to show an unread count for message threads while another view showed a list of threads, with the unread ones highlighted. This was difficult to handle with MVC — marking a single thread as read would update the thread model, and then also need to update the unread count model. These dependencies and cascading updates often occur in a large MVC application, leading to a tangled weave of data flow and unpredictable results.
Control is inverted with stores: the stores accept updates and reconcile them as appropriate, rather than depending on something external to update its data in a consistent way. Nothing outside the store has any insight into how it manages the data for its domain, helping to keep a clear separation of concerns. This also makes stores more testable than models, especially since stores have no direct setter methods like `setAsRead()`, but instead have only an input point for the payload, which is delivered through the dispatcher and originates with actions.
## Structure and Data Flow
Data in a Flux application flows in a single direction, in a cycle:
<pre>
Views ---> (actions) ----> Dispatcher ---> (registered callback) ---> Stores -------+
Ʌ |
| V
+-- (Controller-Views "change" event handlers) ---- (Stores emit "change" events) --+
</pre>
A unidirectional data flow is central to the Flux pattern, and in fact Flux takes its name from the Latin word for flow. In the above diagram, the dispatcher, stores and views are independent nodes with distinct inputs and outputs. The actions are simply discrete, semantic helper functions that facilitate passing data to the dispatcher.
All data flows through the dispatcher as a central hub. Actions most often originate from user interactions with the views, and are nothing more than a call into the dispatcher. The dispatcher then invokes the callbacks that the stores have registered with it, effectively dispatching the data payload contained in the actions to all stores. Within their registered callbacks, stores determine which actions they are interested in, and respond accordingly. The stores then emit a "change" event to alert the controller-views that a change to the data layer has occurred. Controller-views listen for these events and retrieve data from the stores in an event handler. The controller-views call their own `render()` method via `setState()` or `forceUpdate()`, updating themselves and all of their children.
This structure allows us to reason easily about our application in a way that is reminiscent of functional reactive programming, or more specifically data-flow programming or flow-based programming, where data flows through the application in a single direction — there are no two-way bindings. Application state is maintained only in the stores, allowing the different parts of the application to remain highly decoupled. Where dependencies do occur between stores, they are kept in a strict hierarchy, with synchronous updates managed by the dispatcher.
We found that two-way data bindings led to cascading updates, where changing one object led to another object changing, which could also trigger more updates. As applications grew, these cascading updates made it very difficult to predict what would change as the result of one user interaction. When updates can only change data within a single round, the system as a whole becomes more predictable.
Let's look at the various parts of the Flux update cycle up close. A good place to start is the dispatcher.
### A Single Dispatcher
The dispatcher is the central hub that manages all data flow in a Flux application. It is essentially a registry of callbacks into the stores. Each store registers itself and provides a callback. When the dispatcher responds to an action, all stores in the application are sent the data payload provided by the action via the callbacks in the registry.
As an application grows, the dispatcher becomes more vital, as it can manage dependencies between stores by invoking the registered callbacks in a specific order. Stores can declaratively wait for other stores to finish updating, and then update themselves accordingly.
### Stores
Stores contain the application state and logic. Their role is somewhat similar to a model in a traditional MVC, but they manage the state of many objects — they are not instances of one object. Nor are they the same as Backbone's collections. More than simply managing a collection of ORM-style objects, stores manage the application state for a particular __domain__ within the application.
For example, Facebook's [Lookback Video Editor](https://facebook.com/lookback/edit) utilized a TimeStore that kept track of the playback time position and the playback state. On the other hand, the same application's ImageStore kept track of a collection of images. The TodoStore in our [TodoMVC example](https://github.com/facebook/react/tree/master/examples/todomvc-flux) is similar in that it manages a collection of to-do items. A store exhibits characteristics of both a collection of models and a singleton model of a logical domain.
As mentioned above, a store registers itself with the dispatcher and provides it with a callback. This callback receives the action's data payload as a parameter. The payload contains a type attribute, identifying the action's type. Within the store's registered callback, a switch statement based on the action's type is used to interpret the payload and to provide the proper hooks into the store's internal methods. This allows an action to result in an update to the state of the store, via the dispatcher. After the stores are updated, they broadcast an event declaring that their state has changed, so the views may query the new state and update themselves.
### Views and Controller-Views
React provides the kind of composable views we need for the view layer. Close to the top of the nested view hierarchy, a special kind of view listens for events that are broadcast by the stores that it depends on. One could call this a controller-view, as it provides the glue code to get the data from the stores and to pass this data down the chain of its descendants. We might have one of these controller-views governing any significant section of the page.
When it receives the event from the store, it first requests the new data it needs via the stores' public getter methods. It then calls its own `setState()` or `forceUpdate()` methods, causing its `render()` method and the `render()` method of all its descendants to run.
We often pass the entire state of the store down the chain of views in a single object, allowing different descendants to use what they need. In addition to keeping the controller-like behavior at the top of the hierarchy, and thus keeping our descendant views as functionally pure as possible, passing down the entire state of the store in a single object also has the effect of reducing the number of props we need to manage.
Occasionally we may need to add additional controller-views deeper in the hierarchy to keep components simple. This might help us to better encapsulate a section of the hierarchy related to a specific data domain. Be aware, however, that controller-views deeper in the hierarchy can violate the singular flow of data by introducing a new, potentially conflicting entry point for the data flow. In making the decision of whether to add a deep controller-view, balance the gain of simpler components against the complexity of multiple data updates flowing into the hierarchy at different points. These multiple data updates can lead to odd effects, with React's render method getting invoked repeatedly by updates from different controller-views, potentially increasing the difficulty of debugging.
### Actions
The dispatcher exposes a method that allows a view to trigger a dispatch to the stores, and to include a payload of data, or an action. The action construction may be wrapped into a semantic helper method which sends the payload to the dispatcher. For example, we may want to change the text of a to-do item in a to-do list application. We would create an action with a function signature like `updateText(todoId, newText)` in our `TodoActions` module. This method may be invoked from within our views' event handlers, so we can call it in response to a user action. This action method also adds the action type to the payload, so that when the payload is interpreted in the store, it can respond appropriately to a payload with a particular action type. In our example, this type might be named something like `TODO_UPDATE_TEXT`.
Actions may also come from other places, such as the server. This happens, for example, during data initialization. It may also happen when the server returns an error code or when the server has updates to provide to the application. We'll talk more about server actions in a future article. In this post we're only concerned with the basics of the data flow.
### What About that Dispatcher?
As mentioned earlier, the dispatcher is also able to manage dependencies between stores. This functionality is available through the `waitFor()` method within the Dispatcher class. We did not need to use this method within the extremely simple [TodoMVC application](https://github.com/facebook/react/tree/master/examples/todomvc-flux), but we have included it [in the example dispatcher](https://github.com/facebook/react/blob/master/examples/todomvc-flux/js/dispatcher/Dispatcher.js#L110) as an example of what a dispatcher should be able to do in a larger, more complex application.
Within the TodoStore's registered callback we could explicitly wait for any dependencies to first update before moving forward:
```javascript
case 'TODO_CREATE':
Dispatcher.waitFor([
PrependedTextStore.dispatcherIndex,
YetAnotherStore.dispatcherIndex
], function() {
TodoStore.create(PrependedTextStore.getText() + ' ' + action.text);
TodoStore.emit('change');
});
break;
```
The arguments for `waitFor()` are an array of dipatcher registry indexes, and a final callback to invoke after the callbacks at the given indexes have completed. Thus the store that is invoking `waitFor()` can depend on the state of another store to inform how it should update its own state.
A problem arises if we create circular dependencies. If Store A waits for Store B, and B waits for A, then we'll have a very bad situation on our hands. We'll need a more robust dispatcher that flags these circular dependencies with console errors, and this is not easily accomplished with promises. Unfortunately, that's a bit beyond the scope of this documentation. In the future we hope to cover how to build a more robust dispatcher and how to initialize, update, and save the state of the application with persistent data, like a web service API.

608
docs/docs/flux-todo-list.md Normal file
View File

@@ -0,0 +1,608 @@
---
id: flux-todo-list
title: Flux TodoMVC Tutorial
layout: docs
prev: flux-overview.html
---
To demonstrate the Flux architecture with some example code, let's take on the classic TodoMVC application. The entire application is available in the React GitHub repo within the [todomvc-flux](https://github.com/facebook/react/tree/master/examples/todomvc-flux) example directory, but let's walk through the development of it a step at a time.
To begin, we'll need some boilerplate and get up and running with a module system. Node's module system, based on CommonJS, will fit the bill very nicely and we can build off of [react-boilerplate](https://github.com/petehunt/react-boilerplate) to get up and running quickly. Assuming you have npm installed, simply clone the react-boilerplate code from GitHub, and navigate into the resulting directory in Terminal (or whatever CLI application you like). Next run the npm scripts to get up and running: `npm install`, then `npm run build`, and lastly `npm start` to continuously build using Browserify.
The TodoMVC example has all this built into it as well, but if you're starting with react-boilerplate make sure you edit your package.json file to match the file structure and dependencies described in the TodoMVC example's [package.json](https://github.com/facebook/react/tree/master/examples/todomvc-flux/package.json), or else your code won't match up with the explanations below.
Source Code Structure
---------------------
The resulting index.js file may be used as the entry point into our app, but we'll put most of our code in a 'js' directory. Let's let Browserify do its thing, and now we'll open a new tab in Terminal (or a GUI file browser) to look at the directory. It should look something like this:
```
myapp
|
+ ...
+ js
|
+ app.js
+ bundle.js // generated by Browserify whenever we make changes.
+ index.html
+ ...
```
Next we'll dive into the js directory, and layout our application's primary directory structure:
```
myapp
|
+ ...
+ js
|
+ actions
+ components // all React components, both views and controller-views
+ constants
+ dispatcher
+ stores
+ app.js
+ bundle.js
+ index.html
+ ...
```
Creating a Dispatcher
---------------------
Now we are ready to create a dispatcher. Here is an naive example of a Dispatcher class, written with JavaScript promises, polyfilled with Jake Archibald's [ES6-Promises](https://github.com/jakearchibald/ES6-Promises) module.
```javascript
var Promise = require('es6-promise').Promise;
var merge = require('react/lib/merge');
var _callbacks = [];
var _promises = [];
/**
* Add a promise to the queue of callback invocation promises.
* @param {function} callback The Store's registered callback.
* @param {object} payload The data from the Action.
*/
var _addPromise = function(callback, payload) {
_promises.push(new Promise(function(resolve, reject) {
if (callback(payload)) {
resolve(payload);
} else {
reject(new Error('Dispatcher callback unsuccessful'));
}
}));
};
/**
* Empty the queue of callback invocation promises.
*/
var _clearPromises = function() {
_promises = [];
};
var Dispatcher = function() {};
Dispatcher.prototype = merge(Dispatcher.prototype, {
/**
* Register a Store's callback so that it may be invoked by an action.
* @param {function} callback The callback to be registered.
* @return {number} The index of the callback within the _callbacks array.
*/
register: function(callback) {
_callbacks.push(callback);
return _callbacks.length - 1; // index
},
/**
* dispatch
* @param {object} payload The data from the action.
*/
dispatch: function(payload) {
_callbacks.forEach(function(callback) {
_addPromise(callback, payload);
});
Promise.all(_promises).then(_clearPromises);
}
});
module.exports = Dispatcher;
```
The public API of this basic Dispatcher consists of only two methods: register() and dispatch(). We'll use register() within our stores to register each store's callback. We'll use dispatch() within our actions to trigger the invocation of the callbacks.
Now we are all set to create a dispatcher that is more specific to our app, which we'll call AppDispatcher.
```javascript
var Dispatcher = require('./Dispatcher');
var merge = require('react/lib/merge');
var AppDispatcher = merge(Dispatcher.prototype, {
/**
* A bridge function between the views and the dispatcher, marking the action
* as a view action. Another variant here could be handleServerAction.
* @param {object} action The data coming from the view.
*/
handleViewAction: function(action) {
this.dispatch({
source: 'VIEW_ACTION',
action: action
});
}
});
module.exports = AppDispatcher;
```
Now we've created an implementation that is a bit more specific to our needs, with a helper function we can use in the actions coming from our views' event handlers. We might expand on this later to provide a separate helper for server updates, but for now this is all we need.
Creating Stores
----------------
We can use Node's EventEmitter to get started with a store. We need EventEmitter to broadcast the 'change' event to our controller-views. So let's take a look at what that looks like. I've omitted some of the code for the sake of brevity, but for the full version see [TodoStore.js](https://github.com/Facebook/react/blob/master/examples/todomvc-flux/js/stores/TodoStore.js) in the TodoMVC example code.
```javascript
var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var TodoConstants = require('../constants/TodoConstants');
var merge = require('react/lib/merge');
var CHANGE_EVENT = 'change';
var _todos = {}; // collection of todo items
/**
* Create a TODO item.
* @param {string} text The content of the TODO
*/
function create(text) {
// Using the current timestamp in place of a real id.
var id = Date.now();
_todos[id] = {
id: id,
complete: false,
text: text
};
}
/**
* Delete a TODO item.
* @param {string} id
*/
function destroy(id) {
delete _todos[id];
}
var TodoStore = merge(EventEmitter.prototype, {
/**
* Get the entire collection of TODOs.
* @return {object}
*/
getAll: function() {
return _todos;
},
emitChange: function() {
this.emit(CHANGE_EVENT);
},
/**
* @param {function} callback
*/
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
/**
* @param {function} callback
*/
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
dispatcherIndex: AppDispatcher.register(function(payload) {
var action = payload.action;
var text;
switch(action.actionType) {
case TodoConstants.TODO_CREATE:
text = action.text.trim();
if (text !== '') {
create(text);
TodoStore.emitChange();
}
break;
case TodoConstants.TODO_DESTROY:
destroy(action.id);
TodoStore.emitChange();
break;
// add more cases for other actionTypes, like TODO_UPDATE, etc.
}
return true; // No errors. Needed by promise in Dispatcher.
})
});
module.exports = TodoStore;
```
There are a few important things to note in the above code. To start, we are maintaining a private data structure called _todos. This object contains all the individual to-do items. Because this variable lives outside the class, but within the closure of the module, it remains private — it cannot be directly changed from the outside. This helps us preserve a distinct input/output interface for the flow of data by making it impossible to update the store without using an action.
Another important part is the registration of the store's callback with the dispatcher. We pass in our payload handling callback to the dispatcher and preserve the index that this store has in the dispatcher's registry. The callback function currently only handles one actionType, but later we can add as many as we need.
Listening to Changes with a Controller-View
-------------------------------------------
We need a React component near the top of our component hierarchy to listen for changes in the store. In a larger app, we would have more of these listening components, perhaps one for every section of the page. In Facebook's Ads Creation Tool, we have many of these controller-like views, each governing a specific section of the UI. In the Lookback Video Editor, we only had two: one for the animated preview and one for the image selection interface. Here's one for our TodoMVC example. Again, this is slightly abbreviated, but for the full code you can take a look at the TodoMVC example's [TodoApp.react.js](https://github.com/facebook/react/blob/master/examples/todomvc-flux/js/components/TodoApp.react.js)
```javascript
/** @jsx React.DOM */
var Footer = require('./Footer.react');
var Header = require('./Header.react');
var MainSection = require('./MainSection.react');
var React = require('react');
var TodoStore = require('../stores/TodoStore');
function getTodoState() {
return {
allTodos: TodoStore.getAll()
};
}
var TodoApp = React.createClass({
getInitialState: function() {
return getTodoState();
},
componentDidMount: function() {
TodoStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
TodoStore.removeChangeListener(this._onChange);
},
/**
* @return {object}
*/
render: function() {
return (
<div>
<Header />
<MainSection
allTodos={this.state.allTodos}
areAllComplete={this.state.areAllComplete}
/>
<Footer allTodos={this.state.allTodos} />
</div>
);
},
_onChange: function() {
this.setState(getTodoState());
}
});
module.exports = TodoApp;
```
Now we're in our familiar React territory, utilizing React's lifecycle methods. We set up the initial state of this controller-view in getInitialState(), register an event listener in componentDidMount(), and then clean up after ourselves within componentWillUnmount(). We render a containing div and pass down the collection of states we got from the TodoStore.
The Header component contains the primary text input for the application, but it does not need to know the state of the store. The MainSection and Footer do need this data, so we pass it down to them.
More Views
----------
At a high level, the React component hierarchy of the app looks like this:
```javascript
<TodoApp>
<Header>
<TodoTextInput />
<MainSection>
<ul>
<TodoItem />
</ul>
</MainSection>
</TodoApp>
```
If a TodoItem is in edit mode, it will also render a TodoTextInput as a child. Let's take a look at how some of these components display the data they receive as props, and how they communicate through actions with the dispatcher.
The MainSection needs to iterate over the collection of to-do items it received from TodoApp to create the list of TodoItems. In the component's render() method, we can do that iteration like so:
```javascript
var allTodos = this.props.allTodos;
for (var key in allTodos) {
todos.push(<TodoItem key={key} todo={allTodos[key]} />);
}
return (
<section id="main">
<ul id="todo-list">{todos}</ul>
);
```
Now each TodoItem can display it's own text, and perform actions utilizing it's own ID. Explaining all the different actions that a TodoItem can invoke in the TodoMVC example goes beyond the scope of this article, but let's just take a look at the action that deletes one of the to-do items. Here is an abbreviated version of the TodoItem:
```javascript
/** @jsx React.DOM */
var React = require('react');
var TodoActions = require('../actions/TodoActions');
var TodoTextInput = require('./TodoTextInput.react');
var TodoItem = React.createClass({
propTypes: {
todo: React.PropTypes.object.isRequired
},
render: function() {
var todo = this.props.todo;
return (
<li
key={todo.id}>
<label>
{todo.text}
</label>
<button className="destroy" onClick={this._onDestroyClick} />
</li>
);
},
_onDestroyClick: function() {
TodoActions.destroy(this.props.todo.id);
}
});
module.exports = TodoItem;
```
With a destroy action available in our library of TodoActions, and a store ready to handle it, connecting the user's interaction with application state changes could not be simpler. We just wrap our onClick handler around the destroy action, provide it with the id, and we're done. Now the user can click the destroy button and kick off the Flux cycle to update the rest of the application.
Text input, on the other hand, is just a bit more complicated because we need to hang on to the state of the text input within the React component itself. Let's take a look at how TodoTextInput works.
As you'll see below, with every change to the input, React expects us to update the state of the component. So when we are finally ready to save the text inside the input, we will put the value held in the component's state in the action's payload. This is UI state, rather than application state, and keeping that distinction in mind is a good guide for where state should live. All application state should live in the store, while components occasionally hold on to UI state. Ideally, React components preserve as little state as possible.
Because TodoTextInput is being used in multiple places within our application, with different behaviors, we'll need to pass the onSave method in as a prop from the component's parent. This allows onSave to invoke different actions depending on where it is used.
```javascript
/** @jsx React.DOM */
var React = require('react');
var ReactPropTypes = React.PropTypes;
var ENTER_KEY_CODE = 13;
var TodoTextInput = React.createClass({
propTypes: {
className: ReactPropTypes.string,
id: ReactPropTypes.string,
placeholder: ReactPropTypes.string,
onSave: ReactPropTypes.func.isRequired,
value: ReactPropTypes.string
},
getInitialState: function() {
return {
value: this.props.value || ''
};
},
/**
* @return {object}
*/
render: function() /*object*/ {
return (
<input
className={this.props.className}
id={this.props.id}
placeholder={this.props.placeholder}
onBlur={this._save}
onChange={this._onChange}
onKeyDown={this._onKeyDown}
value={this.state.value}
autoFocus={true}
/>
);
},
/**
* Invokes the callback passed in as onSave, allowing this component to be
* used in different ways.
*/
_save: function() {
this.props.onSave(this.state.value);
this.setState({
value: ''
});
},
/**
* @param {object} event
*/
_onChange: function(/*object*/ event) {
this.setState({
value: event.target.value
});
},
/**
* @param {object} event
*/
_onKeyDown: function(event) {
if (event.keyCode === ENTER_KEY_CODE) {
this._save();
}
}
});
module.exports = TodoTextInput;
```
The Header passes in the onSave method as a prop to allow the TodoTextInput to create new
to-do items:
```javascript
/** @jsx React.DOM */
var React = require('react');
var TodoActions = require('../actions/TodoActions');
var TodoTextInput = require('./TodoTextInput.react');
var Header = React.createClass({
/**
* @return {object}
*/
render: function() {
return (
<header id="header">
<h1>todos</h1>
<TodoTextInput
id="new-todo"
placeholder="What needs to be done?"
onSave={this._onSave}
/>
</header>
);
},
/**
* Event handler called within TodoTextInput.
* Defining this here allows TodoTextInput to be used in multiple places
* in different ways.
* @param {string} text
*/
_onSave: function(text) {
TodoActions.create(text);
}
});
module.exports = Header;
```
In a different context, such as in editing mode for an existing to-do item, we might pass an onSave callback that invokes `TodoActions.update(text)` instead.
Creating Semantic Actions
-------------------------
Here is the basic code for the two actions we used above in our views:
```javascript
/**
* TodoActions
*/
var AppDispatcher = require('../dispatcher/AppDispatcher');
var TodoConstants = require('../constants/TodoConstants');
var TodoActions = {
/**
* @param {string} text
*/
create: function(text) {
AppDispatcher.handleViewAction({
actionType: TodoConstants.TODO_CREATE,
text: text
});
},
/**
* @param {string} id
*/
destroy: function(id) {
AppDispatcher.handleViewAction({
actionType: TodoConstants.TODO_DESTROY,
id: id
});
},
};
module.exports = TodoActions;
```
As you can see, we really would not need to have the helpers AppDispatcher.handleViewAction() or TodoActions.create(). We could, in theory, call AppDispatcher.dispatch() directly and provide a payload. But as our application grows, having these helpers keeps the code clean and semantic. It's just a lot cleaner to write TodoActions.destroy(id) instead of writing a whole lot of things that our TodoItem shouldn't have to know about.
The payload produced by the TodoActions.create() will look like:
```javascript
{
source: 'VIEW_ACTION',
action: {
type: 'TODO_CREATE',
text: 'Write blog post about Flux'
}
}
```
This payload is provided to the TodoStore through its registered callback. The TodoStore then broadcasts the 'change' event, and the MainSection responds by fetching the new collection of to-do items from the TodoStore and changing its state. This change in state causes the TodoApp component to call its own render() method, and the render() method of all of its descendents.
Start Me Up
-----------
The bootstrap file of our application is app.js. It simply takes the TodoApp component and renders it in the root element of the application.
```javascript
/** @jsx React.DOM */
var React = require('react');
var TodoApp = require('./components/TodoApp.react');
React.renderComponent(
<TodoApp />,
document.getElementById('todoapp')
);
```
Adding Dependency Management to the Dispatcher
----------------------------------------------
As I said previously, our Dispatcher implementation is a bit naive. It's pretty good, but it will not suffice for most applications. We need a way to be able to manage dependencies between Stores. Let's add that functionality with a waitFor() method within the main body of the Dispatcher class.
We'll need another public method, waitFor().
```javascript
/**
* @param {array} promisesIndexes
* @param {function} callback
*/
waitFor: function(promiseIndexes, callback) {
var selectedPromises = _promises.filter(function(/*object*/ _, /*number*/ j) {
return promiseIndexes.indexOf(j) !== -1;
});
Promise.all(selectedPromises).then(callback);
}
```
Now within the TodoStore callback we can explicitly wait for any dependencies to first update before moving forward. However, if Store A waits for Store B, and B waits for A, then a circular dependency will occur. A more robust dispatcher is required to flag this scenario with warnings in the console.
The Future of Flux
------------------
A lot of people ask if Facebook will release Flux as an open source framework. Really, Flux is just an architecture, not a framework. But perhaps a Flux boilerplate project might make sense, if there is enough interest. Please let us know if you'd like to see us do this.
Thanks for taking the time to read about how we build client-side applications at Facebook. We hope Flux proves as useful to you as it has to us.

View File

@@ -104,3 +104,12 @@ string renderComponentToString(ReactComponent component)
Render a component to its initial HTML. This should only be used on the server. React will return an HTML string. You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes.
If you call `React.renderComponent()` on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.
### React.renderComponentToStaticMarkup
```javascript
string renderComponentToStaticMarkup(ReactComponent component)
```
Similar to `renderComponentToString`, except this doesn't create extra DOM attributes such as `data-react-id`, that React uses internally. This is useful if you want to use React as a simple static page generator, as stripping away the extra attributes can save lots of bytes.

View File

@@ -9,7 +9,7 @@ next: component-specs.html
## ReactComponent
Component classes created by `createClass()` return instances of `ReactComponent` when called. Most of the time when you're using React you're either creating or consuming these component objects.
Component classes created by `React.createClass()` return instances of `ReactComponent` when called. Most of the time when you're using React you're either creating or consuming these component objects.
### getDOMNode
@@ -24,20 +24,24 @@ If this component has been mounted into the DOM, this returns the corresponding
### setProps
```javascript
setProps(object nextProps)
setProps(object nextProps[, function callback])
```
When you're integrating with an external JavaScript application you may want to signal a change to a React component rendered with `renderComponent()`. Simply call `setProps()` to change its properties and trigger a re-render.
When you're integrating with an external JavaScript application you may want to signal a change to a React component rendered with `React.renderComponent()`.
Though calling `React.renderComponent()` again on the same node is the preferred way to update a root-level component, you can also call `setProps()` to change its properties and trigger a re-render. In addition, you can supply an optional callback function that is executed once `setProps` is completed and the component is re-rendered.
> Note:
>
> This method can only be called on a root-level component. That is, it's only available on the component passed directly to `renderComponent()` and none of its children. If you're inclined to use `setProps()` on a child component, instead take advantage of reactive updates and pass the new prop to the child component when it's created in `render()`.
> When possible, the declarative approach of calling `React.renderComponent()` again is preferred; it tends to make updates easier to reason about. (There's no significant performance difference between the two approaches.)
>
> This method can only be called on a root-level component. That is, it's only available on the component passed directly to `React.renderComponent()` and none of its children. If you're inclined to use `setProps()` on a child component, instead take advantage of reactive updates and pass the new prop to the child component when it's created in `render()`.
### replaceProps
```javascript
replaceProps(object nextProps)
replaceProps(object nextProps[, function callback])
```
Like `setProps()` but deletes any pre-existing props instead of merging the two objects.
@@ -76,7 +80,7 @@ Properties that are specified directly on the target component instance (such as
setState(object nextState[, function callback])
```
Merges nextState with the current state. This is the primary method you use to trigger UI updates from event handlers and server request callbacks. In addition, you can supply an optional callback function that is executed once `setState` is completed.
Merges nextState with the current state. This is the primary method you use to trigger UI updates from event handlers and server request callbacks. In addition, you can supply an optional callback function that is executed once `setState` is completed and the component is re-rendered.
> Notes:
>

View File

@@ -72,6 +72,6 @@ There is also the React-specific attribute `dangerouslySetInnerHTML` ([more here
```
cx cy d fill fx fy gradientTransform gradientUnits offset points r rx ry
spreadMethod stopColor stopOpacity stroke strokeLinecap strokeWidth transform
spreadMethod stopColor stopOpacity stroke strokeLinecap strokeWidth textAnchor transform
version viewBox x1 x2 x y1 y2 y
```

View File

@@ -61,7 +61,7 @@ Now that we've identified the components in our mock, let's arrange them into a
## Step 2: Build a static version in React
<iframe width="100%" height="300" src="http://jsfiddle.net/6wQMG/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
<iframe width="100%" height="300" src="http://jsfiddle.net/n25pd/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
Now that you have your component hierarchy it's time to start implementing your app. The easiest way is to build a version that takes your data model and renders the UI but has no interactivity. It's easiest to decouple these processes because building a static version requires a lot of typing and no thinking, and adding interactivity requires a lot of thinking and not a lot of typing. We'll see why.
@@ -105,7 +105,7 @@ So finally, our state is:
## Step 4: Identify where your state should live
<iframe width="100%" height="300" src="http://jsfiddle.net/QvHnx/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
<iframe width="100%" height="300" src="http://jsfiddle.net/t53sx/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
OK, so we've identified what the minimal set of app state is. Next we need to identify which component mutates, or *owns*, this state.
@@ -130,7 +130,7 @@ You can start seeing how your application will behave: set `filterText` to `"bal
## Step 5: Add inverse data flow
<iframe width="100%" height="300" src="http://jsfiddle.net/3Vs3Q/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
<iframe width="100%" height="300" src="http://jsfiddle.net/F8H7p/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
So far we've built an app that renders correctly as a function of props and state flowing down the hierarchy. Now it's time to support data flowing the other way: the form components deep in the hierarchy need to update the state in `FilterableProductTable`.

View File

@@ -22,7 +22,7 @@ It'll also have a few neat features:
### Want to skip all this and just see the source?
[It's all on GitHub.](https://github.com/petehunt/react-tutorial)
[It's all on GitHub.](https://github.com/reactjs/react-tutorial)
### Getting started
@@ -40,9 +40,7 @@ For this tutorial we'll use prebuilt JavaScript files on a CDN. Open up your fav
<body>
<div id="content"></div>
<script type="text/jsx">
/**
* @jsx React.DOM
*/
/** @jsx React.DOM */
// The above declaration must remain intact at the top of the script.
// Your code here
</script>
@@ -91,10 +89,9 @@ The first thing you'll notice is the XML-ish syntax in your JavaScript. We have
var CommentBox = React.createClass({
render: function() {
return (
React.DOM.div({
className: 'commentBox',
children: 'Hello, world! I am a CommentBox.'
})
React.DOM.div({className: "commentBox"},
"Hello, world! I am a CommentBox."
)
);
}
});
@@ -304,12 +301,16 @@ React.renderComponent(
Now that the data is available in the `CommentList`, let's render the comments dynamically:
```javascript{4-6,9}
```javascript{4-10,13}
// tutorial10.js
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function (comment) {
return <Comment author={comment.author}>{comment.text}</Comment>;
return (
<Comment author={comment.author}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
@@ -411,7 +412,7 @@ var CommentBox = React.createClass({
Here, `componentWillMount` is a method called automatically by React before a component is rendered. The key to dynamic updates is the call to `this.setState()`. We replace the old array of comments with the new one from the server and the UI automatically updates itself. Because of this reactivity, it is only a minor change to add live updates. We will use simple polling here but you could easily use WebSockets or other technologies.
```javascript{3,16-17,31}
```javascript{3,19-20,34}
// tutorial14.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
@@ -420,6 +421,9 @@ var CommentBox = React.createClass({
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
@@ -471,7 +475,7 @@ var CommentForm = React.createClass({
Let's make the form interactive. When the user submits the form, we should clear it, submit a request to the server, and refresh the list of comments. To start, let's listen for the form's submit event and clear it.
```javascript{3-13,16-17,21}
```javascript{3-13,16-18}
// tutorial16.js
var CommentForm = React.createClass({
handleSubmit: function() {
@@ -489,11 +493,7 @@ var CommentForm = React.createClass({
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="text" placeholder="Your name" ref="author" />
<input
type="text"
placeholder="Say something..."
ref="text"
/>
<input type="text" placeholder="Say something..." ref="text" />
<input type="submit" value="Post" />
</form>
);
@@ -517,7 +517,7 @@ When a user submits a comment, we will need to refresh the list of comments to i
We need to pass data from the child component to its parent. We do this by passing a `callback` in props from parent to child:
```javascript{12-14,28}
```javascript{15-17,30}
// tutorial17.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
@@ -526,6 +526,9 @@ var CommentBox = React.createClass({
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
@@ -544,9 +547,7 @@ var CommentBox = React.createClass({
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm
onCommentSubmit={this.handleCommentSubmit}
/>
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
@@ -570,11 +571,7 @@ var CommentForm = React.createClass({
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="text" placeholder="Your name" ref="author" />
<input
type="text"
placeholder="Say something..."
ref="text"
/>
<input type="text" placeholder="Say something..." ref="text" />
<input type="submit" value="Post" />
</form>
);
@@ -584,7 +581,7 @@ var CommentForm = React.createClass({
Now that the callbacks are in place, all we have to do is submit to the server and refresh the list:
```javascript{13-21}
```javascript{16-27}
// tutorial19.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
@@ -593,6 +590,9 @@ var CommentBox = React.createClass({
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
@@ -604,6 +604,9 @@ var CommentBox = React.createClass({
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
@@ -619,9 +622,7 @@ var CommentBox = React.createClass({
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm
onCommentSubmit={this.handleCommentSubmit}
/>
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
@@ -632,7 +633,7 @@ var CommentBox = React.createClass({
Our application is now feature complete but it feels slow to have to wait for the request to complete before your comment appears in the list. We can optimistically add this comment to the list to make the app feel faster.
```javascript{13-15}
```javascript{16-18}
// tutorial20.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
@@ -641,6 +642,9 @@ var CommentBox = React.createClass({
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
@@ -655,6 +659,9 @@ var CommentBox = React.createClass({
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
@@ -670,9 +677,7 @@ var CommentBox = React.createClass({
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm
onCommentSubmit={this.handleCommentSubmit}
/>
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}

View File

@@ -13,6 +13,51 @@ next: complementary-tools.html
"At Facebook and Instagram, were trying to push the limits of whats possible on the web with React. My talk will start with a brief introduction to the framework, and then dive into three controversial topics: Throwing out the notion of templates and building views with JavaScript, “re-rendering” your entire application when your data changes, and a lightweight implementation of the DOM and events." -- [Pete Hunt](http://www.petehunt.net/)
### Thinking in react - tagtree.tv
A [tagtree.tv](http://tagtree.tv/) video conveying the principles of [Thinking in React](/react/docs/thinking-in-react.html) while building a simple app
<figure>[![](/react/img/docs/thinking-in-react-tagtree.png)](http://tagtree.tv/thinking-in-react)</figure>
### Secrets of the Virtual DOM - MtnWest JS
<iframe width="560" height="315" src="//www.youtube.com/embed/h3KksH8gfcQ" frameborder="0" allowfullscreen></iframe>
"In this talk Ill be discussing why we built a virtual DOM, how it compares to other systems, and its relevance to the future of browser technologies." -- [Pete Hunt](http://www.petehunt.net/)
### CodeWinds
[Pete Hunt](http://www.petehunt.net/) talked with [Jeff Barczewski](http://jeff.barczewski.com/) about React in CodeWinds Episode 4.
<figure>[![](/react/img/docs/codewinds-004.png)](http://codewinds.com/4)</figure>
<table width="100%"><tr><td>
02:08 - What is React and why use it?<br />
03:08 - The symbiotic relationship of ClojureScript and React<br />
04:54 - The history of React and why it was created<br />
09:43 - Updating web page with React without using data binding<br />
13:11 - Using the virtual DOM to change the browser DOM<br />
13:57 - Programming with React, render targets HTML, canvas, other<br />
16:45 - Working with designers. Contrasted with Ember and AngularJS<br />
21:45 - JSX Compiler bridging HTML and React javascript<br />
23:50 - Autobuilding JSX and in browser tools for React<br />
24:50 - Tips and tricks to working with React, getting started<br />
</td><td>
27:17 - Rendering HTML on the server with Node.js. Rendering backends<br />
29:20 - React evolved through survival of the fittest at Facebook<br />
30:15 - Ideas for having state on server and client, using web sockets.<br />
32:05 - React-multiuser - distributed shared mutable state using Firebase<br />
33:03 - Better debugging with React using the state transitions, replaying events<br />
34:08 - Differences from Web Components<br />
34:25 - Notable companies using React<br />
35:16 - Could a React backend plugin be created to target PDF?<br />
36:30 - Future of React, what's next?<br />
39:38 - Contributing and getting help<br />
</td></tr></table>
[Read the episode notes](http://codewinds.com/4)
### JavaScript Jabber
[Pete Hunt](http://www.petehunt.net/) and [Jordan Walke](https://github.com/jordwalke) talked about React in JavaScript Jabber 73.
@@ -66,3 +111,7 @@ by [Stoyan Stefanov](http://www.phpied.com/)
### "Functional DOM programming" - Meteor DevShop 11
<iframe width="650" height="315" src="//www.youtube.com/embed/qqVbr_LaCIo" frameborder="0" allowfullscreen></iframe>
### "Rethinking Web App Development at Facebook" - Facebook F8 Conference 2014
<iframe width="650" height="315" src="//www.youtube.com/embed/nYkdrAPrdcw" frameborder="0" allowfullscreen></iframe>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -20,5 +20,5 @@ React.renderComponent(<input value="hi" />, mountNode);
setTimeout(function() {
React.renderComponent(<input value={null} />, mountNode);
}, 2000);
}, 1000);
```

View File

@@ -26,7 +26,7 @@ var UserGist = React.createClass({
$.get(this.props.source, function(result) {
var lastGist = result[0];
this.setState({
username: lastGist.user.login,
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}.bind(this));

5
examples/README.md Normal file
View File

@@ -0,0 +1,5 @@
# React Examples
Here are some small React demos. Some use [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html) and some include third-party library integration.
For more fully-featured examples, check out [React TodoMVC](http://todomvc.com/architecture-examples/react/) and [React + Backbone TodoMVC](http://todomvc.com/labs/architecture-examples/react-backbone/).

View File

@@ -18,9 +18,7 @@ var BallmerPeakCalculator = React.createClass({
this.setState({bac: event.target.value});
},
render: function() {
var bac;
var pct;
pct = computeBallmerPeak(this.state.bac);
var pct = computeBallmerPeak(this.state.bac);
if (isNaN(pct)) {
pct = 'N/A';
} else {

View File

@@ -1,3 +0,0 @@
# TodoMVC-Backbone
This is a lightweight version of TodoMVC. Its primary purpose is to demo the Backbone integration rather than being feature-complete (refer to `todomvc-director` for a full TodoMVC-compilant app).

View File

@@ -1,556 +0,0 @@
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
/*-moz-appearance: none;*/
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eaeaea url('bg.png');
color: #4d4d4d;
width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todoapp input::-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
#todoapp h1 {
position: absolute;
top: -120px;
width: 100%;
font-size: 70px;
font-weight: bold;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
#main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
label[for='toggle-all'] {
display: none;
}
#toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
border: none; /* Mobile Safari */
}
#toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
#toggle-all:checked:before {
color: #737373;
}
#todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
/*-moz-appearance: none;*/
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#todo-list li .toggle:after {
content: '✔';
line-height: 43px; /* 40 + a couple of pixels visual adjustment */
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
#todo-list li .toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
#todo-list li label {
white-space: pre;
word-break: break-word;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
-moz-transition: color 0.4s;
-ms-transition: color 0.4s;
-o-transition: color 0.4s;
transition: color 0.4s;
}
#todo-list li.completed label {
color: #a9a9a9;
text-decoration: line-through;
}
#todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
-ms-transition: all 0.2s;
-o-transition: all 0.2s;
transition: all 0.2s;
}
#todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-moz-transform: scale(1.3);
-ms-transform: scale(1.3);
-o-transform: scale(1.3);
transform: scale(1.3);
}
#todo-list li .destroy:after {
content: '✖';
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
z-index: 1;
text-align: center;
}
#footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
float: left;
text-align: left;
}
#filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
#filters li {
display: inline;
}
#filters li a {
color: #83756f;
margin: 2px;
text-decoration: none;
}
#filters li a.selected {
font-weight: bold;
}
#clear-completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
#info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
#info a {
color: inherit;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
height: 40px;
}
#toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
.hidden {
display: none;
}
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #C5C5C5;
border-bottom: 1px dashed #F7F7F7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
/**body*/.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
/**body*/.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
/**body*/.learn-bar > .learn {
left: 8px;
}
/**body*/.learn-bar #todoapp {
width: 550px;
margin: 130px auto 40px auto;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,22 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>React • TodoMVC</title>
<link rel="stylesheet" href="css/base.css">
<!--[if IE]>
<script src="js/ie.js"></script>
<![endif]-->
</head>
<body>
<div id="container"></div>
<script src="../../build/react.js"></script>
<script src="../../build/JSXTransformer.js"></script>
<script type="text/javascript" src="../shared/thirdparty/jquery.min.js" charset="utf-8"></script>
<script type="text/javascript" src="thirdparty/lodash.min.js" charset="utf-8"></script>
<script type="text/javascript" src="thirdparty/backbone-min.js" charset="utf-8"></script>
<script type="text/javascript" src="thirdparty/backbone.localStorage-min.js" charset="utf-8"></script>
<script type="text/jsx" src="js/app.js"></script>
</body>
</html>

View File

@@ -1,303 +0,0 @@
/** @jsx React.DOM */
var Todo = Backbone.Model.extend({
// Default attributes for the todo
// and ensure that each todo created has `title` and `completed` keys.
defaults: {
title: '',
completed: false
},
// Toggle the `completed` state of this todo item.
toggle: function() {
this.save({
completed: !this.get('completed')
});
}
});
var TodoList = Backbone.Collection.extend({
// Reference to this collection's model.
model: Todo,
// Save all of the todo items under the `"todos"` namespace.
localStorage: new Store('todos-react-backbone'),
// Filter down the list of all todo items that are finished.
completed: function() {
return this.filter(function( todo ) {
return todo.get('completed');
});
},
remaining: function() {
return this.without.apply(this, this.completed());
},
// We keep the Todos in sequential order, despite being saved by unordered
// GUID in the database. This generates the next order number for new items.
nextOrder: function () {
if (!this.length) {
return 1;
}
return this.last().get('order') + 1;
},
// Todos are sorted by their original insertion order.
comparator: function (todo) {
return todo.get('order');
}
});
var Utils = {
pluralize: function( count, word ) {
return count === 1 ? word : word + 's';
},
stringifyObjKeys: function(obj) {
var s = '';
for (var key in obj) {
if (!obj.hasOwnProperty(key)) {
continue;
}
if (obj[key]) {
s += key + ' ';
}
}
return s;
}
};
// Begin React stuff
var TodoItem = React.createClass({
handleSubmit: function(event) {
var val = this.refs.editField.getDOMNode().value.trim();
if (val) {
this.props.onSave(val);
} else {
this.props.onDestroy();
}
return false;
},
onEdit: function() {
this.props.onEdit();
this.refs.editField.getDOMNode().focus();
},
render: function() {
var classes = Utils.stringifyObjKeys({
completed: this.props.todo.get('completed'), editing: this.props.editing
});
return (
<li className={classes}>
<div className="view">
<input
className="toggle"
type="checkbox"
checked={this.props.todo.get('completed')}
onChange={this.props.onToggle}
key={this.props.key}
/>
<label onDoubleClick={this.onEdit}>
{this.props.todo.get('title')}
</label>
<button className="destroy" onClick={this.props.onDestroy} />
</div>
<form onSubmit={this.handleSubmit}>
<input
ref="editField"
className="edit"
defaultValue={this.props.todo.get('title')}
onBlur={this.handleSubmit}
autoFocus="autofocus"
/>
</form>
</li>
);
}
});
var TodoFooter = React.createClass({
render: function() {
var activeTodoWord = Utils.pluralize(this.props.count, 'todo');
var clearButton = null;
if (this.props.completedCount > 0) {
clearButton = (
<button id="clear-completed" onClick={this.props.onClearCompleted}>
Clear completed ({this.props.completedCount})
</button>
);
}
return (
<footer id="footer">
<span id="todo-count">
<strong>{this.props.count}</strong>{' '}
{activeTodoWord}{' '}left
</span>
{clearButton}
</footer>
);
}
});
// An example generic Mixin that you can add to any component that should react
// to changes in a Backbone component. The use cases we've identified thus far
// are for Collections -- since they trigger a change event whenever any of
// their constituent items are changed there's no need to reconcile for regular
// models. One caveat: this relies on getBackboneModels() to always return the
// same model instances throughout the lifecycle of the component. If you're
// using this mixin correctly (it should be near the top of your component
// hierarchy) this should not be an issue.
var BackboneMixin = {
componentDidMount: function() {
// Whenever there may be a change in the Backbone data, trigger a reconcile.
this.getBackboneModels().forEach(function(model) {
model.on('add change remove', this.forceUpdate.bind(this, null), this);
}, this);
},
componentWillUnmount: function() {
// Ensure that we clean up any dangling references when the component is
// destroyed.
this.getBackboneModels().forEach(function(model) {
model.off(null, null, this);
}, this);
}
};
var TodoApp = React.createClass({
mixins: [BackboneMixin],
getInitialState: function() {
return {editing: null};
},
componentDidMount: function() {
// Additional functionality for todomvc: fetch() the collection on init
this.props.todos.fetch();
this.refs.newField.getDOMNode().focus();
},
componentDidUpdate: function() {
// If saving were expensive we'd listen for mutation events on Backbone and
// do this manually. however, since saving isn't expensive this is an
// elegant way to keep it reactively up-to-date.
this.props.todos.forEach(function(todo) {
todo.save();
});
},
getBackboneModels: function() {
return [this.props.todos];
},
handleSubmit: function(event) {
event.preventDefault();
var val = this.refs.newField.getDOMNode().value.trim();
if (val) {
this.props.todos.create({
title: val,
completed: false,
order: this.props.todos.nextOrder()
});
this.refs.newField.getDOMNode().value = '';
}
},
toggleAll: function(event) {
var checked = event.nativeEvent.target.checked;
this.props.todos.forEach(function(todo) {
todo.set('completed', checked);
});
},
edit: function(todo) {
this.setState({editing: todo.get('id')});
},
save: function(todo, text) {
todo.set('title', text);
this.setState({editing: null});
},
clearCompleted: function() {
this.props.todos.completed().forEach(function(todo) {
todo.destroy();
});
},
render: function() {
var footer = null;
var main = null;
var todoItems = this.props.todos.map(function(todo) {
return (
<TodoItem
key={todo.cid}
todo={todo}
onToggle={todo.toggle.bind(todo)}
onDestroy={todo.destroy.bind(todo)}
onEdit={this.edit.bind(this, todo)}
editing={this.state.editing === todo.get('id')}
onSave={this.save.bind(this, todo)}
/>
);
}, this);
var activeTodoCount = this.props.todos.remaining().length;
var completedCount = todoItems.length - activeTodoCount;
if (activeTodoCount || completedCount) {
footer =
<TodoFooter
count={activeTodoCount}
completedCount={completedCount}
onClearCompleted={this.clearCompleted}
/>;
}
if (todoItems.length) {
main = (
<section id="main">
<input id="toggle-all" type="checkbox" onChange={this.toggleAll} />
<ul id="todo-list">
{todoItems}
</ul>
</section>
);
}
return (
<div>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<form onSubmit={this.handleSubmit}>
<input
ref="newField"
id="new-todo"
placeholder="What needs to be done?"
/>
</form>
</header>
{main}
{footer}
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>
Created by{' '}
<a href="http://github.com/petehunt/">petehunt</a>
</p>
<p>Part of{' '}<a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</div>
);
}
});
React.renderComponent(
<TodoApp todos={new TodoList()} />, document.getElementById('container')
);

View File

@@ -1,34 +0,0 @@
/*! HTML5 Shiv pre3.6 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
Uncompressed source: https://github.com/aFarkas/html5shiv */
(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}</style>";
c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);
/* ES5-shim
https://github.com/kriskowal/es5-shim
Copyright 2009-2012 by contributors, MIT License */
(function(definition){if(typeof define=="function")define(definition);else if(typeof YUI=="function")YUI.add("es5",definition);else definition()})(function(){if(!Function.prototype.bind)Function.prototype.bind=function bind(that){var target=this;if(typeof target!="function")throw new TypeError("Function.prototype.bind called on incompatible "+target);var args=slice.call(arguments,1);var bound=function(){if(this instanceof bound){var result=target.apply(this,args.concat(slice.call(arguments)));if(Object(result)===
result)return result;return this}else return target.apply(that,args.concat(slice.call(arguments)))};if(target.prototype)bound.prototype=Object.create(target.prototype);return bound};var call=Function.prototype.call;var prototypeOfArray=Array.prototype;var prototypeOfObject=Object.prototype;var slice=prototypeOfArray.slice;var _toString=call.bind(prototypeOfObject.toString);var owns=call.bind(prototypeOfObject.hasOwnProperty);var defineGetter;var defineSetter;var lookupGetter;var lookupSetter;var supportsAccessors;
if(supportsAccessors=owns(prototypeOfObject,"__defineGetter__")){defineGetter=call.bind(prototypeOfObject.__defineGetter__);defineSetter=call.bind(prototypeOfObject.__defineSetter__);lookupGetter=call.bind(prototypeOfObject.__lookupGetter__);lookupSetter=call.bind(prototypeOfObject.__lookupSetter__)}if([1,2].splice(0).length!=2){var array_splice=Array.prototype.splice;Array.prototype.splice=function(start,deleteCount){if(!arguments.length)return[];else return array_splice.apply(this,[start===void 0?
0:start,deleteCount===void 0?this.length-start:deleteCount].concat(slice.call(arguments,2)))}}if(!Array.isArray)Array.isArray=function isArray(obj){return _toString(obj)=="[object Array]"};var boxedString=Object("a"),splitString=boxedString[0]!="a"||!(0 in boxedString);if(!Array.prototype.forEach)Array.prototype.forEach=function forEach(fun){var object=toObject(this),self=splitString&&_toString(this)=="[object String]"?this.split(""):object,thisp=arguments[1],i=-1,length=self.length>>>0;if(_toString(fun)!=
"[object Function]")throw new TypeError;while(++i<length)if(i in self)fun.call(thisp,self[i],i,object)};if(!Array.prototype.map)Array.prototype.map=function map(fun){var object=toObject(this),self=splitString&&_toString(this)=="[object String]"?this.split(""):object,length=self.length>>>0,result=Array(length),thisp=arguments[1];if(_toString(fun)!="[object Function]")throw new TypeError(fun+" is not a function");for(var i=0;i<length;i++)if(i in self)result[i]=fun.call(thisp,self[i],i,object);return result};
if(!Array.prototype.filter)Array.prototype.filter=function filter(fun){var object=toObject(this),self=splitString&&_toString(this)=="[object String]"?this.split(""):object,length=self.length>>>0,result=[],value,thisp=arguments[1];if(_toString(fun)!="[object Function]")throw new TypeError(fun+" is not a function");for(var i=0;i<length;i++)if(i in self){value=self[i];if(fun.call(thisp,value,i,object))result.push(value)}return result};if(!Array.prototype.every)Array.prototype.every=function every(fun){var object=
toObject(this),self=splitString&&_toString(this)=="[object String]"?this.split(""):object,length=self.length>>>0,thisp=arguments[1];if(_toString(fun)!="[object Function]")throw new TypeError(fun+" is not a function");for(var i=0;i<length;i++)if(i in self&&!fun.call(thisp,self[i],i,object))return false;return true};if(!Array.prototype.some)Array.prototype.some=function some(fun){var object=toObject(this),self=splitString&&_toString(this)=="[object String]"?this.split(""):object,length=self.length>>>
0,thisp=arguments[1];if(_toString(fun)!="[object Function]")throw new TypeError(fun+" is not a function");for(var i=0;i<length;i++)if(i in self&&fun.call(thisp,self[i],i,object))return true;return false};if(!Array.prototype.reduce)Array.prototype.reduce=function reduce(fun){var object=toObject(this),self=splitString&&_toString(this)=="[object String]"?this.split(""):object,length=self.length>>>0;if(_toString(fun)!="[object Function]")throw new TypeError(fun+" is not a function");if(!length&&arguments.length==
1)throw new TypeError("reduce of empty array with no initial value");var i=0;var result;if(arguments.length>=2)result=arguments[1];else{do{if(i in self){result=self[i++];break}if(++i>=length)throw new TypeError("reduce of empty array with no initial value");}while(true)}for(;i<length;i++)if(i in self)result=fun.call(void 0,result,self[i],i,object);return result};if(!Array.prototype.reduceRight)Array.prototype.reduceRight=function reduceRight(fun){var object=toObject(this),self=splitString&&_toString(this)==
"[object String]"?this.split(""):object,length=self.length>>>0;if(_toString(fun)!="[object Function]")throw new TypeError(fun+" is not a function");if(!length&&arguments.length==1)throw new TypeError("reduceRight of empty array with no initial value");var result,i=length-1;if(arguments.length>=2)result=arguments[1];else{do{if(i in self){result=self[i--];break}if(--i<0)throw new TypeError("reduceRight of empty array with no initial value");}while(true)}do if(i in this)result=fun.call(void 0,result,
self[i],i,object);while(i--);return result};if(!Array.prototype.indexOf||[0,1].indexOf(1,2)!=-1)Array.prototype.indexOf=function indexOf(sought){var self=splitString&&_toString(this)=="[object String]"?this.split(""):toObject(this),length=self.length>>>0;if(!length)return-1;var i=0;if(arguments.length>1)i=toInteger(arguments[1]);i=i>=0?i:Math.max(0,length+i);for(;i<length;i++)if(i in self&&self[i]===sought)return i;return-1};if(!Array.prototype.lastIndexOf||[0,1].lastIndexOf(0,-3)!=-1)Array.prototype.lastIndexOf=
function lastIndexOf(sought){var self=splitString&&_toString(this)=="[object String]"?this.split(""):toObject(this),length=self.length>>>0;if(!length)return-1;var i=length-1;if(arguments.length>1)i=Math.min(i,toInteger(arguments[1]));i=i>=0?i:length-Math.abs(i);for(;i>=0;i--)if(i in self&&sought===self[i])return i;return-1};if(!Object.keys){var hasDontEnumBug=true,dontEnums=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],dontEnumsLength=
dontEnums.length;for(var key in{"toString":null})hasDontEnumBug=false;Object.keys=function keys(object){if(typeof object!="object"&&typeof object!="function"||object===null)throw new TypeError("Object.keys called on a non-object");var keys=[];for(var name in object)if(owns(object,name))keys.push(name);if(hasDontEnumBug)for(var i=0,ii=dontEnumsLength;i<ii;i++){var dontEnum=dontEnums[i];if(owns(object,dontEnum))keys.push(dontEnum)}return keys}}var negativeDate=-621987552E5,negativeYearString="-000001";
if(!Date.prototype.toISOString||(new Date(negativeDate)).toISOString().indexOf(negativeYearString)===-1)Date.prototype.toISOString=function toISOString(){var result,length,value,year,month;if(!isFinite(this))throw new RangeError("Date.prototype.toISOString called on non-finite value.");year=this.getUTCFullYear();month=this.getUTCMonth();year+=Math.floor(month/12);month=(month%12+12)%12;result=[month+1,this.getUTCDate(),this.getUTCHours(),this.getUTCMinutes(),this.getUTCSeconds()];year=(year<0?"-":
year>9999?"+":"")+("00000"+Math.abs(year)).slice(0<=year&&year<=9999?-4:-6);length=result.length;while(length--){value=result[length];if(value<10)result[length]="0"+value}return year+"-"+result.slice(0,2).join("-")+"T"+result.slice(2).join(":")+"."+("000"+this.getUTCMilliseconds()).slice(-3)+"Z"};var dateToJSONIsSupported=false;try{dateToJSONIsSupported=Date.prototype.toJSON&&(new Date(NaN)).toJSON()===null&&(new Date(negativeDate)).toJSON().indexOf(negativeYearString)!==-1&&Date.prototype.toJSON.call({toISOString:function(){return true}})}catch(e){}if(!dateToJSONIsSupported)Date.prototype.toJSON=
function toJSON(key){var o=Object(this),tv=toPrimitive(o),toISO;if(typeof tv==="number"&&!isFinite(tv))return null;toISO=o.toISOString;if(typeof toISO!="function")throw new TypeError("toISOString property is not callable");return toISO.call(o)};if(!Date.parse||"Date.parse is buggy")Date=function(NativeDate){function Date(Y,M,D,h,m,s,ms){var length=arguments.length;if(this instanceof NativeDate){var date=length==1&&String(Y)===Y?new NativeDate(Date.parse(Y)):length>=7?new NativeDate(Y,M,D,h,m,s,ms):
length>=6?new NativeDate(Y,M,D,h,m,s):length>=5?new NativeDate(Y,M,D,h,m):length>=4?new NativeDate(Y,M,D,h):length>=3?new NativeDate(Y,M,D):length>=2?new NativeDate(Y,M):length>=1?new NativeDate(Y):new NativeDate;date.constructor=Date;return date}return NativeDate.apply(this,arguments)}var isoDateExpression=new RegExp("^"+"(\\d{4}|[+-]\\d{6})"+"(?:-(\\d{2})"+"(?:-(\\d{2})"+"(?:"+"T(\\d{2})"+":(\\d{2})"+"(?:"+":(\\d{2})"+"(?:\\.(\\d{3}))?"+")?"+"("+"Z|"+"(?:"+"([-+])"+"(\\d{2})"+":(\\d{2})"+")"+")?)?)?)?"+
"$");var months=[0,31,59,90,120,151,181,212,243,273,304,334,365];function dayFromMonth(year,month){var t=month>1?1:0;return months[month]+Math.floor((year-1969+t)/4)-Math.floor((year-1901+t)/100)+Math.floor((year-1601+t)/400)+365*(year-1970)}for(var key in NativeDate)Date[key]=NativeDate[key];Date.now=NativeDate.now;Date.UTC=NativeDate.UTC;Date.prototype=NativeDate.prototype;Date.prototype.constructor=Date;Date.parse=function parse(string){var match=isoDateExpression.exec(string);if(match){var year=
Number(match[1]),month=Number(match[2]||1)-1,day=Number(match[3]||1)-1,hour=Number(match[4]||0),minute=Number(match[5]||0),second=Number(match[6]||0),millisecond=Number(match[7]||0),offset=!match[4]||match[8]?0:Number(new NativeDate(1970,0)),signOffset=match[9]==="-"?1:-1,hourOffset=Number(match[10]||0),minuteOffset=Number(match[11]||0),result;if(hour<(minute>0||second>0||millisecond>0?24:25)&&minute<60&&second<60&&millisecond<1E3&&month>-1&&month<12&&hourOffset<24&&minuteOffset<60&&day>-1&&day<dayFromMonth(year,
month+1)-dayFromMonth(year,month)){result=((dayFromMonth(year,month)+day)*24+hour+hourOffset*signOffset)*60;result=((result+minute+minuteOffset*signOffset)*60+second)*1E3+millisecond+offset;if(-864E13<=result&&result<=864E13)return result}return NaN}return NativeDate.parse.apply(this,arguments)};return Date}(Date);if(!Date.now)Date.now=function now(){return(new Date).getTime()};if("0".split(void 0,0).length){var string_split=String.prototype.split;String.prototype.split=function(separator,limit){if(separator===
void 0&&limit===0)return[];return string_split.apply(this,arguments)}}if("".substr&&"0b".substr(-1)!=="b"){var string_substr=String.prototype.substr;String.prototype.substr=function(start,length){return string_substr.call(this,start<0?(start=this.length+start)<0?0:start:start,length)}}var ws="\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003"+"\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028"+"\u2029\ufeff";if(!String.prototype.trim||ws.trim()){ws="["+ws+"]";var trimBeginRegexp=
new RegExp("^"+ws+ws+"*"),trimEndRegexp=new RegExp(ws+ws+"*$");String.prototype.trim=function trim(){if(this===undefined||this===null)throw new TypeError("can't convert "+this+" to object");return String(this).replace(trimBeginRegexp,"").replace(trimEndRegexp,"")}}function toInteger(n){n=+n;if(n!==n)n=0;else if(n!==0&&n!==1/0&&n!==-(1/0))n=(n>0||-1)*Math.floor(Math.abs(n));return n}function isPrimitive(input){var type=typeof input;return input===null||type==="undefined"||type==="boolean"||type===
"number"||type==="string"}function toPrimitive(input){var val,valueOf,toString;if(isPrimitive(input))return input;valueOf=input.valueOf;if(typeof valueOf==="function"){val=valueOf.call(input);if(isPrimitive(val))return val}toString=input.toString;if(typeof toString==="function"){val=toString.call(input);if(isPrimitive(val))return val}throw new TypeError;}var toObject=function(o){if(o==null)throw new TypeError("can't convert "+o+" to object");return Object(o)}});

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
/**
* Backbone localStorage Adapter
* Version 1.1.4
*
* https://github.com/jeromegn/Backbone.localStorage
*/(function(a,b){typeof exports=="object"?module.exports=b(require("underscore"),require("backbone")):typeof define=="function"&&define.amd?define(["underscore","backbone"],function(c,d){return b(c||a._,d||a.Backbone)}):b(_,Backbone)})(this,function(a,b){function c(){return((1+Math.random())*65536|0).toString(16).substring(1)}function d(){return c()+c()+"-"+c()+"-"+c()+"-"+c()+"-"+c()+c()+c()}return b.LocalStorage=window.Store=function(a){if(!this.localStorage)throw"Backbone.localStorage: Environment does not support localStorage.";this.name=a;var b=this.localStorage().getItem(this.name);this.records=b&&b.split(",")||[]},a.extend(b.LocalStorage.prototype,{save:function(){this.localStorage().setItem(this.name,this.records.join(","))},create:function(a){return a.id||(a.id=d(),a.set(a.idAttribute,a.id)),this.localStorage().setItem(this.name+"-"+a.id,JSON.stringify(a)),this.records.push(a.id.toString()),this.save(),this.find(a)},update:function(b){return this.localStorage().setItem(this.name+"-"+b.id,JSON.stringify(b)),a.include(this.records,b.id.toString())||this.records.push(b.id.toString()),this.save(),this.find(b)},find:function(a){return this.jsonData(this.localStorage().getItem(this.name+"-"+a.id))},findAll:function(){return(a.chain||a)(this.records).map(function(a){return this.jsonData(this.localStorage().getItem(this.name+"-"+a))},this).compact().value()},destroy:function(b){return b.isNew()?!1:(this.localStorage().removeItem(this.name+"-"+b.id),this.records=a.reject(this.records,function(a){return a===b.id.toString()}),this.save(),b)},localStorage:function(){return localStorage},jsonData:function(a){return a&&JSON.parse(a)},_clear:function(){var b=this.localStorage(),c=new RegExp("^"+this.name+"-");b.removeItem(this.name),(a.chain||a)(b).keys().filter(function(a){return c.test(a)}).each(function(a){b.removeItem(a)})},_storageSize:function(){return this.localStorage().length}}),b.LocalStorage.sync=window.Store.sync=b.localSync=function(a,c,d){var e=c.localStorage||c.collection.localStorage,f,g,h=b.$.Deferred&&b.$.Deferred();try{switch(a){case"read":f=c.id!=undefined?e.find(c):e.findAll();break;case"create":f=e.create(c);break;case"update":f=e.update(c);break;case"delete":f=e.destroy(c)}}catch(i){i.code===DOMException.QUOTA_EXCEEDED_ERR&&e._storageSize()===0?g="Private browsing is unsupported":g=i.message}return f?(d&&d.success&&(b.VERSION==="0.9.10"?d.success(c,f,d):d.success(f)),h&&h.resolve(f)):(g=g?g:"Record Not Found",d&&d.error&&(b.VERSION==="0.9.10"?d.error(c,g,d):d.error(g)),h&&h.reject(g)),d&&d.complete&&d.complete(f),h&&h.promise()},b.ajaxSync=b.sync,b.getSyncMethod=function(a){return a.localStorage||a.collection&&a.collection.localStorage?b.localSync:b.ajaxSync},b.sync=function(a,c,d){return b.getSyncMethod(c).apply(this,[a,c,d])},b.LocalStorage});

View File

@@ -1,39 +0,0 @@
/*!
Lo-Dash 0.8.2 lodash.com/license
Underscore.js 1.4.2 underscorejs.org/LICENSE
*/
;(function(e,t){function s(e){if(e&&e.__wrapped__)return e;if(!(this instanceof s))return new s(e);this.__wrapped__=e}function o(e,t,n){t||(t=0);var r=e.length,i=r-t>=(n||H),s=i?{}:e;if(i)for(n=t-1;++n<r;){var o=e[n]+"";(Z.call(s,o)?s[o]:s[o]=[]).push(e[n])}return function(e){if(i){var n=e+"";return Z.call(s,n)&&-1<T(s[n],e)}return-1<T(s,e,t)}}function u(e,n){var r=e.b,i=n.b,e=e.a,n=n.a;if(e!==n){if(e>n||e===t)return 1;if(e<n||n===t)return-1}return r<i?-1:1}function a(e,t,n){function r(){var u=arguments
,a=s?this:t;return i||(e=t[o]),n.length&&(u=u.length?n.concat(nt.call(u)):n),this instanceof r?(p.prototype=e.prototype,a=new p,(u=e.apply(a,u))&&Ht[typeof u]?u:a):e.apply(a,u)}var i=m(e),s=!n,o=e;return s&&(n=t),r}function f(e,n){return e?"function"!=typeof e?function(t){return t[e]}:n!==t?function(t,r,i){return e.call(n,t,r,i)}:e:A}function l(){for(var e={e:"",g:Et,i:"",j:Mt,m:Tt,n:kt,o:J,p:"",q:n,r:_t,c:{d:""},l:{d:""}},t,i=-1;t=arguments[++i];)for(var s in t){var o=t[s];/d|h/.test(s)?("string"==typeof
o&&(o={b:o,k:o}),e.c[s]=o.b,e.l[s]=o.k):e[s]=o}t=e.a;if("d"!=(e.f=/^[^,]+/.exec(t)[0])||!e.c.h)e.c=r;i="",e.r&&(i+="'use strict';"),i+="var i,A,j="+e.f+",t="+(e.i||e.f)+";if(!"+e.f+")return t;"+e.p+";",e.c&&(i+="var k=j.length;i=-1;",e.l&&(i+="if(k===+k){"),e.n&&(i+="if(y.call(j)==w){j=j.split('')}"),i+=e.c.d+";while(++i<k){A=j[i];"+e.c.h+"}",e.l&&(i+="}"));if(e.l){e.c?i+="else {":e.m&&(i+="var k=j.length;i=-1;if(k&&O(j)){while(++i<k){A=j[i+=''];"+e.l.h+"}}else {"),e.g||(i+="var u=typeof j=='function'&&q.call(j,'prototype');"
);if(e.j&&e.q)i+="var n=-1,o=Y[typeof j]?l(j):[],k=o.length;"+e.l.d+";while(++n<k){i=o[n];",e.g||(i+="if(!(u&&i=='prototype')){"),i+="A=j[i];"+e.l.h+"",e.g||(i+="}");else{i+=e.l.d+";for(i in j){";if(!e.g||e.q)i+="if(",e.g||(i+="!(u&&i=='prototype')"),!e.g&&e.q&&(i+="&&"),e.q&&(i+="h.call(j,i)"),i+="){";i+="A=j[i];"+e.l.h+";";if(!e.g||e.q)i+="}"}i+="}";if(e.g){i+="var g=j.constructor;";for(s=0;7>s;s++)i+="i='"+e.o[s]+"';if(","constructor"==e.o[s]&&(i+="!(g&&g.prototype===j)&&"),i+="h.call(j,i)){A=j[i];"+
e.l.h+"}"}if(e.c||e.m)i+="}"}return i+=e.e+";return t",Function("D,E,F,I,e,f,J,h,M,O,Q,S,T,X,Y,l,q,v,w,y,z","var G=function("+t+"){"+i+"};return G")(Dt,_,L,u,Q,f,en,Z,T,v,$t,m,Jt,mt,Ht,ut,tt,nt,yt,rt)}function c(e){return"\\"+Bt[e]}function h(e){return Qt[e]}function p(){}function d(e){return Gt[e]}function v(e){return rt.call(e)==ct}function m(e){return"function"==typeof e}function g(e){var t=i;if(!e||"object"!=typeof e||v(e))return t;var n=e.constructor;return(!Lt||"function"==typeof e.toString||"string"!=typeof
(e+""))&&(!m(n)||n instanceof n)?xt?(en(e,function(e,n,r){return t=!Z.call(r,n),i}),t===i):(en(e,function(e,n){t=n}),t===i||Z.call(e,t)):t}function y(e,t,n,s,o){if(e==r)return e;n&&(t=i);if(n=Ht[typeof e]){var u=rt.call(e);if(!Pt[u]||Nt&&v(e))return e;var a=u==ht,n=a||(u==mt?Jt(e):n)}if(!n||!t)return n?a?nt.call(e):Zt({},e):e;n=e.constructor;switch(u){case pt:case dt:return new n(+e);case vt:case yt:return new n(e);case gt:return n(e.source,U.exec(e))}s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[
u]==e)return o[u];var f=a?n(e.length):{};return s.push(e),o.push(f),(a?mn:tn)(e,function(e,n){f[n]=y(e,t,r,s,o)}),f}function b(e,t,s,o){if(e==r||t==r)return e===t;if(e===t)return 0!==e||1/e==1/t;if(Ht[typeof e]||Ht[typeof t])e=e.__wrapped__||e,t=t.__wrapped__||t;var u=rt.call(e);if(u!=rt.call(t))return i;switch(u){case pt:case dt:return+e==+t;case vt:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case gt:case yt:return e==t+""}var a=Dt[u];if(Nt&&!a&&(a=v(e))&&!v(t)||!a&&(u!=mt||Lt&&("function"!=typeof e.
toString&&"string"==typeof (e+"")||"function"!=typeof t.toString&&"string"==typeof (t+""))))return i;s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[u]==e)return o[u]==t;var u=-1,f=n,l=0;s.push(e),o.push(t);if(a){l=e.length;if(f=l==t.length)for(;l--&&(f=b(e[l],t[l],s,o)););return f}a=e.constructor,f=t.constructor;if(a!=f&&(!m(a)||!(a instanceof a&&m(f)&&f instanceof f)))return i;for(var c in e)if(Z.call(e,c)&&(l++,!Z.call(t,c)||!b(e[c],t[c],s,o)))return i;for(c in t)if(Z.call(t,c)&&!(l--))return i;if(
Et)for(;7>++u;)if(c=J[u],Z.call(e,c)&&(!Z.call(t,c)||!b(e[c],t[c],s,o)))return i;return n}function w(e,t,n){var r=-Infinity,i=-1,s=e?e.length:0,o=r;if(t||s!==+s)t=f(t,n),mn(e,function(e,n,i){n=t(e,n,i),n>r&&(r=n,o=e)});else for(;++i<s;)e[i]>o&&(o=e[i]);return o}function E(e,t,n,r){var s=e,o=e?e.length:0,u=3>arguments.length;if(o!==+o)var a=sn(e),o=a.length;else kt&&rt.call(e)==yt&&(s=e.split(""));return mn(e,function(e,f,l){f=a?a[--o]:--o,n=u?(u=i,s[f]):t.call(r,n,s[f],f,l)}),n}function S(e,t,n){
if(e)return t==r||n?e[0]:nt.call(e,0,t)}function x(e,t){for(var n=-1,r=e?e.length:0,i=[];++n<r;){var s=e[n];$t(s)?et.apply(i,t?s:x(s)):i.push(s)}return i}function T(e,t,n){var r=-1,i=e?e.length:0;if("number"==typeof n)r=(0>n?at(0,i+n):n||0)-1;else if(n)return r=C(e,t),e[r]===t?r:-1;for(;++r<i;)if(e[r]===t)return r;return-1}function N(e,t,n){return e?nt.call(e,t==r||n?1:t):[]}function C(e,t,n,r){var i=0,s=e?e.length:i;if(n){n=f(n,r);for(t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r}else for(;i<s;)r=i+
s>>>1,e[r]<t?i=r+1:s=r;return i}function k(e,t,n,r){var s=-1,o=e?e.length:0,u=[],a=[];"function"==typeof t&&(r=n,n=t,t=i);for(n=f(n,r);++s<o;)if(r=n(e[s],s,e),t?!s||a[a.length-1]!==r:0>T(a,r))a.push(r),u.push(e[s]);return u}function L(e,t){return Ot||it&&2<arguments.length?it.call.apply(it,arguments):a(e,t,nt.call(arguments,2))}function A(e){return e}function O(e){mn(nn(e),function(t){var r=s[t]=e[t];s.prototype[t]=function(){var e=[this.__wrapped__];return arguments.length&&et.apply(e,arguments)
,e=r.apply(s,e),this.__chain__&&(e=new s(e),e.__chain__=n),e}})}var n=!0,r=null,i=!1,M="object"==typeof exports&&exports&&("object"==typeof global&&global&&global==global.global&&(e=global),exports),_=Array.prototype,D=Object.prototype,P=0,H=30,B=e._,j=/[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,F=/&(?:amp|lt|gt|quot|#x27);/g,I=/\b__p\+='';/g,q=/\b(__p\+=)''\+/g,R=/(__e\(.*?\)|\b__t\))\+'';/g,U=/\w*$/,z=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,W=RegExp("^"+(D.valueOf+""
).replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),X=/($^)/,V=/[&<>"']/g,$=/['\n\r\t\u2028\u2029\\]/g,J="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),K=Math.ceil,Q=_.concat,G=Math.floor,Y=W.test(Y=Object.getPrototypeOf)&&Y,Z=D.hasOwnProperty,et=_.push,tt=D.propertyIsEnumerable,nt=_.slice,rt=D.toString,it=W.test(it=nt.bind)&&it,st=W.test(st=Array.isArray)&&st,ot=e.isFinite,ut=W.test(ut=Object.keys)&&ut
,at=Math.max,ft=Math.min,lt=Math.random,ct="[object Arguments]",ht="[object Array]",pt="[object Boolean]",dt="[object Date]",vt="[object Number]",mt="[object Object]",gt="[object RegExp]",yt="[object String]",bt=e.clearTimeout,wt=e.setTimeout,Et,St,xt,Tt=n;(function(){function e(){this.x=1}var t={0:1,length:1},n=[];e.prototype={valueOf:1,y:1};for(var r in new e)n.push(r);for(r in arguments)Tt=!r;Et=4>(n+"").length,xt="x"!=n[0],St=(n.splice.call(t,0,1),t[0])})(1);var Nt=!v(arguments),Ct="x"!=nt.call("x"
)[0],kt="xx"!="x"[0]+Object("x")[0];try{var Lt=("[object Object]",rt.call(e.document||0)==mt)}catch(At){}var Ot=it&&/\n|Opera/.test(it+rt.call(e.opera)),Mt=ut&&/^.+$|true/.test(ut+!!e.attachEvent),_t=!Ot,Dt={};Dt[pt]=Dt[dt]=Dt["[object Function]"]=Dt[vt]=Dt[mt]=Dt[gt]=i,Dt[ct]=Dt[ht]=Dt[yt]=n;var Pt={};Pt[ct]=Pt["[object Function]"]=i,Pt[ht]=Pt[pt]=Pt[dt]=Pt[vt]=Pt[mt]=Pt[gt]=Pt[yt]=n;var Ht={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i,unknown:n},Bt={"\\":"\\","'":"'","\n":"n"
,"\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};s.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""};var jt={a:"d,c,x",p:"c=f(c,x)",h:"if(c(A,i,d)===false)return t"},Ft={i:"{}",p:"c=f(c,x)",h:"var p=c(A,i,d);(h.call(t,p)?t[p]++:t[p]=1)"},It={i:"true",h:"if(!c(A,i,d))return!t"},qt={q:i,r:i,a:"m",p:"for(var a=1,b=arguments.length;a<b;a++){if(j=arguments[a]){",h:"t[i]=A",e:"}}"},Rt={i:"[]",h:"c(A,i,d)&&t.push(A)"},Ut={p:"c=f(c,x)"}
,zt={h:{k:jt.h}},Wt={i:"d||[]",d:{b:"t=Array(k)",k:"t="+(Mt?"Array(k)":"[]")},h:{b:"t[i]=c(A,i,d)",k:"t"+(Mt?"[n]=":".push")+"(c(A,i,d))"}},Xt={q:i,a:"m,c,x",i:"{}",p:"var R=typeof c=='function';if(R)c=f(c,x);else var s=e.apply(E,arguments)",h:"if(R?!c(A,i,m):M(s,i)<0)t[i]=A"},Vt=l({a:"m",i:"{}",h:"t[A]=i"});Nt&&(v=function(e){return e?Z.call(e,"callee"):i});var $t=st||function(e){return rt.call(e)==ht};m(/x/)&&(m=function(e){return"[object Function]"==rt.call(e)});var Jt=Y?function(e){if(!e||"object"!=typeof
e)return i;var t=e.valueOf,n="function"==typeof t&&(n=Y(t))&&Y(n);return n?e==n||Y(e)==n&&!v(e):g(e)}:g,Kt=l({a:"m",i:"[]",h:"t.push(i)"}),Qt={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"},Gt=Vt(Qt),Yt=l(qt,{h:"if(t[i]==null)"+qt.h}),Zt=l(qt),en=l(jt,Ut,zt,{q:i}),tn=l(jt,Ut,zt),nn=l({q:i,a:"m",i:"[]",h:"S(A)&&t.push(i)",e:"t.sort()"}),rn=l({a:"A",i:"true",p:"var H=y.call(A),k=A.length;if(D[H]"+(Nt?"||O(A)":"")+"||(H==X&&k===+k&&S(A.splice)))return!k",h:{k:"return false"}}),sn=ut?function(
e){var t=typeof e;return"function"==t&&tt.call(e,"prototype")?Kt(e):e&&Ht[t]?ut(e):[]}:Kt,on=l(qt,{a:"m,dd,N",p:"var P,C=arguments,a=0;if(N==I){var b=2,ee=C[3],ff=C[4]}else var b=C.length,ee=[],ff=[];while(++a<b){if(j=C[a]){",h:"if((dd=A)&&((P=Q(dd))||T(dd))){var K=false,gg=ee.length;while(gg--)if(K=ee[gg]==dd)break;if(K){t[i]=ff[gg]}else {ee.push(dd);ff.push(A=(A=t[i],P)?(Q(A)?A:[]):(T(A)?A:{}));t[i]=G(A,dd,I,ee,ff)}}else if(dd!=null)t[i]=dd"}),un=l(Xt),an=l({a:"m",i:"[]",h:"t"+(Mt?"[n]=":".push"
)+"([i,A])"}),fn=l(Xt,{p:"if(typeof c!='function'){var i=0,s=e.apply(E,arguments),k=s.length;while(++i<k){var p=s[i];if(p in m)t[p]=m[p]}}else {c=f(c,x)",h:"if(c(A,i,m))t[i]=A",e:"}"}),ln=l({a:"m",i:"[]",h:"t.push(A)"}),cn=l({a:"d,hh",i:"false",n:i,d:{b:"if(y.call(d)==w)return d.indexOf(hh)>-1"},h:"if(A===hh)return true"}),hn=l(jt,Ft),pn=l(jt,It),dn=l(jt,Rt),vn=l(jt,Ut,{i:"z",h:"if(c(A,i,d))return A"}),mn=l(jt,Ut),gn=l(jt,Ft,{h:"var p=c(A,i,d);(h.call(t,p)?t[p]:t[p]=[]).push(A)"}),yn=l(Wt,{a:"d,U"
,p:"var C=v.call(arguments,2),R=typeof U=='function'",h:{b:"t[i]=(R?U:A[U]).apply(A,C)",k:"t"+(Mt?"[n]=":".push")+"((R?U:A[U]).apply(A,C))"}}),bn=l(jt,Wt),wn=l(Wt,{a:"d,bb",h:{b:"t[i]=A[bb]",k:"t"+(Mt?"[n]=":".push")+"(A[bb])"}}),En=l({a:"d,c,B,x",i:"B",p:"var V=arguments.length<3;c=f(c,x)",d:{b:"if(V)t=j[++i]"},h:{b:"t=c(t,A,i,d)",k:"t=V?(V=false,A):c(t,A,i,d)"}}),Sn=l(jt,Rt,{h:"!"+Rt.h}),xn=l(jt,It,{i:"false",h:It.h.replace("!","")}),Tn=l(jt,Ft,Wt,{h:{b:"t[i]={a:c(A,i,d),b:i,c:A}",k:"t"+(Mt?"[n]="
:".push")+"({a:c(A,i,d),b:i,c:A})"},e:"t.sort(I);k=t.length;while(k--)t[k]=t[k].c"}),Nn=l(Rt,{a:"d,aa",p:"var s=[];J(aa,function(A,p){s.push(p)});var cc=s.length",h:"for(var Z=true,r=0;r<cc;r++){var p=s[r];if(!(Z=A[p]===aa[p]))break}Z&&t.push(A)"}),Cn=l({q:i,r:i,a:"m",p:"var L=arguments,i=0,k=L.length;if(k>1){while(++i<k)t[L[i]]=F(t[L[i]],t);return t}",h:"if(S(A))t[i]=F(A,t)"});s.VERSION="0.8.2",s.after=function(e,t){return 1>e?t():function(){if(1>--e)return t.apply(this,arguments)}},s.bind=L,s.bindAll=
Cn,s.chain=function(e){return e=new s(e),e.__chain__=n,e},s.clone=y,s.compact=function(e){for(var t=-1,n=e?e.length:0,r=[];++t<n;){var i=e[t];i&&r.push(i)}return r},s.compose=function(){var e=arguments;return function(){for(var t=arguments,n=e.length;n--;)t=[e[n].apply(this,t)];return t[0]}},s.contains=cn,s.countBy=hn,s.debounce=function(e,t,n){function i(){a=r,n||(o=e.apply(u,s))}var s,o,u,a;return function(){var r=n&&!a;return s=arguments,u=this,bt(a),a=wt(i,t),r&&(o=e.apply(u,s)),o}},s.defaults=
Yt,s.defer=function(e){var n=nt.call(arguments,1);return wt(function(){return e.apply(t,n)},1)},s.delay=function(e,n){var r=nt.call(arguments,2);return wt(function(){return e.apply(t,r)},n)},s.difference=function(e){var t=[];if(!e)return t;for(var n=-1,r=e.length,i=Q.apply(_,arguments),i=o(i,r);++n<r;){var s=e[n];i(s)||t.push(s)}return t},s.escape=function(e){return e==r?"":(e+"").replace(V,h)},s.every=pn,s.extend=Zt,s.filter=dn,s.find=vn,s.first=S,s.flatten=x,s.forEach=mn,s.forIn=en,s.forOwn=tn,
s.functions=nn,s.groupBy=gn,s.has=function(e,t){return e?Z.call(e,t):i},s.identity=A,s.indexOf=T,s.initial=function(e,t,n){return e?nt.call(e,0,-(t==r||n?1:t)):[]},s.intersection=function(e){var t=arguments.length,n=[],r=-1,i=e?e.length:0,s=[];e:for(;++r<i;){var u=e[r];if(0>T(s,u)){for(var a=1;a<t;a++)if(!(n[a]||(n[a]=o(arguments[a])))(u))continue e;s.push(u)}}return s},s.invert=Vt,s.invoke=yn,s.isArguments=v,s.isArray=$t,s.isBoolean=function(e){return e===n||e===i||rt.call(e)==pt},s.isDate=function(
e){return rt.call(e)==dt},s.isElement=function(e){return e?1===e.nodeType:i},s.isEmpty=rn,s.isEqual=b,s.isFinite=function(e){return ot(e)&&rt.call(e)==vt},s.isFunction=m,s.isNaN=function(e){return rt.call(e)==vt&&e!=+e},s.isNull=function(e){return e===r},s.isNumber=function(e){return rt.call(e)==vt},s.isObject=function(e){return e?Ht[typeof e]:i},s.isPlainObject=Jt,s.isRegExp=function(e){return rt.call(e)==gt},s.isString=function(e){return rt.call(e)==yt},s.isUndefined=function(e){return e===t},s
.keys=sn,s.last=function(e,t,n){if(e){var i=e.length;return t==r||n?e[i-1]:nt.call(e,-t||i)}},s.lastIndexOf=function(e,t,n){var r=e?e.length:0;for("number"==typeof n&&(r=(0>n?at(0,r+n):ft(n,r-1))+1);r--;)if(e[r]===t)return r;return-1},s.lateBind=function(e,t){return a(t,e,nt.call(arguments,2))},s.map=bn,s.max=w,s.memoize=function(e,t){var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return Z.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},s.merge=on,s.min=function(e,t,n){var r=
Infinity,i=-1,s=e?e.length:0,o=r;if(t||s!==+s)t=f(t,n),mn(e,function(e,n,i){n=t(e,n,i),n<r&&(r=n,o=e)});else for(;++i<s;)e[i]<o&&(o=e[i]);return o},s.mixin=O,s.noConflict=function(){return e._=B,this},s.object=function(e,t){for(var n=-1,r=e?e.length:0,i={};++n<r;){var s=e[n];t?i[s]=t[n]:i[s[0]]=s[1]}return i},s.omit=un,s.once=function(e){var t,s=i;return function(){return s?t:(s=n,t=e.apply(this,arguments),e=r,t)}},s.pairs=an,s.partial=function(e){return a(e,nt.call(arguments,1))},s.pick=fn,s.pluck=
wn,s.random=function(e,t){return e==r&&t==r&&(t=1),e=+e||0,t==r&&(t=e,e=0),e+G(lt()*((+t||0)-e+1))},s.range=function(e,t,n){e=+e||0,n=+n||1,t==r&&(t=e,e=0);for(var i=-1,t=at(0,K((t-e)/n)),s=Array(t);++i<t;)s[i]=e,e+=n;return s},s.reduce=En,s.reduceRight=E,s.reject=Sn,s.rest=N,s.result=function(e,t){var n=e?e[t]:r;return m(n)?e[t]():n},s.shuffle=function(e){var t=-1,n=Array(e?e.length:0);return mn(e,function(e){var r=G(lt()*(++t+1));n[t]=n[r],n[r]=e}),n},s.size=function(e){var t=e?e.length:0;return t===+
t?t:sn(e).length},s.some=xn,s.sortBy=Tn,s.sortedIndex=C,s.tap=function(e,t){return t(e),e},s.template=function(e,t,n){e||(e=""),n||(n={});var r,i,o=0,u=s.templateSettings,a="__p += '",f=n.variable||u.variable,l=f;e.replace(RegExp((n.escape||u.escape||X).source+"|"+(n.interpolate||u.interpolate||X).source+"|"+(n.evaluate||u.evaluate||X).source+"|$","g"),function(t,n,i,s,u){a+=e.slice(o,u).replace($,c),a+=n?"'+__e("+n+")+'":s?"';"+s+";__p+='":i?"'+((__t=("+i+"))==null?'':__t)+'":"",r||(r=s||j.test(
n||i)),o=u+t.length}),a+="';",l||(f="obj",r?a="with("+f+"){"+a+"}":(n=RegExp("(\\(\\s*)"+f+"\\."+f+"\\b","g"),a=a.replace(z,"$&"+f+".").replace(n,"$1__d"))),a=(r?a.replace(I,""):a).replace(q,"$1").replace(R,"$1;"),a="function("+f+"){"+(l?"":f+"||("+f+"={});")+"var __t,__p='',__e=_.escape"+(r?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":(l?"":",__d="+f+"."+f+"||"+f)+";")+a+"return __p}";try{i=Function("_","return "+a)(s)}catch(h){throw h.source=a,h}return t?i(t):(i.source=
a,i)},s.throttle=function(e,t){function n(){a=new Date,u=r,s=e.apply(o,i)}var i,s,o,u,a=0;return function(){var r=new Date,f=t-(r-a);return i=arguments,o=this,0>=f?(bt(u),a=r,s=e.apply(o,i)):u||(u=wt(n,f)),s}},s.times=function(e,t,n){for(var e=+e||0,r=-1,i=Array(e);++r<e;)i[r]=t.call(n,r);return i},s.toArray=function(e){if(!e)return[];var t=e.length;return t===+t?(Ct?rt.call(e)==yt:"string"==typeof e)?e.split(""):nt.call(e):ln(e)},s.unescape=function(e){return e==r?"":(e+"").replace(F,d)},s.union=
function(){for(var e=-1,t=Q.apply(_,arguments),n=t.length,r=[];++e<n;){var i=t[e];0>T(r,i)&&r.push(i)}return r},s.uniq=k,s.uniqueId=function(e){var t=P++;return e?e+t:t},s.values=ln,s.where=Nn,s.without=function(e){for(var t=-1,n=e?e.length:0,r=o(arguments,1,20),i=[];++t<n;){var s=e[t];r(s)||i.push(s)}return i},s.wrap=function(e,t){return function(){var n=[e];return arguments.length&&et.apply(n,arguments),t.apply(this,n)}},s.zip=function(e){for(var t=-1,n=e?w(wn(arguments,"length")):0,r=Array(n);++
t<n;)r[t]=wn(arguments,t);return r},s.all=pn,s.any=xn,s.collect=bn,s.detect=vn,s.drop=N,s.each=mn,s.foldl=En,s.foldr=E,s.head=S,s.include=cn,s.inject=En,s.methods=nn,s.select=dn,s.tail=N,s.take=S,s.unique=k,O(s),s.prototype.chain=function(){return this.__chain__=n,this},s.prototype.value=function(){return this.__wrapped__},mn("pop push reverse shift sort splice unshift".split(" "),function(e){var t=_[e];s.prototype[e]=function(){var e=this.__wrapped__;return t.apply(e,arguments),St&&e.length===0&&delete
e[0],this.__chain__&&(e=new s(e),e.__chain__=n),e}}),mn(["concat","join","slice"],function(e){var t=_[e];s.prototype[e]=function(){var e=t.apply(this.__wrapped__,arguments);return this.__chain__&&(e=new s(e),e.__chain__=n),e}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(e._=s,define(function(){return s})):M?"object"==typeof module&&module&&module.exports==M?(module.exports=s)._=s:M._=s:e._=s})(this);

View File

@@ -1,11 +0,0 @@
# TodoMVC-director
This is the exact copy of the React-powered [TodoMVC](http://todomvc.com/labs/architecture-examples/react/). To test it, use [bower](http://bower.io) to fetch the dependencies:
`bower install`
Then fire up a server here:
`python -m SimpleHTTPServer`
And go visit `localhost:8000`.

View File

@@ -1,9 +0,0 @@
{
"name": "todomvc-react",
"version": "0.0.0",
"dependencies": {
"todomvc-common": "~0.1.7",
"react": "~0.4.0",
"director": "~1.2.0"
}
}

View File

@@ -1,24 +0,0 @@
<!doctype html>
<html lang="en" data-framework="react">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>React • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
</head>
<body>
<section id="todoapp"></section>
<footer id="info"></footer>
<div id="benchmark"></div>
<script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/react/react.js"></script>
<script src="bower_components/react/JSXTransformer.js"></script>
<script src="bower_components/director/build/director.min.js"></script>
<script type="text/jsx" src="js/utils.jsx"></script>
<script type="text/jsx" src="js/todoItem.jsx"></script>
<script type="text/jsx" src="js/footer.jsx"></script>
<script type="text/jsx" src="js/app.jsx"></script>
</body>
</html>

View File

@@ -1,207 +0,0 @@
/**
* @jsx React.DOM
*/
/*jshint quotmark:false */
/*jshint white:false */
/*jshint trailing:false */
/*jshint newcap:false */
/*global Utils, ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS,
TodoItem, TodoFooter, React, Router*/
(function (window, React) {
'use strict';
window.ALL_TODOS = 'all';
window.ACTIVE_TODOS = 'active';
window.COMPLETED_TODOS = 'completed';
var ENTER_KEY = 13;
var TodoApp = React.createClass({
getInitialState: function () {
var todos = Utils.store('react-todos');
return {
todos: todos,
nowShowing: ALL_TODOS,
editing: null
};
},
componentDidMount: function () {
var router = Router({
'/': this.setState.bind(this, {nowShowing: ALL_TODOS}),
'/active': this.setState.bind(this, {nowShowing: ACTIVE_TODOS}),
'/completed': this.setState.bind(this, {nowShowing: COMPLETED_TODOS})
});
router.init();
this.refs.newField.getDOMNode().focus();
},
handleNewTodoKeyDown: function (event) {
if (event.which !== ENTER_KEY) {
return;
}
var val = this.refs.newField.getDOMNode().value.trim();
var todos;
var newTodo;
if (val) {
todos = this.state.todos;
newTodo = {
id: Utils.uuid(),
title: val,
completed: false
};
this.setState({todos: todos.concat([newTodo])});
this.refs.newField.getDOMNode().value = '';
}
return false;
},
toggleAll: function (event) {
var checked = event.target.checked;
this.state.todos.forEach(function (todo) {
todo.completed = checked;
});
this.setState({todos: this.state.todos});
},
toggle: function (todo) {
todo.completed = !todo.completed;
this.setState({todos: this.state.todos});
},
destroy: function (todo) {
var newTodos = this.state.todos.filter(function (candidate) {
return candidate.id !== todo.id;
});
this.setState({todos: newTodos});
},
edit: function (todo, callback) {
// refer to todoItem.js `handleEdit` for the reasoning behind the
// callback
this.setState({editing: todo.id}, function () {
callback();
});
},
save: function (todo, text) {
todo.title = text;
this.setState({todos: this.state.todos, editing: null});
},
cancel: function () {
this.setState({editing: null});
},
clearCompleted: function () {
var newTodos = this.state.todos.filter(function (todo) {
return !todo.completed;
});
this.setState({todos: newTodos});
},
componentDidUpdate: function () {
Utils.store('react-todos', this.state.todos);
},
render: function () {
var footer = null;
var main = null;
var todoItems = {};
var activeTodoCount;
var completedCount;
var shownTodos = this.state.todos.filter(function (todo) {
switch (this.state.nowShowing) {
case ACTIVE_TODOS:
return !todo.completed;
case COMPLETED_TODOS:
return todo.completed;
default:
return true;
}
}.bind(this));
shownTodos.forEach(function (todo) {
todoItems[todo.id] = (
<TodoItem
todo={todo}
onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)}
onEdit={this.edit.bind(this, todo)}
editing={this.state.editing === todo.id}
onSave={this.save.bind(this, todo)}
onCancel={this.cancel}
/>
);
}.bind(this));
activeTodoCount = this.state.todos.filter(function (todo) {
return !todo.completed;
}).length;
completedCount = this.state.todos.length - activeTodoCount;
if (activeTodoCount || completedCount) {
footer =
<TodoFooter
count={activeTodoCount}
completedCount={completedCount}
nowShowing={this.state.nowShowing}
onClearCompleted={this.clearCompleted}
/>;
}
if (this.state.todos.length) {
main = (
<section id="main">
<input
id="toggle-all"
type="checkbox"
onChange={this.toggleAll}
checked={activeTodoCount === 0}
/>
<ul id="todo-list">
{todoItems}
</ul>
</section>
);
}
return (
<div>
<header id="header">
<h1>todos</h1>
<input
ref="newField"
id="new-todo"
placeholder="What needs to be done?"
onKeyDown={this.handleNewTodoKeyDown}
/>
</header>
{main}
{footer}
</div>
);
}
});
React.renderComponent(<TodoApp />, document.getElementById('todoapp'));
React.renderComponent(
<div>
<p>Double-click to edit a todo</p>
<p>Created by{' '}
<a href="http://github.com/petehunt/">petehunt</a>
</p>
<p>Part of{' '}<a href="http://todomvc.com">TodoMVC</a></p>
</div>,
document.getElementById('info'));
})(window, React);

View File

@@ -1,58 +0,0 @@
/**
* @jsx React.DOM
*/
/*jshint quotmark:false */
/*jshint white:false */
/*jshint trailing:false */
/*jshint newcap:false */
/*global React, ALL_TODOS, ACTIVE_TODOS, Utils, COMPLETED_TODOS */
(function (window) {
'use strict';
window.TodoFooter = React.createClass({
render: function () {
var activeTodoWord = Utils.pluralize(this.props.count, 'item');
var clearButton = null;
if (this.props.completedCount > 0) {
clearButton = (
<button
id="clear-completed"
onClick={this.props.onClearCompleted}>
{''}Clear completed ({this.props.completedCount}){''}
</button>
);
}
var show = {
ALL_TODOS: '',
ACTIVE_TODOS: '',
COMPLETED_TODOS: ''
};
show[this.props.nowShowing] = 'selected';
return (
<footer id="footer">
<span id="todo-count">
<strong>{this.props.count}</strong>
{' '}{activeTodoWord}{' '}left{''}
</span>
<ul id="filters">
<li>
<a href="#/" className={show[ALL_TODOS]}>All</a>
</li>
{' '}
<li>
<a href="#/active" className={show[ACTIVE_TODOS]}>Active</a>
</li>
{' '}
<li>
<a href="#/completed" className={show[COMPLETED_TODOS]}>Completed</a>
</li>
</ul>
{clearButton}
</footer>
);
}
});
})(window);

View File

@@ -1,93 +0,0 @@
/**
* @jsx React.DOM
*/
/*jshint quotmark: false */
/*jshint white: false */
/*jshint trailing: false */
/*jshint newcap: false */
/*global React, Utils */
(function (window) {
'use strict';
var ESCAPE_KEY = 27;
var ENTER_KEY = 13;
window.TodoItem = React.createClass({
handleSubmit: function () {
var val = this.state.editText.trim();
if (val) {
this.props.onSave(val);
this.setState({editText: val});
} else {
this.props.onDestroy();
}
return false;
},
handleEdit: function () {
// react optimizes renders by batching them. This means you can't call
// parent's `onEdit` (which in this case triggeres a re-render), and
// immediately manipulate the DOM as if the rendering's over. Put it as a
// callback. Refer to app.js' `edit` method
this.props.onEdit(function () {
var node = this.refs.editField.getDOMNode();
node.focus();
node.setSelectionRange(node.value.length, node.value.length);
}.bind(this));
},
handleKeyDown: function (event) {
if (event.keyCode === ESCAPE_KEY) {
this.setState({editText: this.props.todo.title});
this.props.onCancel();
} else if (event.keyCode === ENTER_KEY) {
this.handleSubmit();
} else {
this.setState({editText: event.target.value});
}
},
handleChange: function (event) {
this.setState({editText: event.target.value});
},
getInitialState: function () {
return {editText: this.props.todo.title};
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.todo.title !== this.props.todo.title) {
this.setState(this.getInitialState());
}
},
render: function () {
return (
<li className={Utils.stringifyObjKeys({
completed: this.props.todo.completed,
editing: this.props.editing
})}>
<div className="view">
<input
className="toggle"
type="checkbox"
checked={this.props.todo.completed ? 'checked' : null}
onChange={this.props.onToggle}
/>
<label onDoubleClick={this.handleEdit}>
{this.props.todo.title}
</label>
<button className='destroy' onClick={this.props.onDestroy} />
</div>
<input
ref="editField"
className="edit"
value={this.state.editText}
onBlur={this.handleSubmit}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
/>
</li>
);
}
});
})(window);

View File

@@ -1,49 +0,0 @@
(function (window) {
'use strict';
window.Utils = {
uuid: function () {
/*jshint bitwise:false */
var i, random;
var uuid = '';
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-';
}
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
.toString(16);
}
return uuid;
},
pluralize: function (count, word) {
return count === 1 ? word : word + 's';
},
store: function (namespace, data) {
if (data) {
return localStorage.setItem(namespace, JSON.stringify(data));
}
var store = localStorage.getItem(namespace);
return (store && JSON.parse(store)) || [];
},
stringifyObjKeys: function (obj) {
var s = '';
var key;
for (key in obj) {
if (obj.hasOwnProperty(key) && obj[key]) {
s += key + ' ';
}
}
return s.trim();
}
};
})(window);

View File

@@ -5,7 +5,7 @@ module.exports = {
react_docs: {
files: [
{
src: ['react.min.js', 'JSXTransformer.js'],
src: ['react.js', 'JSXTransformer.js'],
dest: 'docs/js/',
cwd: 'build/',
expand: true

View File

@@ -14,9 +14,7 @@ var normal = {
getConfig: function() {
return {
commonerConfig: grunt.config.data.pkg.commonerConfig,
constants: {
__VERSION__: grunt.config.data.pkg.version
}
constants: {}
};
},
sourceDir: "src",

View File

@@ -10,7 +10,7 @@ var reactVersionExp = /\bReact\.version\s*=\s*['"]([^'"]+)['"];/;
module.exports = function() {
var reactVersion = reactVersionExp.exec(
grunt.file.read('./src/browser/React.js')
grunt.file.read('./src/browser/ui/React.js')
)[1];
var npmReactVersion = grunt.file.readJSON('./npm-react/package.json').version;
var reactToolsVersion = grunt.config.data.pkg.version;

12
main.js
View File

@@ -1,10 +1,16 @@
'use strict';
var visitors = require('./vendor/fbtransform/visitors').transformVisitors;
var visitors = require('./vendor/fbtransform/visitors');
var transform = require('jstransform').transform;
module.exports = {
transform: function(code) {
return transform(visitors.react, code).code;
transform: function(code, options) {
var visitorList;
if (options && options.harmony) {
visitorList = visitors.getAllVisitors();
} else {
visitorList = visitors.transformVisitors.react;
}
return transform(visitorList, code).code;
}
};

View File

@@ -1,7 +1,7 @@
{
"name": "react",
"description": "React is a JavaScript library for building user interfaces.",
"version": "0.9.0",
"version": "0.10.0",
"keywords": [
"react"
],

2725
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "react-tools",
"description": "A set of complementary tools to React, including the JSX transformer.",
"version": "0.9.0",
"version": "0.10.0",
"keywords": [
"react",
"jsx",
@@ -33,13 +33,13 @@
"url": "https://github.com/facebook/react"
},
"dependencies": {
"commoner": "~0.9.1",
"commoner": "^0.9.2",
"esprima-fb": "~3001.1.0-dev-harmony-fb",
"jstransform": "~3.0.0"
},
"devDependencies": {
"benchmark": "~1.0.0",
"browserify": "~3.20.0",
"browserify": "^3.33.0",
"coverify": "~1.0.4",
"envify": "~1.2.0",
"es3ify": "~0.1.2",
@@ -75,5 +75,8 @@
"preferGlobal": true,
"commonerConfig": {
"version": 4
},
"scripts": {
"test": "./node_modules/.bin/grunt test"
}
}

View File

@@ -23,7 +23,6 @@
"unused": "vars",
"globals": {
"__VERSION__": false,
"__DEV__": false,
"require": false,
"module": false,

View File

@@ -29,7 +29,7 @@ var shallowEqual = require('shallowEqual');
*
* Example:
*
* var ReactComponentWithPureRender =
* var ReactComponentWithPureRenderMixin =
* require('ReactComponentWithPureRenderMixin');
* React.createClass({
* mixins: [ReactComponentWithPureRenderMixin],

View File

@@ -0,0 +1,48 @@
/**
* Copyright 2013-2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactRAFBatchingStrategy
* @typechecks static-only
*/
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactUpdates = require('ReactUpdates');
var requestAnimationFrame = require('requestAnimationFrame');
function tick() {
ReactUpdates.flushBatchedUpdates();
requestAnimationFrame(tick);
}
var ReactRAFBatchingStrategy = {
isBatchingUpdates: true,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
batchedUpdates: function(callback, param) {
callback(param);
}
};
if (ExecutionEnvironment.canUseDOM) {
requestAnimationFrame(tick);
}
module.exports = ReactRAFBatchingStrategy;

View File

@@ -0,0 +1,95 @@
/**
* Copyright 2013-2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @emails react-core
*/
"use strict";
var update = require('update');
describe('update', function() {
it('should support push', function() {
expect(update([1], {$push: [7]})).toEqual([1, 7]);
expect(update.bind(null, [], {$push: 7})).toThrow(
'Invariant Violation: update(): expected spec of $push to be an ' +
'array; got 7. Did you forget to wrap your parameter in an array?'
);
expect(update.bind(null, 1, {$push: 7})).toThrow(
'Invariant Violation: update(): expected target of $push to be an ' +
'array; got 1.'
);
});
it('should support unshift', function() {
expect(update([1], {$unshift: [7]})).toEqual([7, 1]);
expect(update.bind(null, [], {$unshift: 7})).toThrow(
'Invariant Violation: update(): expected spec of $unshift to be an ' +
'array; got 7. Did you forget to wrap your parameter in an array?'
);
expect(update.bind(null, 1, {$unshift: 7})).toThrow(
'Invariant Violation: update(): expected target of $unshift to be an ' +
'array; got 1.'
);
});
it('should support splice', function() {
expect(update([1, 4, 3], {$splice: [[1, 1, 2]]})).toEqual([1, 2, 3]);
expect(update.bind(null, [], {$splice: 1})).toThrow(
'Invariant Violation: update(): expected spec of $splice to be an ' +
'array of arrays; got 1. Did you forget to wrap your parameters in an '+
'array?'
);
expect(update.bind(null, [], {$splice: [1]})).toThrow(
'Invariant Violation: update(): expected spec of $splice to be an ' +
'array of arrays; got 1. Did you forget to wrap your parameters in an ' +
'array?'
);
expect(update.bind(null, 1, {$splice: 7})).toThrow(
'Invariant Violation: Expected $splice target to be an array; got 1'
);
});
it('should support merge', function() {
expect(update({a: 'b'}, {$merge: {c: 'd'}})).toEqual({a: 'b', c: 'd'});
expect(update.bind(null, {}, {$merge: 7})).toThrow(
'Invariant Violation: update(): $merge expects a spec of type ' +
'\'object\'; got 7'
);
expect(update.bind(null, 7, {$merge: {a: 'b'}})).toThrow(
'Invariant Violation: update(): $merge expects a target of type ' +
'\'object\'; got 7'
);
});
it('should support set', function() {
expect(update({a: 'b'}, {$set: {c: 'd'}})).toEqual({c: 'd'});
});
it('should support deep updates', function() {
expect(update({a: 'b', c: {d: 'e'}}, {c: {d: {$set: 'f'}}})).toEqual({
a: 'b',
c: {d: 'f'}
});
});
it('should require a directive', function() {
expect(update.bind(null, {a: 'b'}, {a: 'c'})).toThrow(
'Invariant Violation: update(): You provided a key path to update() ' +
'that did not contain one of $push, $unshift, $splice, $set, $merge. ' +
'Did you forget to include {$set: ...}?'
);
});
});

View File

@@ -15,7 +15,6 @@
*
* @typechecks
* @providesModule ReactCSSTransitionGroup
* @jsx React.DOM
*/
"use strict";
@@ -43,21 +42,22 @@ var ReactCSSTransitionGroup = React.createClass({
// We need to provide this childFactory so that
// ReactCSSTransitionGroupChild can receive updates to name, enter, and
// leave while it is leaving.
return (
<ReactCSSTransitionGroupChild
name={this.props.transitionName}
enter={this.props.transitionEnter}
leave={this.props.transitionLeave}>
{child}
</ReactCSSTransitionGroupChild>
return ReactCSSTransitionGroupChild(
{
name: this.props.transitionName,
enter: this.props.transitionEnter,
leave: this.props.transitionLeave
},
child
);
},
render: function() {
return this.transferPropsTo(
<ReactTransitionGroup childFactory={this._wrapChild}>
{this.props.children}
</ReactTransitionGroup>
ReactTransitionGroup(
{childFactory: this._wrapChild},
this.props.children
)
);
}
});

159
src/addons/update.js Normal file
View File

@@ -0,0 +1,159 @@
/**
* Copyright 2013-2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule update
*/
"use strict";
var copyProperties = require('copyProperties');
var keyOf = require('keyOf');
var invariant = require('invariant');
function shallowCopy(x) {
if (Array.isArray(x)) {
return x.concat();
} else if (x && typeof x === 'object') {
return copyProperties(new x.constructor(), x);
} else {
return x;
}
}
var DIRECTIVE_PUSH = keyOf({$push: null});
var DIRECTIVE_UNSHIFT = keyOf({$unshift: null});
var DIRECTIVE_SPLICE = keyOf({$splice: null});
var DIRECTIVE_SET = keyOf({$set: null});
var DIRECTIVE_MERGE = keyOf({$merge: null});
var ALL_DIRECTIVES_LIST = [
DIRECTIVE_PUSH,
DIRECTIVE_UNSHIFT,
DIRECTIVE_SPLICE,
DIRECTIVE_SET,
DIRECTIVE_MERGE
];
var ALL_DIRECTIVES_SET = {};
ALL_DIRECTIVES_LIST.forEach(function(directive) {
ALL_DIRECTIVES_SET[directive] = true;
});
function invariantArrayCase(value, spec, directive) {
invariant(
Array.isArray(value),
'update(): expected target of %s to be an array; got %s.',
directive,
value
);
var specValue = spec[directive];
invariant(
Array.isArray(specValue),
'update(): expected spec of %s to be an array; got %s. ' +
'Did you forget to wrap your parameter in an array?',
directive,
specValue
);
}
function update(value, spec) {
invariant(
typeof spec === 'object',
'update(): You provided a key path to update() that did not contain one ' +
'of %s. Did you forget to include {%s: ...}?',
ALL_DIRECTIVES_LIST.join(', '),
DIRECTIVE_SET
);
if (spec.hasOwnProperty(DIRECTIVE_SET)) {
invariant(
Object.keys(spec).length === 1,
'Cannot have more than one key in an object with %s',
DIRECTIVE_SET
);
return spec[DIRECTIVE_SET];
}
var nextValue = shallowCopy(value);
if (spec.hasOwnProperty(DIRECTIVE_MERGE)) {
var mergeObj = spec[DIRECTIVE_MERGE];
invariant(
mergeObj && typeof mergeObj === 'object',
'update(): %s expects a spec of type \'object\'; got %s',
DIRECTIVE_MERGE,
mergeObj
);
invariant(
nextValue && typeof nextValue === 'object',
'update(): %s expects a target of type \'object\'; got %s',
DIRECTIVE_MERGE,
nextValue
);
copyProperties(nextValue, spec[DIRECTIVE_MERGE]);
}
if (spec.hasOwnProperty(DIRECTIVE_PUSH)) {
invariantArrayCase(value, spec, DIRECTIVE_PUSH);
spec[DIRECTIVE_PUSH].forEach(function(item) {
nextValue.push(item);
});
}
if (spec.hasOwnProperty(DIRECTIVE_UNSHIFT)) {
invariantArrayCase(value, spec, DIRECTIVE_UNSHIFT);
spec[DIRECTIVE_UNSHIFT].forEach(function(item) {
nextValue.unshift(item);
});
}
if (spec.hasOwnProperty(DIRECTIVE_SPLICE)) {
invariant(
Array.isArray(value),
'Expected %s target to be an array; got %s',
DIRECTIVE_SPLICE,
value
);
invariant(
Array.isArray(spec[DIRECTIVE_SPLICE]),
'update(): expected spec of %s to be an array of arrays; got %s. ' +
'Did you forget to wrap your parameters in an array?',
DIRECTIVE_SPLICE,
spec[DIRECTIVE_SPLICE]
);
spec[DIRECTIVE_SPLICE].forEach(function(args) {
invariant(
Array.isArray(args),
'update(): expected spec of %s to be an array of arrays; got %s. ' +
'Did you forget to wrap your parameters in an array?',
DIRECTIVE_SPLICE,
spec[DIRECTIVE_SPLICE]
);
nextValue.splice.apply(nextValue, args);
});
}
for (var k in spec) {
if (!ALL_DIRECTIVES_SET[k]) {
nextValue[k] = update(value[k], spec[k]);
}
}
return nextValue;
}
module.exports = update;

View File

@@ -72,12 +72,12 @@ var ReactDOM = objMapKeyVal({
a: false,
abbr: false,
address: false,
area: false,
area: true,
article: false,
aside: false,
audio: false,
b: false,
base: false,
base: true,
bdi: false,
bdo: false,
big: false,
@@ -127,7 +127,7 @@ var ReactDOM = objMapKeyVal({
label: false,
legend: false,
li: false,
link: false,
link: true,
main: false,
map: false,
mark: false,
@@ -156,7 +156,7 @@ var ReactDOM = objMapKeyVal({
section: false,
select: false,
small: false,
source: false,
source: true,
span: false,
strong: false,
style: false,
@@ -178,7 +178,7 @@ var ReactDOM = objMapKeyVal({
ul: false,
'var': false,
video: false,
wbr: false,
wbr: true,
// SVG
circle: false,

View File

@@ -19,7 +19,6 @@
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
var PooledClass = require('PooledClass');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactInputSelection = require('ReactInputSelection');
@@ -127,6 +126,12 @@ var TRANSACTION_WRAPPERS = [
*/
function ReactReconcileTransaction() {
this.reinitializeTransaction();
// Only server-side rendering really needs this option (see
// `ReactServerRendering`), but server-side uses
// `ReactServerRenderingTransaction` instead. This option is here so that it's
// accessible and defaults to false when `ReactDOMComponent` and
// `ReactTextComponent` checks it in `mountComponent`.`
this.renderToStaticMarkup = false;
this.reactMountReady = ReactMountReady.getPooled(null);
this.putListenerQueue = ReactPutListenerQueue.getPooled();
}
@@ -140,11 +145,7 @@ var Mixin = {
* TODO: convert to array<TransactionWrapper>
*/
getTransactionWrappers: function() {
if (ExecutionEnvironment.canUseDOM) {
return TRANSACTION_WRAPPERS;
} else {
return [];
}
return TRANSACTION_WRAPPERS;
},
/**

View File

@@ -20,6 +20,7 @@
"use strict";
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactComponent = require('ReactComponent');
var escapeTextForBrowser = require('escapeTextForBrowser');
@@ -44,7 +45,18 @@ var ReactTextComponent = function(initialText) {
this.construct({text: initialText});
};
/**
* Used to clone the text descriptor object before it's mounted.
*
* @param {object} props
* @return {object} A new ReactTextComponent instance
*/
ReactTextComponent.ConvenienceConstructor = function(props) {
return new ReactTextComponent(props.text);
};
mixInto(ReactTextComponent, ReactComponent.Mixin);
mixInto(ReactTextComponent, ReactBrowserComponentMixin);
mixInto(ReactTextComponent, {
/**
@@ -52,7 +64,7 @@ mixInto(ReactTextComponent, {
* any features besides containing text content.
*
* @param {string} rootID DOM ID of the root node.
* @param {ReactReconcileTransaction} transaction
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @param {number} mountDepth number of components in the owner hierarchy
* @return {string} Markup for this text node.
* @internal
@@ -64,9 +76,19 @@ mixInto(ReactTextComponent, {
transaction,
mountDepth
);
var escapedText = escapeTextForBrowser(this.props.text);
if (transaction.renderToStaticMarkup) {
// Normally we'd wrap this in a `span` for the reasons stated above, but
// since this is a situation where React won't take over (static pages),
// we can simply return the text as it is.
return escapedText;
}
return (
'<span ' + DOMPropertyOperations.createMarkupForID(rootID) + '>' +
escapeTextForBrowser(this.props.text) +
escapedText +
'</span>'
);
},

View File

@@ -33,6 +33,7 @@ var ReactCSSTransitionGroup = require('ReactCSSTransitionGroup');
var cx = require('cx');
var cloneWithProps = require('cloneWithProps');
var update = require('update');
React.addons = {
LinkedStateMixin: LinkedStateMixin,
@@ -40,7 +41,8 @@ React.addons = {
TransitionGroup: ReactTransitionGroup,
classSet: cx,
cloneWithProps: cloneWithProps
cloneWithProps: cloneWithProps,
update: update
};
if (__DEV__) {

View File

@@ -21,13 +21,15 @@
var ReactComponent = require('ReactComponent');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactMarkupChecksum = require('ReactMarkupChecksum');
var ReactReconcileTransaction = require('ReactReconcileTransaction');
var ReactServerRenderingTransaction =
require('ReactServerRenderingTransaction');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
/**
* @param {ReactComponent} component
* @return {string} the markup
* @return {string} the HTML markup
*/
function renderComponentToString(component) {
invariant(
@@ -41,19 +43,47 @@ function renderComponentToString(component) {
'returns the generated markup. Please remove the second parameter.'
);
var id = ReactInstanceHandles.createReactRootID();
var transaction = ReactReconcileTransaction.getPooled();
transaction.reinitializeTransaction();
var transaction;
try {
var id = ReactInstanceHandles.createReactRootID();
transaction = ReactServerRenderingTransaction.getPooled(false);
return transaction.perform(function() {
var markup = component.mountComponent(id, transaction, 0);
var componentInstance = instantiateReactComponent(component);
var markup = componentInstance.mountComponent(id, transaction, 0);
return ReactMarkupChecksum.addChecksumToMarkup(markup);
}, null);
} finally {
ReactReconcileTransaction.release(transaction);
ReactServerRenderingTransaction.release(transaction);
}
}
/**
* @param {ReactComponent} component
* @return {string} the HTML markup, without the extra React ID and checksum
* (for generating static pages)
*/
function renderComponentToStaticMarkup(component) {
invariant(
ReactComponent.isValidComponent(component),
'renderComponentToStaticMarkup(): You must pass a valid ReactComponent.'
);
var transaction;
try {
var id = ReactInstanceHandles.createReactRootID();
transaction = ReactServerRenderingTransaction.getPooled(true);
return transaction.perform(function() {
var componentInstance = instantiateReactComponent(component);
return componentInstance.mountComponent(id, transaction, 0);
}, null);
} finally {
ReactServerRenderingTransaction.release(transaction);
}
}
module.exports = {
renderComponentToString: renderComponentToString
renderComponentToString: renderComponentToString,
renderComponentToStaticMarkup: renderComponentToStaticMarkup
};

View File

@@ -0,0 +1,116 @@
/**
* Copyright 2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactServerRenderingTransaction
* @typechecks
*/
"use strict";
var PooledClass = require('PooledClass');
var ReactMountReady = require('ReactMountReady');
var ReactPutListenerQueue = require('ReactPutListenerQueue');
var Transaction = require('Transaction');
var emptyFunction = require('emptyFunction');
var mixInto = require('mixInto');
/**
* Provides a `ReactMountReady` queue for collecting `onDOMReady` callbacks
* during the performing of the transaction.
*/
var ON_DOM_READY_QUEUEING = {
/**
* Initializes the internal `onDOMReady` queue.
*/
initialize: function() {
this.reactMountReady.reset();
},
close: emptyFunction
};
var PUT_LISTENER_QUEUEING = {
initialize: function() {
this.putListenerQueue.reset();
},
close: emptyFunction
};
/**
* Executed within the scope of the `Transaction` instance. Consider these as
* being member methods, but with an implied ordering while being isolated from
* each other.
*/
var TRANSACTION_WRAPPERS = [
PUT_LISTENER_QUEUEING,
ON_DOM_READY_QUEUEING
];
/**
* @class ReactServerRenderingTransaction
* @param {boolean} renderToStaticMarkup
*/
function ReactServerRenderingTransaction(renderToStaticMarkup) {
this.reinitializeTransaction();
this.renderToStaticMarkup = renderToStaticMarkup;
this.reactMountReady = ReactMountReady.getPooled(null);
this.putListenerQueue = ReactPutListenerQueue.getPooled();
}
var Mixin = {
/**
* @see Transaction
* @abstract
* @final
* @return {array} Empty list of operation wrap proceedures.
*/
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
/**
* @return {object} The queue to collect `onDOMReady` callbacks with.
* TODO: convert to ReactMountReady
*/
getReactMountReady: function() {
return this.reactMountReady;
},
getPutListenerQueue: function() {
return this.putListenerQueue;
},
/**
* `PooledClass` looks for this, and will invoke this before allowing this
* instance to be resused.
*/
destructor: function() {
ReactMountReady.release(this.reactMountReady);
this.reactMountReady = null;
ReactPutListenerQueue.release(this.putListenerQueue);
this.putListenerQueue = null;
}
};
mixInto(ReactServerRenderingTransaction, Transaction.Mixin);
mixInto(ReactServerRenderingTransaction, Mixin);
PooledClass.addPoolingTo(ReactServerRenderingTransaction);
module.exports = ReactServerRenderingTransaction;

View File

@@ -55,201 +55,323 @@ describe('ReactServerRendering', function() {
ID_ATTRIBUTE_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
});
it('should generate simple markup', function() {
var response = ReactServerRendering.renderComponentToString(
<span>hello world</span>
);
expect(response).toMatch(
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">hello world</span>'
);
});
it('should not register event listeners', function() {
var EventPluginHub = require('EventPluginHub');
var cb = mocks.getMockFunction();
var response = ReactServerRendering.renderComponentToString(
<span onClick={cb}>hello world</span>
);
expect(EventPluginHub.__getListenerBank()).toEqual({});
});
it('should render composite components', function() {
var Parent = React.createClass({
render: function() {
return <div><Child name="child" /></div>;
}
});
var Child = React.createClass({
render: function() {
return <span>My name is {this.props.name}</span>;
}
});
var response = ReactServerRendering.renderComponentToString(
<Parent />
);
expect(response).toMatch(
'<div ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">My name is </span>' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">child</span>' +
'</span>' +
'</div>'
);
});
it('should only execute certain lifecycle methods', function() {
var lifecycle = [];
var TestComponent = React.createClass({
componentWillMount: function() {
lifecycle.push('componentWillMount');
},
componentDidMount: function() {
lifecycle.push('componentDidMount');
},
getInitialState: function() {
lifecycle.push('getInitialState');
return {name: 'TestComponent'};
},
render: function() {
lifecycle.push('render');
return <span>Component name: {this.state.name}</span>;
},
componentWillUpdate: function() {
lifecycle.push('componentWillUpdate');
},
componentDidUpdate: function() {
lifecycle.push('componentDidUpdate');
},
shouldComponentUpdate: function() {
lifecycle.push('shouldComponentUpdate');
},
componentWillReceiveProps: function() {
lifecycle.push('componentWillReceiveProps');
},
componentWillUnmount: function() {
lifecycle.push('componentWillUnmount');
}
describe('renderComponentToString', function() {
it('should generate simple markup', function() {
var response = ReactServerRendering.renderComponentToString(
<span>hello world</span>
);
expect(response).toMatch(
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">hello world</span>'
);
});
var response = ReactServerRendering.renderComponentToString(
<TestComponent />
);
it('should not register event listeners', function() {
var EventPluginHub = require('EventPluginHub');
var cb = mocks.getMockFunction();
expect(response).toMatch(
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">Component name: </span>' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">TestComponent</span>' +
'</span>'
);
expect(lifecycle).toEqual(
['getInitialState', 'componentWillMount', 'render']
);
});
ReactServerRendering.renderComponentToString(
<span onClick={cb}>hello world</span>
);
expect(EventPluginHub.__getListenerBank()).toEqual({});
});
it('should have the correct mounting behavior', function() {
// This test is testing client-side behavior.
ExecutionEnvironment.canUseDOM = true;
it('should render composite components', function() {
var Parent = React.createClass({
render: function() {
return <div><Child name="child" /></div>;
}
});
var Child = React.createClass({
render: function() {
return <span>My name is {this.props.name}</span>;
}
});
var response = ReactServerRendering.renderComponentToString(
<Parent />
);
expect(response).toMatch(
'<div ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">My name is </span>' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">child</span>' +
'</span>' +
'</div>'
);
});
var mountCount = 0;
var numClicks = 0;
it('should only execute certain lifecycle methods', function() {
function runTest() {
var lifecycle = [];
var TestComponent = React.createClass({
componentWillMount: function() {
lifecycle.push('componentWillMount');
},
componentDidMount: function() {
lifecycle.push('componentDidMount');
},
getInitialState: function() {
lifecycle.push('getInitialState');
return {name: 'TestComponent'};
},
render: function() {
lifecycle.push('render');
return <span>Component name: {this.state.name}</span>;
},
componentWillUpdate: function() {
lifecycle.push('componentWillUpdate');
},
componentDidUpdate: function() {
lifecycle.push('componentDidUpdate');
},
shouldComponentUpdate: function() {
lifecycle.push('shouldComponentUpdate');
},
componentWillReceiveProps: function() {
lifecycle.push('componentWillReceiveProps');
},
componentWillUnmount: function() {
lifecycle.push('componentWillUnmount');
}
});
var TestComponent = React.createClass({
componentDidMount: function() {
mountCount++;
},
click: function() {
numClicks++;
},
render: function() {
return (
<span ref="span" onClick={this.click}>Name: {this.props.name}</span>
var response = ReactServerRendering.renderComponentToString(
<TestComponent />
);
expect(response).toMatch(
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">Component name: </span>' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">TestComponent</span>' +
'</span>'
);
expect(lifecycle).toEqual(
['getInitialState', 'componentWillMount', 'render']
);
}
runTest();
// This should work the same regardless of whether you can use DOM or not.
ExecutionEnvironment.canUseDOM = true;
runTest();
});
var element = document.createElement('div');
React.renderComponent(<TestComponent />, element);
it('should have the correct mounting behavior', function() {
// This test is testing client-side behavior.
ExecutionEnvironment.canUseDOM = true;
var lastMarkup = element.innerHTML;
var mountCount = 0;
var numClicks = 0;
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
React.renderComponent(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
var TestComponent = React.createClass({
componentDidMount: function() {
mountCount++;
},
click: function() {
numClicks++;
},
render: function() {
return (
<span ref="span" onClick={this.click}>Name: {this.props.name}</span>
);
}
});
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
React.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
React.renderComponent(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
var element = document.createElement('div');
React.renderComponent(<TestComponent />, element);
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should be
// unchanged. We will append a sentinel at the end of innerHTML to be sure
// that innerHTML was not changed.
React.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
var lastMarkup = element.innerHTML;
ExecutionEnvironment.canUseDOM = false;
lastMarkup = ReactServerRendering.renderComponentToString(
<TestComponent name="x" />
);
ExecutionEnvironment.canUseDOM = true;
element.innerHTML = lastMarkup + ' __sentinel__';
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
React.renderComponent(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
React.renderComponent(<TestComponent name="x" />, element);
expect(mountCount).toEqual(3);
expect(element.innerHTML.indexOf('__sentinel__') > -1).toBe(true);
React.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
React.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
React.renderComponent(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
var _warn = console.warn;
console.warn = mocks.getMockFunction();
element.innerHTML = lastMarkup;
var instance = React.renderComponent(<TestComponent name="y" />, element);
expect(mountCount).toEqual(4);
expect(console.warn.mock.calls.length).toBe(1);
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
console.warn = _warn;
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should
// be unchanged. We will append a sentinel at the end of innerHTML to be
// sure that innerHTML was not changed.
React.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Ensure the events system works
expect(numClicks).toEqual(0);
ReactTestUtils.Simulate.click(instance.refs.span.getDOMNode());
expect(numClicks).toEqual(1);
ExecutionEnvironment.canUseDOM = false;
lastMarkup = ReactServerRendering.renderComponentToString(
<TestComponent name="x" />
);
ExecutionEnvironment.canUseDOM = true;
element.innerHTML = lastMarkup + ' __sentinel__';
React.renderComponent(<TestComponent name="x" />, element);
expect(mountCount).toEqual(3);
expect(element.innerHTML.indexOf('__sentinel__') > -1).toBe(true);
React.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
var _warn = console.warn;
console.warn = mocks.getMockFunction();
element.innerHTML = lastMarkup;
var instance = React.renderComponent(<TestComponent name="y" />, element);
expect(mountCount).toEqual(4);
expect(console.warn.mock.calls.length).toBe(1);
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
console.warn = _warn;
// Ensure the events system works
expect(numClicks).toEqual(0);
ReactTestUtils.Simulate.click(instance.refs.span.getDOMNode());
expect(numClicks).toEqual(1);
});
it('should throw with silly args', function() {
expect(
ReactServerRendering.renderComponentToString.bind(
ReactServerRendering,
'not a component'
)
).toThrow(
'Invariant Violation: renderComponentToString(): You must pass ' +
'a valid ReactComponent.'
);
});
it('should provide guidance for breaking API changes', function() {
expect(
ReactServerRendering.renderComponentToString.bind(
ReactServerRendering,
<div />,
function(){}
)
).toThrow(
'Invariant Violation: renderComponentToString(): This function ' +
'became synchronous and now returns the generated markup. Please ' +
'remove the second parameter.'
);
});
});
it('should throw with silly args', function() {
expect(
ReactServerRendering.renderComponentToString.bind(
ReactServerRendering,
'not a component'
)
).toThrow(
'Invariant Violation: renderComponentToString(): You must pass ' +
'a valid ReactComponent.'
);
});
describe('renderComponentToStaticMarkup', function() {
it('should not put checksum and React ID on components', function() {
var lifecycle = [];
var NestedComponent = React.createClass({
render: function() {
return <div>inner text</div>;
}
});
it('should provide guidance for breaking API changes', function() {
expect(
ReactServerRendering.renderComponentToString.bind(
ReactServerRendering,
<div />,
function(){}
)
).toThrow(
'Invariant Violation: renderComponentToString(): This function became ' +
'synchronous and now returns the generated markup. Please remove the ' +
'second parameter.'
);
var TestComponent = React.createClass({
render: function() {
lifecycle.push('render');
return <span><NestedComponent /></span>;
}
});
var response = ReactServerRendering.renderComponentToStaticMarkup(
<TestComponent />
);
expect(response).toBe('<span><div>inner text</div></span>');
});
it('should not put checksum and React ID on text components', function() {
var TestComponent = React.createClass({
render: function() {
return <span>{'hello'} {'world'}</span>;
}
});
var response = ReactServerRendering.renderComponentToStaticMarkup(
<TestComponent />
);
expect(response).toBe('<span>hello world</span>');
});
it('should not register event listeners', function() {
var EventPluginHub = require('EventPluginHub');
var cb = mocks.getMockFunction();
ReactServerRendering.renderComponentToString(
<span onClick={cb}>hello world</span>
);
expect(EventPluginHub.__getListenerBank()).toEqual({});
});
it('should only execute certain lifecycle methods', function() {
function runTest() {
var lifecycle = [];
var TestComponent = React.createClass({
componentWillMount: function() {
lifecycle.push('componentWillMount');
},
componentDidMount: function() {
lifecycle.push('componentDidMount');
},
getInitialState: function() {
lifecycle.push('getInitialState');
return {name: 'TestComponent'};
},
render: function() {
lifecycle.push('render');
return <span>Component name: {this.state.name}</span>;
},
componentWillUpdate: function() {
lifecycle.push('componentWillUpdate');
},
componentDidUpdate: function() {
lifecycle.push('componentDidUpdate');
},
shouldComponentUpdate: function() {
lifecycle.push('shouldComponentUpdate');
},
componentWillReceiveProps: function() {
lifecycle.push('componentWillReceiveProps');
},
componentWillUnmount: function() {
lifecycle.push('componentWillUnmount');
}
});
var response = ReactServerRendering.renderComponentToStaticMarkup(
<TestComponent />
);
expect(response).toBe('<span>Component name: TestComponent</span>');
expect(lifecycle).toEqual(
['getInitialState', 'componentWillMount', 'render']
);
}
runTest();
// This should work the same regardless of whether you can use DOM or not.
ExecutionEnvironment.canUseDOM = true;
runTest();
});
it('should throw with silly args', function() {
expect(
ReactServerRendering.renderComponentToStaticMarkup.bind(
ReactServerRendering,
'not a component'
)
).toThrow(
'Invariant Violation: renderComponentToStaticMarkup(): You must pass ' +
'a valid ReactComponent.'
);
});
});
});

View File

@@ -60,6 +60,8 @@ var React = {
ReactMount.renderComponent
),
renderComponentToString: ReactServerRendering.renderComponentToString,
renderComponentToStaticMarkup:
ReactServerRendering.renderComponentToStaticMarkup,
unmountComponentAtNode: ReactMount.unmountComponentAtNode,
isValidClass: ReactCompositeComponent.isValidClass,
isValidComponent: ReactComponent.isValidComponent,
@@ -90,6 +92,6 @@ if (__DEV__) {
// Version exists only in the open-source version of React, not in Facebook's
// internal version.
React.version = '0.9.0';
React.version = '0.10.0';
module.exports = React;

View File

@@ -13,28 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule putDOMComponentListener
* @providesModule ReactBrowserComponentMixin
*/
"use strict";
var ReactEventEmitter = require('ReactEventEmitter');
var ReactMount = require('ReactMount');
var listenTo = ReactEventEmitter.listenTo;
var invariant = require('invariant');
var ELEMENT_NODE_TYPE = 1;
function putDOMComponentListener(id, registrationName, listener) {
var container = ReactMount.findReactContainerForID(id);
if (container) {
var doc = container.nodeType === ELEMENT_NODE_TYPE ?
container.ownerDocument :
container;
listenTo(registrationName, doc);
var ReactBrowserComponentMixin = {
/**
* Returns the DOM node rendered by this component.
*
* @return {DOMElement} The root node of this component.
* @final
* @protected
*/
getDOMNode: function() {
invariant(
this.isMounted(),
'getDOMNode(): A component must be mounted to have a DOM node.'
);
return ReactMount.getNode(this._rootNodeID);
}
};
ReactEventEmitter.putListener(id, registrationName, listener);
}
module.exports = putDOMComponentListener;
module.exports = ReactBrowserComponentMixin;

View File

@@ -39,26 +39,6 @@ var DOC_NODE_TYPE = 9;
* the browser context.
*/
var ReactComponentBrowserEnvironment = {
/**
* Mixed into every component instance.
*/
Mixin: {
/**
* Returns the DOM node rendered by this component.
*
* @return {DOMElement} The root node of this component.
* @final
* @protected
*/
getDOMNode: function() {
invariant(
this.isMounted(),
'getDOMNode(): A component must be mounted to have a DOM node.'
);
return ReactMount.getNode(this._rootNodeID);
}
},
ReactReconcileTransaction: ReactReconcileTransaction,
BackendIDOperations: ReactDOMIDOperations,
@@ -133,21 +113,7 @@ var ReactComponentBrowserEnvironment = {
'See renderComponentToString() for server rendering.'
);
// Asynchronously inject markup by ensuring that the container is not in
// the document when settings its `innerHTML`.
var parent = container.parentNode;
if (parent) {
var next = container.nextSibling;
parent.removeChild(container);
container.innerHTML = markup;
if (next) {
parent.insertBefore(container, next);
} else {
parent.appendChild(container);
}
} else {
container.innerHTML = markup;
}
container.innerHTML = markup;
}
)
};

View File

@@ -22,6 +22,7 @@
var CSSPropertyOperations = require('CSSPropertyOperations');
var DOMProperty = require('DOMProperty');
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactComponent = require('ReactComponent');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactMount = require('ReactMount');
@@ -99,7 +100,7 @@ ReactDOMComponent.Mixin = {
*
* @internal
* @param {string} rootID The root DOM ID for this node.
* @param {ReactReconcileTransaction} transaction
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @param {number} mountDepth number of components in the owner hierarchy
* @return {string} The computed markup.
*/
@@ -131,7 +132,7 @@ ReactDOMComponent.Mixin = {
* @see http://jsperf.com/obj-vs-arr-iteration
*
* @private
* @param {ReactReconcileTransaction} transaction
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @return {string} Markup of opening tag.
*/
_createOpenTagMarkupAndPutListeners: function(transaction) {
@@ -163,15 +164,21 @@ ReactDOMComponent.Mixin = {
}
}
var idMarkup = DOMPropertyOperations.createMarkupForID(this._rootNodeID);
return ret + ' ' + idMarkup + '>';
// For static pages, no need to put React ID and checksum. Saves lots of
// bytes.
if (transaction.renderToStaticMarkup) {
return ret + '>';
}
var markupForID = DOMPropertyOperations.createMarkupForID(this._rootNodeID);
return ret + ' ' + markupForID + '>';
},
/**
* Creates markup for the content between the tags.
*
* @private
* @param {ReactReconcileTransaction} transaction
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @return {string} Content markup.
*/
_createContentMarkup: function(transaction) {
@@ -199,6 +206,15 @@ ReactDOMComponent.Mixin = {
},
receiveComponent: function(nextComponent, transaction) {
if (nextComponent === this) {
// Since props and context are immutable after the component is
// mounted, we can do a cheap identity compare here to determine
// if this is a superfluous reconcile.
// TODO: compare the descriptor
return;
}
assertValidProps(nextComponent.props);
ReactComponent.Mixin.receiveComponent.call(
this,
@@ -395,5 +411,6 @@ ReactDOMComponent.Mixin = {
mixInto(ReactDOMComponent, ReactComponent.Mixin);
mixInto(ReactDOMComponent, ReactDOMComponent.Mixin);
mixInto(ReactDOMComponent, ReactMultiChild.Mixin);
mixInto(ReactDOMComponent, ReactBrowserComponentMixin);
module.exports = ReactDOMComponent;

View File

@@ -30,6 +30,9 @@ var CompositionEventPlugin = require('CompositionEventPlugin');
var DefaultEventPluginOrder = require('DefaultEventPluginOrder');
var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
var MobileSafariClickEventPlugin = require('MobileSafariClickEventPlugin');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactComponentBrowserEnvironment =
require('ReactComponentBrowserEnvironment');
var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
var ReactDOM = require('ReactDOM');
var ReactDOMButton = require('ReactDOMButton');
@@ -89,6 +92,11 @@ function inject() {
body: createFullPageComponent(ReactDOM.body)
});
// This needs to happen after createFullPageComponent() otherwise the mixin
// gets double injected.
ReactInjection.CompositeComponent.injectMixin(ReactBrowserComponentMixin);
ReactInjection.DOMProperty.injectDOMPropertyConfig(DefaultDOMPropertyConfig);
ReactInjection.Updates.injectBatchingStrategy(
@@ -101,6 +109,8 @@ function inject() {
ServerReactRootIndex.createReactRootIndex
);
ReactInjection.Component.injectEnvironment(ReactComponentBrowserEnvironment);
if (__DEV__) {
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
if ((/[?&]react_perf\b/).test(url)) {

Some files were not shown because too many files have changed in this diff Show More