Compare commits
139 Commits
0.9-stable
...
v0.10.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dedf0c20da | ||
|
|
12bdb8d24c | ||
|
|
3b2d176f59 | ||
|
|
68da0eccbe | ||
|
|
593889b87a | ||
|
|
732f4717a6 | ||
|
|
7807413256 | ||
|
|
ec893833ee | ||
|
|
5aaf4dbae9 | ||
|
|
ecea2918cf | ||
|
|
7f24943e5a | ||
|
|
0491d30e7c | ||
|
|
55b0222596 | ||
|
|
b95fbbe4a2 | ||
|
|
e505e47e01 | ||
|
|
e0c487649d | ||
|
|
22057ef61c | ||
|
|
0278f01d95 | ||
|
|
0cec4af8d7 | ||
|
|
8d495f3b6e | ||
|
|
7987e6a51d | ||
|
|
d88d479685 | ||
|
|
3f3187c14f | ||
|
|
c1443e92e6 | ||
|
|
52e8f3fdb0 | ||
|
|
cdf4f07a15 | ||
|
|
6ff116e34b | ||
|
|
d889a01caf | ||
|
|
04f9887f0e | ||
|
|
9b427a322f | ||
|
|
7bbdcdba96 | ||
|
|
5106b793f7 | ||
|
|
83e4ef16e6 | ||
|
|
308c9a0752 | ||
|
|
6a01752f3d | ||
|
|
6fd53815cd | ||
|
|
3efa02da91 | ||
|
|
d9af091244 | ||
|
|
25773ed1b3 | ||
|
|
9e224e615f | ||
|
|
3ce2cd04e2 | ||
|
|
652f5aea28 | ||
|
|
280ff2e5a7 | ||
|
|
5ede7fb619 | ||
|
|
3ea3274ca4 | ||
|
|
2ca810fbf3 | ||
|
|
d9dd9d5cb3 | ||
|
|
9ffd70c688 | ||
|
|
3171436d97 | ||
|
|
9ce7ecc3d9 | ||
|
|
9f9c8bcebf | ||
|
|
ec54dcbd8f | ||
|
|
9766ed5797 | ||
|
|
1d209248ef | ||
|
|
21e06196cd | ||
|
|
4b56947560 | ||
|
|
554b677e60 | ||
|
|
aa70419f9d | ||
|
|
af1b63456e | ||
|
|
ac3051530a | ||
|
|
c5f56f318a | ||
|
|
be2c185888 | ||
|
|
7d0e6c6c0b | ||
|
|
13aa8d37e6 | ||
|
|
06f762da77 | ||
|
|
4ad320dd13 | ||
|
|
521201f121 | ||
|
|
41d30bb7de | ||
|
|
9c87aef67f | ||
|
|
ec8b0d7fbf | ||
|
|
e954a1c0d9 | ||
|
|
1d27770b40 | ||
|
|
4c4446d283 | ||
|
|
a8fc3b940d | ||
|
|
6666538316 | ||
|
|
620c1bc2ff | ||
|
|
99dab49f92 | ||
|
|
9b0534eb77 | ||
|
|
eee04b19e1 | ||
|
|
f734083a17 | ||
|
|
3fe9f9f336 | ||
|
|
e39c19423a | ||
|
|
6203e53d16 | ||
|
|
88a4a566ae | ||
|
|
2f6656e3e9 | ||
|
|
a0ecf47242 | ||
|
|
4b670a08fa | ||
|
|
6b78cfb0f4 | ||
|
|
8df5e55efd | ||
|
|
237adacc3a | ||
|
|
90e996324a | ||
|
|
b2649dd73b | ||
|
|
0217461d16 | ||
|
|
3c4f45fe45 | ||
|
|
6485e21956 | ||
|
|
ae72e6ef91 | ||
|
|
42444f6bb9 | ||
|
|
071d2a947c | ||
|
|
298a05517e | ||
|
|
cbfbb54c1d | ||
|
|
26179d2b79 | ||
|
|
ba78edbed8 | ||
|
|
34b6707132 | ||
|
|
7b773a6b3d | ||
|
|
1dfc5c79f9 | ||
|
|
04111d5228 | ||
|
|
a6c1b91c7d | ||
|
|
f34f0d2912 | ||
|
|
46cae63d2c | ||
|
|
5049fc6b05 | ||
|
|
8b1279e6b2 | ||
|
|
854d1f7c1b | ||
|
|
ab2d59f8b0 | ||
|
|
25cafec4a9 | ||
|
|
61c287c5ea | ||
|
|
7bba8c3257 | ||
|
|
2f0507f730 | ||
|
|
989f6f987d | ||
|
|
e2b006f9ae | ||
|
|
141ff66986 | ||
|
|
da0b34e945 | ||
|
|
cbce621570 | ||
|
|
472be09ff6 | ||
|
|
7144a9f241 | ||
|
|
8aaf5fdbf4 | ||
|
|
445611f3e6 | ||
|
|
b79a3cbd21 | ||
|
|
0790040aac | ||
|
|
2900997b5f | ||
|
|
7eb33ef176 | ||
|
|
5545887a48 | ||
|
|
e29584b49b | ||
|
|
c43b3f406c | ||
|
|
a4d6796705 | ||
|
|
89d4d352e1 | ||
|
|
f3c1383af9 | ||
|
|
c32e398a5c | ||
|
|
95edc396df | ||
|
|
1de77f1fbe |
4
.mailmap
4
.mailmap
@@ -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>
|
||||
@@ -29,6 +32,7 @@ Pete Hunt <floydophone@gmail.com>
|
||||
Pete Hunt <floydophone@gmail.com> <pete.hunt@fb.com>
|
||||
Pete Hunt <floydophone@gmail.com> <pete@instagram.com>
|
||||
Pete Hunt <floydophone@gmail.com> <phunt@instagram.com>
|
||||
Petri Lievonen <plievone@cc.hut.fi>
|
||||
Pieter Vanderwerff <me@pieter.io> <pieter@heyday.co.nz>
|
||||
Richard Feldman <richard.t.feldman@gmail.com> <richard@noredink.com>
|
||||
Richard Livesey <Livesey7@hotmail.co.uk>
|
||||
|
||||
15
AUTHORS
15
AUTHORS
@@ -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>
|
||||
@@ -88,6 +98,7 @@ Paul Seiffert <paul.seiffert@gmail.com>
|
||||
Paul Shen <paul@mnml0.com>
|
||||
Pete Hunt <floydophone@gmail.com>
|
||||
Peter Cottle <pcottle@fb.com>
|
||||
Petri Lievonen <plievone@cc.hut.fi>
|
||||
Pieter Vanderwerff <me@pieter.io>
|
||||
Richard D. Worth <rdworth@gmail.com>
|
||||
Richard Feldman <richard.t.feldman@gmail.com>
|
||||
@@ -95,14 +106,16 @@ Richard Livesey <Livesey7@hotmail.co.uk>
|
||||
Sander Spies <sandermail@gmail.com>
|
||||
Sean Kinsey <oyvind@fb.com>
|
||||
Sebastian Markbåge <sebastian@calyptus.eu>
|
||||
Shaun Trennery <shaun.trennery@gmail.com>
|
||||
Simon Højberg <r.hackr@gmail.com>
|
||||
Stoyan Stefanov <ssttoo@ymail.com>
|
||||
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>
|
||||
plievone <plievone@cc.hut.fi>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- On mouse wheel events, `deltaY` is no longer negated
|
||||
- When prop types validation fails, a warning is logged instead of an error thrown (with the production build of React, type checks are now skipped for performance)
|
||||
- On `input`, `select`, and `textarea` elements, `.getValue()` is no longer supported; use `.getDOMNode().value` instead
|
||||
- `this.context` on components is now reserved for internal use by React
|
||||
|
||||
#### New Features
|
||||
|
||||
|
||||
@@ -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']);
|
||||
|
||||
|
||||
@@ -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.9.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.9.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.9.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
|
||||
|
||||
@@ -13,7 +13,7 @@ redcarpet:
|
||||
pygments: true
|
||||
name: React
|
||||
markdown: redcarpet
|
||||
react_version: 0.9.0-rc1
|
||||
react_version: 0.10.0-alpha
|
||||
description: A JavaScript library for building user interfaces
|
||||
relative_permalinks: true
|
||||
paginate: 5
|
||||
|
||||
@@ -749,3 +749,19 @@ p code {
|
||||
div[data-twttr-id] iframe {
|
||||
margin: 10px auto !important;
|
||||
}
|
||||
|
||||
/* Acknowledgements */
|
||||
|
||||
.three-column {
|
||||
@include clearfix;
|
||||
}
|
||||
|
||||
.three-column > ul {
|
||||
float: left;
|
||||
margin-left: 30px;
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
.three-column > ul:first-child {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
title: Getting Started
|
||||
- id: tutorial
|
||||
title: Tutorial
|
||||
- id: thinking-in-react
|
||||
title: Thinking in React
|
||||
- title: Community Resources
|
||||
items:
|
||||
- id: videos
|
||||
title: Videos
|
||||
- id: complementary-tools
|
||||
title: Complementary Tools
|
||||
- id: example-apps
|
||||
title: Example Apps
|
||||
- id: examples
|
||||
title: Examples
|
||||
- title: Guides
|
||||
items:
|
||||
- id: why-react
|
||||
@@ -49,8 +53,8 @@
|
||||
title: Test Utilities
|
||||
- id: clone-with-props
|
||||
title: Cloning Components
|
||||
- id: examples
|
||||
title: Examples
|
||||
- id: update
|
||||
title: Immutability Helpers
|
||||
- title: Reference
|
||||
items:
|
||||
- id: top-level-api
|
||||
|
||||
@@ -74,7 +74,10 @@
|
||||
{{ content }}
|
||||
|
||||
<footer class="wrap">
|
||||
<div class="left">A Facebook & Instagram collaboration.</div>
|
||||
<div class="left">
|
||||
A Facebook & Instagram collaboration.<br>
|
||||
<a href="/react/acknowledgements.html">Acknowledgements</a>
|
||||
</div>
|
||||
<div class="right">© 2014 Facebook Inc.</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -68,6 +68,7 @@ In cases where you want to preserve the space adjacent to a newline, you can wri
|
||||
- On mouse wheel events, `deltaY` is no longer negated
|
||||
- When prop types validation fails, a warning is logged instead of an error thrown (with the production build of React, the type checks are now skipped for performance)
|
||||
- On `input`, `select`, and `textarea` elements, `.getValue()` is no longer supported; use `.getDOMNode().value` instead
|
||||
- `this.context` on components is now reserved for internal use by React
|
||||
|
||||
#### New Features
|
||||
|
||||
@@ -86,7 +87,7 @@ In cases where you want to preserve the space adjacent to a newline, you can wri
|
||||
- On all events, `.currentTarget` is now properly set
|
||||
- On keyboard events, `.key` is now polyfilled in all browsers for special (non-printable) keys
|
||||
- On clipboard events, `.clipboardData` is now polyfilled in IE
|
||||
- On drag events, `.dragTransfer` is now present
|
||||
- On drag events, `.dataTransfer` is now present
|
||||
- Added support for `onMouseOver` and `onMouseOut` in addition to the existing `onMouseEnter` and `onMouseLeave` events
|
||||
- Added support for `onLoad` and `onError` on `<img>` elements
|
||||
- Added support for `onReset` on `<form>` elements
|
||||
|
||||
@@ -76,6 +76,7 @@ In cases where you want to preserve the space adjacent to a newline, you can wri
|
||||
- On mouse wheel events, `deltaY` is no longer negated
|
||||
- When prop types validation fails, a warning is logged instead of an error thrown (with the production build of React, type checks are now skipped for performance)
|
||||
- On `input`, `select`, and `textarea` elements, `.getValue()` is no longer supported; use `.getDOMNode().value` instead
|
||||
- `this.context` on components is now reserved for internal use by React
|
||||
|
||||
#### New Features
|
||||
|
||||
@@ -96,7 +97,7 @@ In cases where you want to preserve the space adjacent to a newline, you can wri
|
||||
- On all events, `.currentTarget` is now properly set
|
||||
- On keyboard events, `.key` is now polyfilled in all browsers for special (non-printable) keys
|
||||
- On clipboard events, `.clipboardData` is now polyfilled in IE
|
||||
- On drag events, `.dragTransfer` is now present
|
||||
- On drag events, `.dataTransfer` is now present
|
||||
- Added support for `onMouseOver` and `onMouseOut` in addition to the existing `onMouseEnter` and `onMouseLeave` events
|
||||
- Added support for `onLoad` and `onError` on `<img>` elements
|
||||
- Added support for `onReset` on `<form>` elements
|
||||
|
||||
95
docs/_posts/2014-02-24-community-roundup-17.md
Normal file
95
docs/_posts/2014-02-24-community-roundup-17.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: "Community Round-up #17"
|
||||
layout: post
|
||||
author: Jonas Gebhardt
|
||||
---
|
||||
|
||||
|
||||
It's exciting to see the number of real-world React applications and components skyrocket over the past months! This community round-up features a few examples of inspiring React applications and components.
|
||||
|
||||
## React in the Real World
|
||||
|
||||
### Facebook Lookback video editor
|
||||
Large parts of Facebook's web frontend are already powered by React. The recently released Facebook [Lookback video and its corresponding editor](https://www.facebook.com/lookback/edit/) are great examples of a complex, real-world React app.
|
||||
|
||||
### Russia's largest bank is now powered by React
|
||||
Sberbank, Russia's largest bank, recently switched large parts of their site to use React, as detailed in [this post by Vyacheslav Slinko](https://groups.google.com/forum/#!topic/reactjs/Kj6WATX0atg).
|
||||
|
||||
### Relato
|
||||
[Relato](http://bripkens.github.io/relato/) by [Ben Ripkens](https://github.com/bripkens) shows Open Source Statistics based on npm data. It features a filterable and sortable table built in React. Check it out – it's super fast!
|
||||
|
||||
### Makona Editor
|
||||
|
||||
John Lynch ([@johnrlynch](https://twitter.com/johnrlynch)) created Makona, a block-style document editor for the web. Blocks of different content types comprise documents, authored using plain markup. At the switch of a toggle, block contents are then rendered on the page. While not quite a WYSIWYG editor, Makona uses plain textareas for input. This makes it compatible with a wider range of platforms than traditional rich text editors.
|
||||
<figure>[](http://johnthethird.github.io/makona-editor/)</figure>
|
||||
|
||||
### Create Chrome extensions using React
|
||||
React is in no way limited to just web pages. Brandon Tilley ([@BinaryMuse](https://twitter.com/BinaryMuse)) just released a detailed walk-through of [how he built his Chrome extension "Fast Tab Switcher" using React](http://brandontilley.com/2014/02/24/creating-chrome-extensions-with-react.html).
|
||||
|
||||
|
||||
### Twitter Streaming Client
|
||||
|
||||
Javier Aguirre ([@javaguirre](https://twitter.com/javaguirre)) put together a simple [twitter streaming client using node, socket.io and React](http://javaguirre.net/2014/02/11/twitter-streaming-api-with-node-socket-io-and-reactjs/).
|
||||
|
||||
|
||||
### Sproutsheet
|
||||
|
||||
[Sproutsheet](http://sproutsheet.com/) is a gardening calendar. You can use it to track certain events that happen in the life of your plants. It's currently in beta and supports localStorage, and data/image import and export.
|
||||
|
||||
### Instant Domain Search
|
||||
[Instant Domain Search](https://instantdomainsearch.com/) also uses React. It sure is instant!
|
||||
|
||||
|
||||
### SVG-based graphical node editor
|
||||
[NoFlo](http://noflojs.org/) and [Meemoo](http://meemoo.org/) developer [Forresto Oliphant](http://www.forresto.com/) built an awesome SVG-based [node editor](http://forresto.github.io/prototyping/react/) in React.
|
||||
<figure>[](http://forresto.github.io/prototyping/react/)</figure>
|
||||
|
||||
|
||||
### Ultimate Tic-Tac-Toe Game in React
|
||||
Rafał Cieślak ([@Ravicious](https://twitter.com/Ravicious)) wrote a [React version](http://ravicious.github.io/ultimate-ttt/) of [Ultimate Tic Tac Toe](http://mathwithbaddrawings.com/2013/06/16/ultimate-tic-tac-toe/). Find the source [here](https://github.com/ravicious/ultimate-ttt).
|
||||
|
||||
|
||||
|
||||
### ReactJS Gallery
|
||||
|
||||
[Emanuele Rampichini](https://github.com/lele85)'s [ReactJS Gallery](https://github.com/lele85/ReactGallery) is a cool demo app that shows fullscreen images from a folder on the server. If the folder content changes, the gallery app updates via websockets.
|
||||
|
||||
Emanuele shared this awesome demo video with us:
|
||||
|
||||
<iframe width="560" height="315" src="//www.youtube.com/embed/jYcpaemt90M" frameborder="0" allowfullscreen></iframe>
|
||||
|
||||
|
||||
|
||||
## React Components
|
||||
|
||||
|
||||
### Table Sorter
|
||||
[Table Sorter](http://bgerm.github.io/react-table-sorter-demo/) by [bgerm](https://github.com/bgerm) [[source](https://github.com/bgerm/react-table-sorter-demo)] is another helpful React component.
|
||||
|
||||
### Static-search
|
||||
|
||||
Dmitry Chestnykh [@dchest](https://twitter.com/dchest) wrote a [static search indexer](https://github.com/dchest/static-search) in Go, along with a [React-based web front-end](http://www.codingrobots.com/search/) that consumes search result via JSON.
|
||||
|
||||
### Lorem Ipsum component
|
||||
|
||||
[Martin Andert](https://github.com/martinandert) created [react-lorem-component](https://github.com/martinandert/react-lorem-component), a simple component for all your placeholding needs.
|
||||
|
||||
### Input with placeholder shim
|
||||
[react-input=placeholder](https://github.com/enigma-io/react-input-placeholder) by [enigma-io](https://github.com/enigma-io) is a small wrapper around React.DOM.input that shims in placeholder functionality for browsers that don't natively support it.
|
||||
|
||||
### diContainer
|
||||
|
||||
[dicontainer](https://github.com/SpektrumFM/dicontainer) provides a dependency container that lets you inject Angular-style providers and services as simple React.js Mixins.
|
||||
|
||||
|
||||
## React server rendering
|
||||
|
||||
Ever wonder how to pre-render React components on the server? [react-server-example](https://github.com/mhart/react-server-example) by Michael Hart ([@hichaelmart](http://twitter.com/hichaelmart)) walks through the necessary steps.
|
||||
|
||||
Similarly, Alan deLevie ([@adelevie](https://twitter.com/adelevie)) created [react-client-server-starter](https://github.com/adelevie/react-client-server-starter), another detailed walk-through of how to server-render your app.
|
||||
|
||||
|
||||
|
||||
## Random Tweet
|
||||
|
||||
<div><blockquote class="twitter-tweet" lang="en"><p>Recent changes: web ui is being upgraded to [#reactjs](https://twitter.com/search?q=%23reactjs&src=hash), HEAD~4 at [https://camlistore.googlesource.com/camlistore/](https://camlistore.googlesource.com/camlistore/)</p>— Camlistore (@Camlistore) <a href="https://twitter.com/Camlistore/status/423925795820539904">January 16, 2014</a></blockquote></div>
|
||||
106
docs/_posts/2014-03-14-community-roundup-18.md
Normal file
106
docs/_posts/2014-03-14-community-roundup-18.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: "Community Round-up #18"
|
||||
layout: post
|
||||
author: Jonas Gebhardt
|
||||
---
|
||||
|
||||
In this Round-up, we are taking a few closer looks at React's interplay with different frameworks and architectures.
|
||||
|
||||
## "Little framework BIG splash"
|
||||
|
||||
Let's start with yet another refreshing introduction to React: Craig Savolainen ([@maedhr](https://twitter.com/maedhr)) walks through some first steps, demonstrating [how to build a Google Maps component](http://infinitemonkeys.influitive.com/little-framework-big-splash) using React.
|
||||
|
||||
## Architecting your app with react
|
||||
|
||||
Brandon Konkle ([@bkonkle](https://twitter.com/bkonkle))
|
||||
[Architecting your app with react](http://lincolnloop.com/blog/architecting-your-app-react-part-1/)
|
||||
We're looking forward to part 2!
|
||||
|
||||
> React is not a full MVC framework, and this is actually one of its strengths. Many who adopt React choose to do so alongside their favorite MVC framework, like Backbone. React has no opinions about routing or syncing data, so you can easily use your favorite tools to handle those aspects of your frontend application. You'll often see React used to manage specific parts of an application's UI and not others. React really shines, however, when you fully embrace its strategies and make it the core of your application's interface.
|
||||
>
|
||||
> [Read the full article...](http://lincolnloop.com/blog/architecting-your-app-react-part-1/)
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
|
||||
## React, Scala and the Play Framework
|
||||
[Matthias Nehlsen](http://matthiasnehlsen.com/) wrote a detailed introductory piece on [React and the Play Framework](http://matthiasnehlsen.com/blog/2014/01/05/play-framework-and-facebooks-react-library/), including a helpful architectural diagram of a typical React app.
|
||||
|
||||
Nehlsen's React frontend is the second implementation of his chat application's frontend, following an AngularJS version. Both implementations are functionally equivalent and offer some perspective on differences between the two frameworks.
|
||||
|
||||
In [another article](http://matthiasnehlsen.com/blog/2014/01/24/scala-dot-js-and-reactjs/), he walks us through the process of using React with scala.js to implement app-wide undo functionality.
|
||||
|
||||
Also check out his [talk](http://m.ustream.tv/recorded/42780242) at Ping Conference 2014, in which he walks through a lot of the previously content in great detail.
|
||||
|
||||
## React and Backbone
|
||||
|
||||
The folks over at [Venmo](https://venmo.com/) are using React in conjunction with Backbone.
|
||||
Thomas Boyt ([@thomasaboyt](https://twitter.com/thomasaboyt)) wrote [this detailed piece](http://www.thomasboyt.com/2013/12/17/using-reactjs-as-a-backbone-view.html) about why React and Backbone are "a fantastic pairing".
|
||||
|
||||
## React vs. Ember
|
||||
|
||||
Eric Berry ([@coderberry](https://twitter.com/coderberry)) developed Ember equivalents for some of the official React examples. Read his post for a side-by-side comparison of the respective implementations: ["Facebook React vs. Ember"](http://instructure.github.io/blog/2013/12/17/facebook-react-vs-ember/).
|
||||
|
||||
|
||||
## React and plain old HTML
|
||||
|
||||
Daniel Lo Nigro ([@Daniel15](https://twitter.com/Daniel15)) created [React-Magic](https://github.com/reactjs/react-magic), which leverages React to ajaxify plain old html pages and even [allows CSS transitions between pageloads](http://stuff.dan.cx/facebook/react-hacks/magic/red.php).
|
||||
|
||||
> React-Magic intercepts all navigation (link clicks and form posts) and loads the requested page via an AJAX request. React is then used to "diff" the old HTML with the new HTML, and only update the parts of the DOM that have been changed.
|
||||
>
|
||||
> [Check out the project on GitHub...](https://github.com/reactjs/react-magic)
|
||||
|
||||
On a related note, [Reactize](https://turbo-react.herokuapp.com/) by Ross Allen ([@ssorallen](https://twitter.com/ssorallen)) is a similarly awesome project: A wrapper for Rails' [Turbolinks](https://github.com/rails/turbolinks/), which seems to have inspired John Lynch ([@johnrlynch](https://twitter.com/johnrlynch)) to then create [a server-rendered version using the JSX transformer in Rails middleware](http://www.rigelgroupllc.com/blog/2014/01/12/react-jsx-transformer-in-rails-middleware/).
|
||||
|
||||
## React and Object.observe
|
||||
Check out [François de Campredon](https://github.com/fdecampredon)'s implementation of [TodoMVC based on React and ES6's Object.observe](https://github.com/fdecampredon/react-observe-todomvc/).
|
||||
|
||||
|
||||
## React and Angular
|
||||
|
||||
Ian Bicking ([@ianbicking](https://twitter.com/ianbicking)) of Mozilla Labs [explains why he "decided to go with React instead of Angular.js"](https://plus.google.com/+IanBicking/posts/Qj8R5SWAsfE).
|
||||
|
||||
### ng-React Update
|
||||
|
||||
[David Chang](https://github.com/davidchang) works through some performance improvements of his [ngReact](https://github.com/davidchang/ngReact) project. His post ["ng-React Update - React 0.9 and Angular Track By"](http://davidandsuzi.com/ngreact-update/) includes some helpful advice on boosting render performance for Angular components.
|
||||
|
||||
> Angular gives you a ton of functionality out of the box - a full MV* framework - and I am a big fan, but I'll admit that you need to know how to twist the right knobs to get performance.
|
||||
>
|
||||
> That said, React gives you a very strong view component out of the box with the performance baked right in. Try as I did, I couldn't actually get it any faster. So pretty impressive stuff.
|
||||
>
|
||||
>[Read the full post...](http://davidandsuzi.com/ngreact-update/)
|
||||
|
||||
|
||||
React was also recently mentioned at ng-conf, where the Angular team commented on React's concept of the virtual DOM:
|
||||
|
||||
<iframe width="560" height="315" src="//www.youtube.com/embed/srt3OBP2kGc?start=113" frameborder="0" allowfullscreen></iframe>
|
||||
|
||||
## React and Web Components
|
||||
|
||||
Jonathan Krause ([@jonykrause](https://twitter.com/jonykrause)) offers his thoughts regarding [parallels between React and Web Components](http://jonykrau.se/posts/the-value-of-react), highlighting the value of React's ability to render pages on the server practically for free.
|
||||
|
||||
## Immutable React
|
||||
|
||||
[Peter Hausel](http://pk11.kinja.com/) shows how to build a Wikipedia auto-complete demo based on immutable data structures (similar to [mori](https://npmjs.org/package/mori)), really taking advantage of the framework's one-way reactive data binding:
|
||||
|
||||
> Its truly reactive design makes DOM updates finally sane and when combined with persistent data structures one can experience JavaScript development like it was never done before.
|
||||
|
||||
> [Read the full post](http://tech.kinja.com/immutable-react-1495205675)
|
||||
|
||||
|
||||
## D3 and React
|
||||
|
||||
[Ben Smith](http://10consulting.com/) built some great SVG-based charting components using a little less of D3 and a little more of React: [D3 and React - the future of charting components?](http://10consulting.com/2014/02/19/d3-plus-reactjs-for-charting/)
|
||||
|
||||
## Om and React
|
||||
Josh Haberman ([@joshhaberman](https://twitter.com/JoshHaberman)) discusses performance differences between React, Om and traditional MVC frameworks in "[A closer look at OM vs React performance](http://blog.reverberate.org/2014/02/on-future-of-javascript-mvc-frameworks.html)".
|
||||
|
||||
Speaking of Om: [Omchaya](https://github.com/sgrove/omchaya) by Sean Grove ([@sgrove](https://twitter.com/sgrove)) is a neat Cljs/Om example project.
|
||||
|
||||
|
||||
## Random Tweets
|
||||
|
||||
<div><blockquote class="twitter-tweet" lang="en"><p>Worked for 2 hours on a [@react_js](https://twitter.com/react_js) app sans internet. Love that I could get stuff done with it without googling every question.</p>— John Shimek (@varikin) <a href="https://twitter.com/varikin/status/436606891657949185">February 20, 2014</a></blockquote></div>
|
||||
71
docs/_posts/2014-03-19-react-v0.10-rc1.md
Normal file
71
docs/_posts/2014-03-19-react-v0.10-rc1.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: React v0.10 RC
|
||||
layout: post
|
||||
author: Paul O’Shannessy
|
||||
---
|
||||
|
||||
[v0.9 has only been out for a month](http://facebook.github.io/react/blog/2014/02/20/react-v0.9.html), but we’re getting ready to push out v0.10 already. Unlike v0.9 which took a long time, we don't have a long list of changes to talk about.
|
||||
|
||||
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**
|
||||
<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.
|
||||
|
||||
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, we’re 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 you’re making to a mounted component. We’ll 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
|
||||
* Made it possible to server render without React-related markup (`data-reactid`, `data-react-checksum`) - `React.renderComponentToStaticMarkup`
|
||||
* Added support for more attributes:
|
||||
* `srcSet` for `<img>` to images at different pixel ratios
|
||||
* `textAnchor` for SVG
|
||||
|
||||
#### Bug Fixes
|
||||
* Ensure all void elements don’t 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.
|
||||
|
||||
### 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).
|
||||
127
docs/acknowledgements.md
Normal file
127
docs/acknowledgements.md
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
id: acknowledgements
|
||||
title: Acknowledgements
|
||||
layout: single
|
||||
---
|
||||
|
||||
We'd like to thank all of our contributors:
|
||||
|
||||
<div class="three-column">
|
||||
<ul>
|
||||
<li>Alan deLevie</li>
|
||||
<li>Alex Zelenskiy</li>
|
||||
<li>Alexander Solovyov</li>
|
||||
<li>Andreas Svensson</li>
|
||||
<li>Andrew Davey</li>
|
||||
<li>Andrew Zich</li>
|
||||
<li>Andrey Popp</li>
|
||||
<li>Ayman Osman</li>
|
||||
<li>Ben Alpert</li>
|
||||
<li>Ben Newman</li>
|
||||
<li>Ben Ripkens</li>
|
||||
<li>Bob Eagan</li>
|
||||
<li>Brian Cooke</li>
|
||||
<li>Brian Kim</li>
|
||||
<li>Brian Rue</li>
|
||||
<li>Cam Spiers</li>
|
||||
<li>Cat Chen</li>
|
||||
<li>Cheng Lou</li>
|
||||
<li>Christian Roman</li>
|
||||
<li>Christoph Pojer</li>
|
||||
<li>Clay Allsopp</li>
|
||||
<li>Connor McSheffrey</li>
|
||||
<li>Dan Schafer</li>
|
||||
<li>Daniel Gasienica</li>
|
||||
<li>Daniel Lo Nigro</li>
|
||||
<li>Daniel Miladinov</li>
|
||||
<li>Danny Ben-David</li>
|
||||
<li>David Hellsing</li>
|
||||
<li>David Hu</li>
|
||||
<li>Dustin Getz</li>
|
||||
<li>Eric Clemmons</li>
|
||||
<li>Eric Schoffstall</li>
|
||||
<li>Fabio M. Costa</li>
|
||||
<li>Felipe Oliveira Carvalho</li>
|
||||
<li>Felix Kling</li>
|
||||
<li>Fernando Correia</li>
|
||||
<li>Greg Roodt</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>Guido Bouman</li>
|
||||
<li>Harry Hull</li>
|
||||
<li>Hugo Jobling</li>
|
||||
<li>Ian Obermiller</li>
|
||||
<li>Ingvar Stepanyan</li>
|
||||
<li>Isaac Salier-Hellendag</li>
|
||||
<li>Ivan Kozik</li>
|
||||
<li>Jakub Malinowski</li>
|
||||
<li>James Ide</li>
|
||||
<li>Jamie Wong</li>
|
||||
<li>Jamison Dance</li>
|
||||
<li>Jan Kassens</li>
|
||||
<li>Jared Forsyth</li>
|
||||
<li>Jason Bonta</li>
|
||||
<li>Jason Trill</li>
|
||||
<li>Jean Lauliac</li>
|
||||
<li>Jeff Morrison</li>
|
||||
<li>Jeffrey Lin</li>
|
||||
<li>Jignesh Kakadiya</li>
|
||||
<li>Johannes Baiter</li>
|
||||
<li>John Watson</li>
|
||||
<li>Jonas Gebhardt</li>
|
||||
<li>Jonathan Hsu</li>
|
||||
<li>Jordan Walke</li>
|
||||
<li>Josh Duck</li>
|
||||
<li>Jun Wu</li>
|
||||
<li>Keito Uchiyama</li>
|
||||
<li>Kit Randel</li>
|
||||
<li>Kunal Mehta</li>
|
||||
<li>Laurence Rowe</li>
|
||||
<li>Levi McCallum</li>
|
||||
<li>Logan Allen</li>
|
||||
<li>Luigy Leon</li>
|
||||
<li>Mark Richardson</li>
|
||||
<li>Marshall Roch</li>
|
||||
<li>Martin Andert</li>
|
||||
<li>Martin Konicek</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>Mathieu M-Gosselin</li>
|
||||
<li>Matt Harrison</li>
|
||||
<li>Matti Nelimarkka</li>
|
||||
<li>Michal Srb</li>
|
||||
<li>Mouad Debbar</li>
|
||||
<li>Nadeesha Cabral</li>
|
||||
<li>Nicholas Bergson-Shilcock</li>
|
||||
<li>Nick Gavalas</li>
|
||||
<li>Nick Thompson</li>
|
||||
<li>Owen Coutts</li>
|
||||
<li>Pascal Hartig</li>
|
||||
<li>Paul O’Shannessy</li>
|
||||
<li>Paul Seiffert</li>
|
||||
<li>Paul Shen</li>
|
||||
<li>Pete Hunt</li>
|
||||
<li>Peter Cottle</li>
|
||||
<li>Petri Lievonen</li>
|
||||
<li>Pieter Vanderwerff</li>
|
||||
<li>Richard D. Worth</li>
|
||||
<li>Richard Feldman</li>
|
||||
<li>Richard Livesey</li>
|
||||
<li>Sander Spies</li>
|
||||
<li>Sean Kinsey</li>
|
||||
<li>Sebastian Markbåge</li>
|
||||
<li>Shaun Trennery</li>
|
||||
<li>Simon Højberg</li>
|
||||
<li>Stoyan Stefanov</li>
|
||||
<li>Sundeep Malladi</li>
|
||||
<li>Thomas Aylott</li>
|
||||
<li>Timothy Yung</li>
|
||||
<li>Tom Occhino</li>
|
||||
<li>Vjeux</li>
|
||||
<li>Wincent Colaiuta</li>
|
||||
<li>Zach Bruggeman</li>
|
||||
<li>imagentleman</li>
|
||||
</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.
|
||||
@@ -33,6 +33,10 @@ React.createClass({
|
||||
// A React component.
|
||||
optionalComponent: React.PropTypes.component,
|
||||
|
||||
// You can also declare that a prop is an instance of a class. This uses
|
||||
// JS's instanceof operator.
|
||||
optionalMessage: React.PropTypes.instanceOf(Message),
|
||||
|
||||
// You can ensure that your prop is limited to specific values by treating
|
||||
// it as an enum.
|
||||
optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
|
||||
@@ -40,7 +44,8 @@ React.createClass({
|
||||
// An object that could be one of many types
|
||||
optionalUnion: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.number
|
||||
React.PropTypes.number,
|
||||
React.PropTypes.instanceOf(Message)
|
||||
]),
|
||||
|
||||
// An array of a certain type
|
||||
@@ -52,10 +57,6 @@ React.createClass({
|
||||
fontSize: React.PropTypes.number
|
||||
}),
|
||||
|
||||
// You can also declare that a prop is an instance of a class. This uses
|
||||
// JS's instanceof operator.
|
||||
someClass: React.PropTypes.instanceOf(SomeClass),
|
||||
|
||||
// You can chain any of the above with isRequired to make sure a warning is
|
||||
// shown if the prop isn't provided.
|
||||
requiredFunc: React.PropTypes.func.isRequired,
|
||||
|
||||
@@ -126,15 +126,18 @@ 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).
|
||||
|
||||
* `console.*`
|
||||
|
||||
When using HTML5 elements in IE8 including `<section>`, `<article>`, `<nav>`, `<header>`, and `<footer>`, it's also necessary to include [html5shiv](https://github.com/aFarkas/html5shiv) or a similar script.
|
||||
|
||||
|
||||
### Cross-browser Issues
|
||||
|
||||
Although React is pretty good at abstracting browser differences, some browsers are limited or present quirky behaviors that we couldn't find a workaround.
|
||||
Although React is pretty good at abstracting browser differences, some browsers are limited or present quirky behaviors that we couldn't find a workaround for.
|
||||
|
||||
|
||||
#### onScroll event on IE8
|
||||
|
||||
@@ -38,14 +38,7 @@ 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.
|
||||
|
||||
* [reactify](https://github.com/andreypopp/reactify) - use JSX with [browserify](http://browserify.org/)
|
||||
* [grunt-react](https://github.com/ericclemmons/grunt-react) - [grunt](http://gruntjs.com/) task for JSX
|
||||
* [gulp-react](https://github.com/sindresorhus/gulp-react) - [gulp](http://gulpjs.com/) task for JSX
|
||||
* [jsx-requirejs-plugin](https://github.com/philix/jsx-requirejs-plugin) - use JSX with [require.js](http://requirejs.org/) and precompile JSX files with r.js
|
||||
* [pyReact](https://github.com/facebook/react-python) - use JSX with [Python](http://www.python.org/)
|
||||
* [react-rails](https://github.com/facebook/react-rails) - use JSX with [Ruby on Rails](http://rubyonrails.org/)
|
||||
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
|
||||
@@ -53,5 +46,10 @@ The open-source community has built tools that integrate JSX with several build
|
||||
* 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.
|
||||
|
||||
### Debugging
|
||||
|
||||
[React Developer Tools](https://github.com/facebook/react-devtools) is a [Chrome extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) that allows you to inspect the React component hierarchy in the Chrome Developer Tools.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -20,7 +20,7 @@ React provides a `ReactTransitionGroup` addon component as a low-level API for a
|
||||
```javascript{22-24}
|
||||
/** @jsx React.DOM */
|
||||
|
||||
var ReactCSSTransitionGroup = React.addons.TransitionGroup;
|
||||
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
|
||||
|
||||
var TodoList = React.createClass({
|
||||
getInitialState: function() {
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Cloning Components
|
||||
layout: docs
|
||||
permalink: clone-with-props.html
|
||||
prev: test-utils.html
|
||||
next: examples.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
62
docs/docs/09.6-update.md
Normal 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
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
id: examples
|
||||
title: Examples
|
||||
layout: docs
|
||||
permalink: examples.html
|
||||
prev: class-name-manipulation.html
|
||||
---
|
||||
|
||||
### Production Apps
|
||||
|
||||
* All of [Instagram.com](http://instagram.com/) is built on React.
|
||||
* Many components on [Facebook.com](http://www.facebook.com/), including the commenting interface, ads creation flows, and page insights.
|
||||
* [Khan Academy](http://khanacademy.org/) is using React for most new JS development.
|
||||
|
||||
|
||||
### Sample Code
|
||||
|
||||
* We've included [a step-by-step comment box tutorial](/react/docs/tutorial.html).
|
||||
* [The React starter kit](/react/downloads.html) includes several examples which you can [view online in our GitHub repository](https://github.com/facebook/react/tree/master/examples/).
|
||||
* [React one-hour email](https://github.com/petehunt/react-one-hour-email/commits/master) goes step-by-step from a static HTML mock to an interactive email reader (written in just one hour!)
|
||||
* [Rendr + React app template](https://github.com/petehunt/rendr-react-template/) demonstrates how to use React's server rendering capabilities.
|
||||
@@ -4,7 +4,7 @@ title: Complementary Tools
|
||||
layout: docs
|
||||
permalink: complementary-tools.html
|
||||
prev: videos.html
|
||||
next: example-apps.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.
|
||||
@@ -13,45 +13,48 @@ If you want your project on this list, or think one of these projects should be
|
||||
|
||||
### 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
|
||||
* **[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
|
||||
* **[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](https://github.com/andreypopp/react-router-component)**
|
||||
* **[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
|
||||
* **[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](https://github.com/andreypopp/react-async)** Adds a `getInitialStateAsync(cb)` method suitable for data fetching on both the client and the server.
|
||||
* **[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-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.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
id: example-apps
|
||||
title: Example apps
|
||||
layout: docs
|
||||
permalink: example-apps.html
|
||||
prev: complementary-tools.html
|
||||
---
|
||||
|
||||
Here is a selection of open-source apps built with React.
|
||||
|
||||
* **[TodoMVC](https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/react/js)**
|
||||
* **[Khan Academy question editor](https://github.com/khan/perseus)** (browse their GitHub account for many more production apps!)
|
||||
* **[github-issues-viewer](https://github.com/jaredly/github-issues-viewer)**
|
||||
* **[hn-react](https://github.com/prabirshrestha/hn-react)** Dead-simple Hacker News client in React
|
||||
28
docs/docs/examples.md
Normal file
28
docs/docs/examples.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
id: examples
|
||||
title: Examples
|
||||
layout: docs
|
||||
permalink: examples.html
|
||||
prev: complementary-tools.html
|
||||
---
|
||||
|
||||
### Production Apps
|
||||
|
||||
* **[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.
|
||||
* **[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.
|
||||
|
||||
### Sample Code
|
||||
|
||||
* **[React starter kit](/react/downloads.html)** Includes several examples which you can [view online in our GitHub repository](https://github.com/facebook/react/tree/master/examples/).
|
||||
* **[React one-hour email](https://github.com/petehunt/react-one-hour-email/commits/master)** Goes step-by-step from a static HTML mock to an interactive email reader, written in just one hour!
|
||||
* **[React server rendering example](https://github.com/mhart/react-server-example)** Demonstrates how to use React's server rendering capabilities.
|
||||
|
||||
### Open-Source Demos
|
||||
|
||||
* **[TodoMVC](https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/react/js)**
|
||||
* **[Khan Academy question editor](https://github.com/khan/perseus)** (Browse their GitHub account for many more production apps!)
|
||||
* **[github-issue-viewer](https://github.com/jaredly/github-issues-viewer)**
|
||||
* **[hn-react](https://github.com/prabirshrestha/hn-react)** Dead-simple Hacker News client.
|
||||
@@ -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.
|
||||
|
||||
@@ -127,7 +127,7 @@ If you want to integrate with other JavaScript frameworks, set timers using `set
|
||||
|
||||
> Note:
|
||||
>
|
||||
> Prior to v0.6, the DOM node was passed in as the last argument. If you were using this, you can still access the DOM node by calling `this.getDOMNode()`.
|
||||
> Prior to v0.9, the DOM node was passed in as the last argument. If you were using this, you can still access the DOM node by calling `this.getDOMNode()`.
|
||||
|
||||
|
||||
### Updating: componentWillReceiveProps
|
||||
@@ -204,7 +204,7 @@ Use this as an opportunity to operate on the DOM when the component has been upd
|
||||
|
||||
> Note:
|
||||
>
|
||||
> Prior to v0.6, the DOM node was passed in as the last argument. If you were using this, you can still access the DOM node by calling `this.getDOMNode()`.
|
||||
> Prior to v0.9, the DOM node was passed in as the last argument. If you were using this, you can still access the DOM node by calling `this.getDOMNode()`.
|
||||
|
||||
|
||||
### Unmounting: componentWillUnmount
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
147
docs/docs/thinking-in-react.md
Normal file
147
docs/docs/thinking-in-react.md
Normal file
@@ -0,0 +1,147 @@
|
||||
---
|
||||
id: thinking-in-react
|
||||
title: Thinking in React
|
||||
layout: docs
|
||||
prev: tutorial.html
|
||||
next: videos.html
|
||||
---
|
||||
|
||||
This was originally a [blog post](/react/blog/2013/11/05/thinking-in-react.html) from the [official React blog](/react/blog).
|
||||
|
||||
React is, in my opinion, the premier way to build big, fast Web apps with JavaScript. It's scaled very well for us at Facebook and Instagram.
|
||||
|
||||
One of the many great parts of React is how it makes you think about apps as you build them. In this post I'll walk you through the thought process of building a searchable product data table using React.
|
||||
|
||||
## Start with a mock
|
||||
|
||||
Imagine that we already have a JSON API and a mock from our designer. Our designer apparently isn't very good because the mock looks like this:
|
||||
|
||||

|
||||
|
||||
Our JSON API returns some data that looks like this:
|
||||
|
||||
```
|
||||
[
|
||||
{category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
|
||||
{category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
|
||||
{category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
|
||||
{category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
|
||||
{category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
|
||||
{category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
|
||||
];
|
||||
```
|
||||
|
||||
## Step 1: break the UI into a component hierarchy
|
||||
|
||||
The first thing you'll want to do is to draw boxes around every component (and subcomponent) in the mock and give them all names. If you're working with a designer they may have already done this, so go talk to them! Their Photoshop layer names may end up being the names of your React components!
|
||||
|
||||
But how do you know what should be its own component? Just use the same techniques for deciding if you should create a new function or object. One such technique is the [single responsibility principle](http://en.wikipedia.org/wiki/Single_responsibility_principle), that is, a component should ideally only do one thing. If it ends up growing it should be decomposed into smaller subcomponents.
|
||||
|
||||
Since you're often displaying a JSON data model to a user, you'll find that if your model was built correctly your UI (and therefore your component structure) will map nicely onto it. That's because user interfaces and data models tend to adhere to the same *information architecture* which means the work of separating your UI into components is often trivial. Just break it up into components that represent exactly one piece of your data model.
|
||||
|
||||

|
||||
|
||||
You'll see here that we have five components in our simple app. I've italicized the data each component represents.
|
||||
|
||||
1. **`FilterableProductTable` (orange):** contains the entirety of the example
|
||||
2. **`SearchBar` (blue):** receives all *user input*
|
||||
3. **`ProductTable` (green):** displays and filters the *data collection* based on *user input*
|
||||
4. **`ProductCategoryRow` (turquoise):** displays a heading for each *category*
|
||||
5. **`ProductRow` (red):** displays a row for each *product*
|
||||
|
||||
If you look at `ProductTable` you'll see that the table header (containing the "Name" and "Price" labels) isn't its own component. This is a matter of preference and there's an argument to be made either way. For this example I left it as part of `ProductTable` because it is part of rendering the *data collection* which is `ProductTable`'s responsibility. However if this header grows to be complex (i.e. if we were to add affordances for sorting) it would certainly make sense to make this its own `ProductTableHeader` component.
|
||||
|
||||
Now that we've identified the components in our mock, let's arrange them into a hierarchy. This is easy. Components that appear within another component in the mock should appear as a child in the hierarchy:
|
||||
|
||||
* `FilterableProductTable`
|
||||
* `SearchBar`
|
||||
* `ProductTable`
|
||||
* `ProductCategoryRow`
|
||||
* `ProductRow`
|
||||
|
||||
## Step 2: Build a static version in React
|
||||
|
||||
<iframe width="100%" height="300" src="http://jsfiddle.net/6wQMG/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.
|
||||
|
||||
To build a static version of your app that renders your data model you'll want to build components that reuse other components and pass data using *props*. *props* are a way of passing data from parent to child. If you're familiar with the concept of *state*, **don't use state at all** to build this static version. State is reserved only for interactivity, that is, data that changes over time. Since this is a static version of the app you don't need it.
|
||||
|
||||
You can build top-down or bottom-up. That is, you can either start with building the components higher up in the hierarchy (i.e. starting with `FilterableProductTable`) or with the ones lower in it (`ProductRow`). In simpler examples it's usually easier to go top-down and on larger projects it's easier to go bottom-up and write tests as you build.
|
||||
|
||||
At the end of this step you'll have a library of reusable components that render your data model. The components will only have `render()` methods since this is a static version of your app. The component at the top of the hierarchy (`FilterableProductTable`) will take your data model as a prop. If you make a change to your underlying data model and call `renderComponent()` again the UI will be updated. It's easy to see how your UI is updated and where to make changes since there's nothing complicated going on since React's **one-way data flow** (also called *one-way binding*) keeps everything modular, easy to reason about, and fast.
|
||||
|
||||
Simply refer to the [React docs](http://facebook.github.io/react/docs/) if you need help executing this step.
|
||||
|
||||
### A brief interlude: props vs state
|
||||
|
||||
There are two types of "model" data in React: props and state. It's important to understand the distinction between the two; skim [the official React docs](http://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html) if you aren't sure what the difference is.
|
||||
|
||||
## Step 3: Identify the minimal (but complete) representation of UI state
|
||||
|
||||
To make your UI interactive you need to be able to trigger changes to your underlying data model. React makes this easy with **state**.
|
||||
|
||||
To build your app correctly you first need to think of the minimal set of mutable state that your app needs. The key here is DRY: *Don't Repeat Yourself*. Figure out what the absolute minimal representation of the state of your application needs to be and compute everything else you need on-demand. For example, if you're building a TODO list, just keep an array of the TODO items around; don't keep a separate state variable for the count. Instead, when you want to render the TODO count simply take the length of the TODO items array.
|
||||
|
||||
Think of all of the pieces of data in our example application. We have:
|
||||
|
||||
* The original list of products
|
||||
* The search text the user has entered
|
||||
* The value of the checkbox
|
||||
* The filtered list of products
|
||||
|
||||
Let's go through each one and figure out which one is state. Simply ask three questions about each piece of data:
|
||||
|
||||
1. Is it passed in from a parent via props? If so, it probably isn't state.
|
||||
2. Does it change over time? If not, it probably isn't state.
|
||||
3. Can you compute it based on any other state or props in your component? If so, it's not state.
|
||||
|
||||
The original list of products is passed in as props, so that's not state. The search text and the checkbox seem to be state since they change over time and can't be computed from anything. And finally, the filtered list of products isn't state because it can be computed by combining the original list of products with the search text and value of the checkbox.
|
||||
|
||||
So finally, our state is:
|
||||
|
||||
* The search text the user has entered
|
||||
* The value of the checkbox
|
||||
|
||||
## Step 4: Identify where your state should live
|
||||
|
||||
<iframe width="100%" height="300" src="http://jsfiddle.net/QvHnx/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.
|
||||
|
||||
Remember: React is all about one-way data flow down the component hierarchy. It may not be immediately clear which component should own what state. **This is often the most challenging part for newcomers to understand,** so follow these steps to figure it out:
|
||||
|
||||
For each piece of state in your application:
|
||||
|
||||
* Identify every component that renders something based on that state.
|
||||
* Find a common owner component (a single component above all the components that need the state in the hierarchy).
|
||||
* Either the common owner or another component higher up in the hierarchy should own the state.
|
||||
* If you can't find a component where it makes sense to own the state, create a new component simply for holding the state and add it somewhere in the hierarchy above the common owner component.
|
||||
|
||||
Let's run through this strategy for our application:
|
||||
|
||||
* `ProductTable` needs to filter the product list based on state and `SearchBar` needs to display the search text and checked state.
|
||||
* The common owner component is `FilterableProductTable`.
|
||||
* It conceptually makes sense for the filter text and checked value to live in `FilterableProductTable`
|
||||
|
||||
Cool, so we've decided that our state lives in `FilterableProductTable`. First, add a `getInitialState()` method to `FilterableProductTable` that returns `{filterText: '', inStockOnly: false}` to reflect the initial state of your application. Then pass `filterText` and `inStockOnly` to `ProductTable` and `SearchBar` as a prop. Finally, use these props to filter the rows in `ProductTable` and set the values of the form fields in `SearchBar`.
|
||||
|
||||
You can start seeing how your application will behave: set `filterText` to `"ball"` and refresh your app. You'll see the data table is updated correctly.
|
||||
|
||||
## Step 5: Add inverse data flow
|
||||
|
||||
<iframe width="100%" height="300" src="http://jsfiddle.net/3Vs3Q/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`.
|
||||
|
||||
React makes this data flow explicit to make it easy to understand how your program works, but it does require a little more typing than traditional two-way data binding. React provides an add-on called `ReactLink` to make this pattern as convenient as two-way binding, but for the purpose of this post we'll keep everything explicit.
|
||||
|
||||
If you try to type or check the box in the current version of the example you'll see that React ignores your input. This is intentional, as we've set the `value` prop of the `input` to always be equal to the `state` passed in from `FilterableProductTable`.
|
||||
|
||||
Let's think about what we want to happen. We want to make sure that whenever the user changes the form we update the state to reflect the user input. Since components should only update their own state, `FilterableProductTable` will pass a callback to `SearchBar` that will fire whenever the state should be updated. We can use the `onChange` event on the inputs to be notified of it. And the callback passed by `FilterableProductTable` will call `setState()` and the app will be updated.
|
||||
|
||||
Though this sounds like a lot it's really just a few lines of code. And it's really explicit how your data is flowing throughout the app.
|
||||
|
||||
## And that's it
|
||||
|
||||
Hopefully this gives you an idea of how to think about building components and applications with React. While it may be a little more typing than you're used to, remember that code is read far more than it's written, and it's extremely easy to read this modular, explicit code. As you start to build large libraries of components you'll appreciate this explicitness and modularity, and with code reuse your lines of code will start to shrink :)
|
||||
@@ -3,10 +3,10 @@ id: tutorial
|
||||
title: Tutorial
|
||||
layout: docs
|
||||
prev: getting-started.html
|
||||
next: videos.html
|
||||
next: thinking-in-react.html
|
||||
---
|
||||
|
||||
We'll be building a simple, but realistic comments box that you can drop into a blog, similar to Disqus, LiveFyre or Facebook comments.
|
||||
We'll be building a simple, but realistic comments box that you can drop into a blog, a basic version of the realtime comments offered by Disqus, LiveFyre or Facebook comments.
|
||||
|
||||
We'll provide:
|
||||
|
||||
@@ -409,7 +409,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 trivial to add live updates. We will use simple polling here but you could easily use WebSockets or other technologies.
|
||||
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}
|
||||
// tutorial14.js
|
||||
|
||||
@@ -3,7 +3,7 @@ id: videos
|
||||
title: Videos
|
||||
layout: docs
|
||||
permalink: videos.html
|
||||
prev: tutorial.html
|
||||
prev: thinking-in-react.html
|
||||
next: complementary-tools.html
|
||||
---
|
||||
|
||||
@@ -13,6 +13,39 @@ next: complementary-tools.html
|
||||
|
||||
"At Facebook and Instagram, we’re trying to push the limits of what’s 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/)
|
||||
|
||||
|
||||
### CodeWinds
|
||||
|
||||
[Pete Hunt](http://www.petehunt.net/) talked with [Jeff Barczewski](http://jeff.barczewski.com/) about React in CodeWinds Episode 4.
|
||||
<figure>[](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.
|
||||
|
||||
@@ -57,15 +57,27 @@ The JSX transformer used to support [XML syntax](/react/docs/jsx-in-depth.html)
|
||||
|
||||
All scripts are also available via [CDNJS](http://cdnjs.com/libraries/react/).
|
||||
|
||||
## Bower
|
||||
## npm
|
||||
|
||||
```sh
|
||||
$ bower install --save react
|
||||
```
|
||||
|
||||
## NPM
|
||||
To install the JSX transformer on your computer, run:
|
||||
|
||||
```sh
|
||||
$ npm install -g react-tools
|
||||
```
|
||||
|
||||
For more info about the `jsx` binary, see the [Getting Started](/react/docs/getting-started.html#offline-transform) guide.
|
||||
|
||||
If you're using an npm-compatible packaging system like browserify or webpack, you can use the `react` package. After installing it using `npm install react` or adding `react` to `package.json`, you can use React:
|
||||
|
||||
```js
|
||||
var React = require('react');
|
||||
React.renderComponent(...);
|
||||
```
|
||||
|
||||
If you'd like to use any [add-ons](/react/docs/addons.html), use `var React = require('react/addons');` instead.
|
||||
|
||||
## Bower
|
||||
|
||||
```sh
|
||||
$ bower install --save react
|
||||
```
|
||||
|
||||
BIN
docs/downloads/react-0.10.0-rc1.zip
Normal file
BIN
docs/downloads/react-0.10.0-rc1.zip
Normal file
Binary file not shown.
BIN
docs/img/blog/makona-editor.png
Normal file
BIN
docs/img/blog/makona-editor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/img/blog/react-svg-fbp.png
Normal file
BIN
docs/img/blog/react-svg-fbp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
docs/img/docs/codewinds-004.png
Normal file
BIN
docs/img/docs/codewinds-004.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
5
examples/README.md
Normal file
5
examples/README.md
Normal 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/).
|
||||
@@ -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 {
|
||||
|
||||
@@ -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).
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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')
|
||||
);
|
||||
@@ -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
@@ -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});
|
||||
@@ -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={"&":"&","<":"<",">":">",'"':""","'":"'"},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);
|
||||
@@ -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`.
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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",
|
||||
|
||||
@@ -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
12
main.js
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react",
|
||||
"description": "React is a JavaScript library for building user interfaces.",
|
||||
"version": "0.9.0-rc1",
|
||||
"version": "0.10.0",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
|
||||
2000
npm-shrinkwrap.json
generated
Normal file
2000
npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-tools",
|
||||
"description": "A set of complementary tools to React, including the JSX transformer.",
|
||||
"version": "0.9.0-rc1",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"unused": "vars",
|
||||
|
||||
"globals": {
|
||||
"__VERSION__": false,
|
||||
"__DEV__": false,
|
||||
"require": false,
|
||||
"module": false,
|
||||
|
||||
@@ -29,7 +29,7 @@ var shallowEqual = require('shallowEqual');
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var ReactComponentWithPureRender =
|
||||
* var ReactComponentWithPureRenderMixin =
|
||||
* require('ReactComponentWithPureRenderMixin');
|
||||
* React.createClass({
|
||||
* mixins: [ReactComponentWithPureRenderMixin],
|
||||
|
||||
48
src/addons/ReactRAFBatchingStrategy.js
Normal file
48
src/addons/ReactRAFBatchingStrategy.js
Normal 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;
|
||||
95
src/addons/__tests__/update-test.js
Normal file
95
src/addons/__tests__/update-test.js
Normal 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: ...}?'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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
159
src/addons/update.js
Normal 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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>'
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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__) {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
116
src/browser/server/ReactServerRenderingTransaction.js
Normal file
116
src/browser/server/ReactServerRenderingTransaction.js
Normal 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;
|
||||
@@ -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.'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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-rc1';
|
||||
React.version = '0.10.0';
|
||||
|
||||
module.exports = React;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
)
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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)) {
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
var DOMProperty = require('DOMProperty');
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
@@ -27,6 +29,8 @@ var ReactRootIndex = require('ReactRootIndex');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var ReactInjection = {
|
||||
Component: ReactComponent.injection,
|
||||
CompositeComponent: ReactCompositeComponent.injection,
|
||||
DOMProperty: DOMProperty.injection,
|
||||
EventPluginHub: EventPluginHub.injection,
|
||||
DOM: ReactDOM.injection,
|
||||
@@ -21,6 +21,7 @@
|
||||
var ReactDOMSelection = require('ReactDOMSelection');
|
||||
|
||||
var containsNode = require('containsNode');
|
||||
var focusNode = require('focusNode');
|
||||
var getActiveElement = require('getActiveElement');
|
||||
|
||||
function isInDocument(node) {
|
||||
@@ -71,7 +72,7 @@ var ReactInputSelection = {
|
||||
priorSelectionRange
|
||||
);
|
||||
}
|
||||
priorFocusedElem.focus();
|
||||
focusNode(priorFocusedElem);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -25,6 +25,7 @@ var ReactPerf = require('ReactPerf');
|
||||
|
||||
var containsNode = require('containsNode');
|
||||
var getReactRootElementInContainer = require('getReactRootElementInContainer');
|
||||
var instantiateReactComponent = require('instantiateReactComponent');
|
||||
var invariant = require('invariant');
|
||||
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
|
||||
|
||||
@@ -296,8 +297,13 @@ var ReactMount = {
|
||||
nextComponent,
|
||||
container,
|
||||
shouldReuseMarkup) {
|
||||
var reactRootID = ReactMount._registerComponent(nextComponent, container);
|
||||
nextComponent.mountComponentIntoNode(
|
||||
|
||||
var componentInstance = instantiateReactComponent(nextComponent);
|
||||
var reactRootID = ReactMount._registerComponent(
|
||||
componentInstance,
|
||||
container
|
||||
);
|
||||
componentInstance.mountComponentIntoNode(
|
||||
reactRootID,
|
||||
container,
|
||||
shouldReuseMarkup
|
||||
@@ -309,7 +315,7 @@ var ReactMount = {
|
||||
getReactRootElementInContainer(container);
|
||||
}
|
||||
|
||||
return nextComponent;
|
||||
return componentInstance;
|
||||
}
|
||||
),
|
||||
|
||||
@@ -610,8 +616,10 @@ var ReactMount = {
|
||||
invariant(
|
||||
false,
|
||||
'findComponentRoot(..., %s): Unable to find element. This probably ' +
|
||||
'means the DOM was unexpectedly mutated (e.g., by the browser). ' +
|
||||
'Try inspecting the child nodes of the element with React ID `%s`.',
|
||||
'means the DOM was unexpectedly mutated (e.g., by the browser), ' +
|
||||
'usually due to forgetting a <tbody> when using tables or nesting <p> ' +
|
||||
'or <a> tags. Try inspecting the child nodes of the element with React ' +
|
||||
'ID `%s`.',
|
||||
targetID,
|
||||
ReactMount.getID(ancestorNode)
|
||||
);
|
||||
@@ -221,6 +221,7 @@ describe('ReactDOMComponent', function() {
|
||||
|
||||
var mixInto = require('mixInto');
|
||||
var ReactDOMComponent = require('ReactDOMComponent');
|
||||
var ReactReconcileTransaction = require('ReactReconcileTransaction');
|
||||
|
||||
var NodeStub = function(initialProps) {
|
||||
this.props = initialProps || {};
|
||||
@@ -229,7 +230,10 @@ describe('ReactDOMComponent', function() {
|
||||
mixInto(NodeStub, ReactDOMComponent.Mixin);
|
||||
|
||||
genMarkup = function(props) {
|
||||
return (new NodeStub(props))._createOpenTagMarkupAndPutListeners();
|
||||
var transaction = new ReactReconcileTransaction();
|
||||
return (new NodeStub(props))._createOpenTagMarkupAndPutListeners(
|
||||
transaction
|
||||
);
|
||||
};
|
||||
|
||||
this.addMatchers({
|
||||
@@ -347,7 +351,7 @@ describe('ReactDOMComponent', function() {
|
||||
|
||||
var callback = function() {};
|
||||
var instance = <div onClick={callback} />;
|
||||
React.renderComponent(instance, container);
|
||||
instance = React.renderComponent(instance, container);
|
||||
|
||||
var rootNode = instance.getDOMNode();
|
||||
var rootNodeID = ReactMount.getID(rootNode);
|
||||
@@ -44,8 +44,8 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
var childControl = <div>Child</div>;
|
||||
var parentContainer = document.createElement('div');
|
||||
var parentControl = <div>Parent</div>;
|
||||
ReactMount.renderComponent(childControl, childContainer);
|
||||
ReactMount.renderComponent(parentControl, parentContainer);
|
||||
childControl = ReactMount.renderComponent(childControl, childContainer);
|
||||
parentControl = ReactMount.renderComponent(parentControl, parentContainer);
|
||||
parentControl.getDOMNode().appendChild(childContainer);
|
||||
|
||||
var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
|
||||
@@ -66,9 +66,9 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
var parentControl = <div>Parent</div>;
|
||||
var grandParentContainer = document.createElement('div');
|
||||
var grandParentControl = <div>Parent</div>;
|
||||
ReactMount.renderComponent(childControl, childContainer);
|
||||
ReactMount.renderComponent(parentControl, parentContainer);
|
||||
ReactMount.renderComponent(grandParentControl, grandParentContainer);
|
||||
childControl = ReactMount.renderComponent(childControl, childContainer);
|
||||
parentControl = ReactMount.renderComponent(parentControl, parentContainer);
|
||||
grandParentControl = ReactMount.renderComponent(grandParentControl, grandParentContainer);
|
||||
parentControl.getDOMNode().appendChild(childContainer);
|
||||
grandParentControl.getDOMNode().appendChild(parentContainer);
|
||||
|
||||
@@ -90,8 +90,8 @@ describe('ReactEventTopLevelCallback', function() {
|
||||
var childControl = <div>Child</div>;
|
||||
var parentContainer = document.createElement('div');
|
||||
var parentControl = <div>Parent</div>;
|
||||
ReactMount.renderComponent(childControl, childContainer);
|
||||
ReactMount.renderComponent(parentControl, parentContainer);
|
||||
childControl = ReactMount.renderComponent(childControl, childContainer);
|
||||
parentControl = ReactMount.renderComponent(parentControl, parentContainer);
|
||||
parentControl.getDOMNode().appendChild(childContainer);
|
||||
|
||||
// ReactEventEmitter.handleTopLevel might remove the target from the DOM.
|
||||
@@ -57,18 +57,24 @@ function insertChildAt(parentNode, childNode, index) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text content of `node` to `text`.
|
||||
*
|
||||
* @param {DOMElement} node Node to change
|
||||
* @param {string} text New text content
|
||||
*/
|
||||
var updateTextContent;
|
||||
if (textContentAccessor === 'textContent') {
|
||||
/**
|
||||
* Sets the text content of `node` to `text`.
|
||||
*
|
||||
* @param {DOMElement} node Node to change
|
||||
* @param {string} text New text content
|
||||
*/
|
||||
updateTextContent = function(node, text) {
|
||||
node.textContent = text;
|
||||
};
|
||||
} else {
|
||||
/**
|
||||
* Sets the text content of `node` to `text`.
|
||||
*
|
||||
* @param {DOMElement} node Node to change
|
||||
* @param {string} text New text content
|
||||
*/
|
||||
updateTextContent = function(node, text) {
|
||||
// In order to preserve newlines correctly, we can't use .innerText to set
|
||||
// the contents (see #1080), so we empty the element then append a text node
|
||||
@@ -231,11 +231,13 @@ var DOMProperty = {
|
||||
* @method
|
||||
*/
|
||||
isCustomAttribute: function(attributeName) {
|
||||
return DOMProperty._isCustomAttributeFunctions.some(
|
||||
function(isCustomAttributeFn) {
|
||||
return isCustomAttributeFn.call(null, attributeName);
|
||||
for (var i = 0; i < DOMProperty._isCustomAttributeFunctions.length; i++) {
|
||||
var isCustomAttributeFn = DOMProperty._isCustomAttributeFunctions[i];
|
||||
if (isCustomAttributeFn(attributeName)) {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -23,6 +23,7 @@ var DOMProperty = require('DOMProperty');
|
||||
|
||||
var escapeTextForBrowser = require('escapeTextForBrowser');
|
||||
var memoizeStringOnly = require('memoizeStringOnly');
|
||||
var warning = require('warning');
|
||||
|
||||
function shouldIgnoreValue(name, value) {
|
||||
return value == null ||
|
||||
@@ -57,11 +58,10 @@ if (__DEV__) {
|
||||
|
||||
// For now, only warn when we have a suggested correction. This prevents
|
||||
// logging too much when using transferPropsTo.
|
||||
if (standardName != null) {
|
||||
console.warn(
|
||||
'Unknown DOM property ' + name + '. Did you mean ' + standardName + '?'
|
||||
);
|
||||
}
|
||||
warning(
|
||||
standardName == null,
|
||||
'Unknown DOM property ' + name + '. Did you mean ' + standardName + '?'
|
||||
);
|
||||
|
||||
};
|
||||
}
|
||||
@@ -162,7 +162,7 @@ var DOMPropertyOperations = {
|
||||
var propName = DOMProperty.getPropertyName[name];
|
||||
var defaultValue = DOMProperty.getDefaultValueForProperty(
|
||||
node.nodeName,
|
||||
name
|
||||
propName
|
||||
);
|
||||
if (!DOMProperty.hasSideEffects[name] ||
|
||||
node[propName] !== defaultValue) {
|
||||
@@ -114,6 +114,7 @@ var DefaultDOMPropertyConfig = {
|
||||
spellCheck: null,
|
||||
src: null,
|
||||
srcDoc: MUST_USE_PROPERTY,
|
||||
srcSet: null,
|
||||
step: null,
|
||||
style: null,
|
||||
tabIndex: null,
|
||||
@@ -153,6 +154,7 @@ var DefaultDOMPropertyConfig = {
|
||||
stroke: MUST_USE_ATTRIBUTE,
|
||||
strokeLinecap: MUST_USE_ATTRIBUTE,
|
||||
strokeWidth: MUST_USE_ATTRIBUTE,
|
||||
textAnchor: MUST_USE_ATTRIBUTE,
|
||||
transform: MUST_USE_ATTRIBUTE,
|
||||
version: MUST_USE_ATTRIBUTE,
|
||||
viewBox: MUST_USE_ATTRIBUTE,
|
||||
@@ -173,6 +175,7 @@ var DefaultDOMPropertyConfig = {
|
||||
stopOpacity: 'stop-opacity',
|
||||
strokeLinecap: 'stroke-linecap',
|
||||
strokeWidth: 'stroke-width',
|
||||
textAnchor: 'text-anchor',
|
||||
viewBox: 'viewBox'
|
||||
},
|
||||
DOMPropertyNames: {
|
||||
@@ -185,18 +188,8 @@ var DefaultDOMPropertyConfig = {
|
||||
hrefLang: 'hreflang',
|
||||
radioGroup: 'radiogroup',
|
||||
spellCheck: 'spellcheck',
|
||||
srcDoc: 'srcdoc'
|
||||
},
|
||||
DOMMutationMethods: {
|
||||
/**
|
||||
* Setting `className` to null may cause it to be set to the string "null".
|
||||
*
|
||||
* @param {DOMElement} node
|
||||
* @param {*} value
|
||||
*/
|
||||
className: function(node, value) {
|
||||
node.className = value || '';
|
||||
}
|
||||
srcDoc: 'srcdoc',
|
||||
srcSet: 'srcset'
|
||||
}
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user