From 97ec6b66d0c258f5952de77d858cd2db20e02b55 Mon Sep 17 00:00:00 2001 From: Vjeux Date: Mon, 23 Dec 2013 06:37:00 +0100 Subject: [PATCH] =?UTF-8?q?chenglou=09docs=20section=20for=20non-dom=20att?= =?UTF-8?q?ributes=20=E2=80=A6=0994c100c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vjeux Document the Diff algorithm … 8f24fca vjeux Add video at the bottom of the front page 0993b61 bricooke Update highlighted lines in tutorial … 38d68fd jaredly rename this tip to be less confusing … 9686487 jaredly fixing capitalization 1a9b213 nicholasbs Fix typo (ot -> to) 18410bf jaredly adding note about initializing state w/ props 4f71ddf jaredly changes as requested cb0cbcc jaredly one liner cd2fd80 squidsoup Tutorial template markup needs a reference to jquery for the ajax calls … 37e544d chenglou docs add input attrs for Dom Differences 7eae8d3 chenglou docs make all link start with /react/docs dbc8563 Daniel15 Simple HTML to JSX converter, built during Hackathon 40 at Facebook. … 2308f95 --- docs/addons.html | 14 + docs/animation.html | 14 + docs/class-name-manipulation.html | 14 + docs/component-api.html | 14 + docs/component-specs.html | 18 +- docs/displaying-data.html | 18 +- docs/dom-differences.html | 21 +- docs/events.html | 16 +- docs/examples.html | 16 +- docs/forms.html | 14 + docs/getting-started.html | 18 +- docs/interactivity-and-dynamic-uis.html | 16 +- docs/jsx-gotchas.html | 16 +- docs/jsx-in-depth.html | 20 +- docs/more-about-refs.html | 14 + docs/multiple-components.html | 16 +- docs/reconciliation.html | 460 +++++++++++++++++ docs/reusable-components.html | 16 +- docs/special-non-dom-attributes.html | 389 ++++++++++++++ docs/tags-and-attributes.html | 20 +- docs/tooling-integration.html | 14 + docs/top-level-api.html | 16 +- docs/tutorial.html | 99 ++-- docs/two-way-binding-helpers.html | 18 +- docs/why-react.html | 14 + docs/working-with-the-browser.html | 16 +- feed.xml | 20 +- html-jsx.html | 89 ++++ index.html | 6 + js/html-jsx-lib.js | 482 ++++++++++++++++++ js/html-jsx.js | 89 ++++ tips/children-props-type.html | 14 + tips/communicate-between-components.html | 14 + ...iveProps-not-triggered-after-mounting.html | 14 + tips/controlled-input-null-value.html | 14 + tips/dom-event-listeners.html | 16 +- tips/false-in-jsx.html | 14 + tips/if-else-in-JSX.html | 14 + tips/initial-ajax.html | 14 + tips/inline-styles.html | 14 + tips/introduction.html | 14 + tips/maximum-number-of-jsx-root-nodes.html | 14 + ...ps-in-getInitialState-as-anti-pattern.html | 35 +- tips/self-closing-tag.html | 14 + tips/style-props-value-px.html | 14 + 45 files changed, 2149 insertions(+), 77 deletions(-) create mode 100644 docs/reconciliation.html create mode 100644 docs/special-non-dom-attributes.html create mode 100644 html-jsx.html create mode 100644 js/html-jsx-lib.js create mode 100644 js/html-jsx.js diff --git a/docs/addons.html b/docs/addons.html index 195a3b877b..65baa9286a 100644 --- a/docs/addons.html +++ b/docs/addons.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/docs/animation.html b/docs/animation.html index a3bf69c0df..5bc79ef428 100644 --- a/docs/animation.html +++ b/docs/animation.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/docs/class-name-manipulation.html b/docs/class-name-manipulation.html index e087a55857..d404f1a708 100644 --- a/docs/class-name-manipulation.html +++ b/docs/class-name-manipulation.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/docs/component-api.html b/docs/component-api.html index 83c09752b4..30064ab8c2 100644 --- a/docs/component-api.html +++ b/docs/component-api.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/docs/component-specs.html b/docs/component-specs.html index 6954311dff..ac980dcc90 100644 --- a/docs/component-specs.html +++ b/docs/component-specs.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -342,12 +356,12 @@

    This method is invoked before getInitialState and therefore cannot rely on this.state or use this.setState.

    propTypes #

    object propTypes
     
    -

    The propTypes object allows you to validate props being passed to your components. For more information about propTypes, see Reusable Components.

    +

    The propTypes object allows you to validate props being passed to your components. For more information about propTypes, see Reusable Components.

    mixins #

    array mixins
     
    -

    The mixins array allows you to use mixins to share behavior among multiple components. For more information about mixins, see Reusable Components.

    +

    The mixins array allows you to use mixins to share behavior among multiple components. For more information about mixins, see Reusable Components.

    Lifecycle Methods #

    diff --git a/docs/displaying-data.html b/docs/displaying-data.html index f23f68f1dd..93bc7ab979 100644 --- a/docs/displaying-data.html +++ b/docs/displaying-data.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -384,9 +398,9 @@

    JSX lets you write JavaScript function calls with HTML syntax. To generate a link in React using pure JavaScript you'd write: React.DOM.a({href: 'http://facebook.github.io/react/'}, 'Hello React!'). With JSX this becomes <a href="http://facebook.github.io/react/">Hello React!</a>. We've found this has made building React apps easier and designers tend to prefer the syntax, but everyone has their own workflow, so JSX is not required to use React.

    -

    JSX is very small; the "hello, world" example above uses every feature of JSX. To learn more about it, see JSX in depth. Or see the transform in action in our live JSX compiler.

    +

    JSX is very small; the "hello, world" example above uses every feature of JSX. To learn more about it, see JSX in depth. Or see the transform in action in our live JSX compiler.

    -

    JSX is similar to HTML, but not exactly the same. See JSX gotchas for some key differences.

    +

    JSX is similar to HTML, but not exactly the same. See JSX gotchas for some key differences.

    The easiest way to get started with JSX is to use the in-browser JSXTransformer. We strongly recommend that you don't use this in production. You can precompile your code using our command-line react-tools package.

    diff --git a/docs/dom-differences.html b/docs/dom-differences.html index bf3b885519..5a8608237f 100644 --- a/docs/dom-differences.html +++ b/docs/dom-differences.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -328,8 +342,9 @@ @@ -338,6 +353,8 @@ ← Prev + Next → +
    diff --git a/docs/events.html b/docs/events.html index 331e2fac36..d65415ed27 100644 --- a/docs/events.html +++ b/docs/events.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -372,7 +386,7 @@ different browsers.

    Event names:

    onChange onInput onSubmit
     
    -

    For more information about the onChange event, see Forms.

    +

    For more information about the onChange event, see Forms.

    Mouse Events #

    Event names:

    onClick onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave
    diff --git a/docs/examples.html b/docs/examples.html
    index 18c3ca3316..4235cf94bf 100644
    --- a/docs/examples.html
    +++ b/docs/examples.html
    @@ -248,6 +248,20 @@
                 
               
             
    +          
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    @@ -331,7 +345,7 @@

    Sample Code #

    diff --git a/docs/getting-started.html b/docs/getting-started.html index e677235c21..98561d4a81 100644 --- a/docs/getting-started.html +++ b/docs/getting-started.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -358,7 +372,7 @@ </body> </html>
    -

    The XML syntax inside of JavaScript is called JSX; check out the JSX syntax to learn more about it. In order to translate it to vanilla JavaScript we use <script type="text/jsx"> and include JSXTransformer.js to actually perform the transformation in the browser.

    +

    The XML syntax inside of JavaScript is called JSX; check out the JSX syntax to learn more about it. In order to translate it to vanilla JavaScript we use <script type="text/jsx"> and include JSXTransformer.js to actually perform the transformation in the browser.

    Separate File #

    Your React JSX file can live in a separate file. Create the following src/helloworld.js.

    /** @jsx React.DOM */
    @@ -405,7 +419,7 @@
     

    Want CommonJS? #

    If you want to use React within a module system, fork our repo, npm install and run grunt. A nice set of CommonJS modules will be generated. Our jsx build tool can be integrated into most packaging systems (not just CommonJS) quite easily.

    Next Steps #

    -

    Check out the tutorial and the other examples in the /examples directory to learn more. Good luck, and welcome!

    +

    Check out the tutorial and the other examples in the /examples directory to learn more. Good luck, and welcome!

    diff --git a/docs/interactivity-and-dynamic-uis.html b/docs/interactivity-and-dynamic-uis.html index 720fc85e6b..a474e32ece 100644 --- a/docs/interactivity-and-dynamic-uis.html +++ b/docs/interactivity-and-dynamic-uis.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    @@ -323,7 +337,7 @@

    Interactivity and Dynamic UIs

    -

    You've already learned how to display data with React. Now let's look at how to make our UIs interactive.

    +

    You've already learned how to display data with React. Now let's look at how to make our UIs interactive.

    A Simple Example #

    /** @jsx React.DOM */
     
     var LikeButton = React.createClass({
    diff --git a/docs/jsx-gotchas.html b/docs/jsx-gotchas.html
    index 323be573a8..b8621ec9f4 100644
    --- a/docs/jsx-gotchas.html
    +++ b/docs/jsx-gotchas.html
    @@ -248,6 +248,20 @@
                 
               
             
    +          
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    @@ -328,7 +342,7 @@

    Note:

    -

    For DOM differences, such as the inline style attribute, check here.

    +

    For DOM differences, such as the inline style attribute, check here.

    Whitespace Removal #

    JSX doesn't follow the same whitespace elimination rules as HTML. JSX removes all whitespace between two curly braces expressions. If you want to have whitespace, simply add {' '}.

    diff --git a/docs/jsx-in-depth.html b/docs/jsx-in-depth.html index 96434da5ec..f1e6a39710 100644 --- a/docs/jsx-in-depth.html +++ b/docs/jsx-in-depth.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    @@ -358,7 +372,7 @@ attributes are transformed into function calls and objects, respectively.

    Use the JSX Compiler to try out JSX and see how it desugars into native JavaScript.

    -

    If you want to use JSX, the Getting Started guide shows +

    If you want to use JSX, the Getting Started guide shows how to setup compilation.

    @@ -386,7 +400,7 @@ references the class.

    var MyComponent = React.createClass({/*...*/});
     var app = <MyComponent someProperty={true} />;
     
    -

    See Multiple Components to learn more about using composite components.

    +

    See Multiple Components to learn more about using composite components.

    Note:

    @@ -443,7 +457,7 @@ efforts include:

  • JSX does not alter or add to the semantics of JavaScript.
  • -

    JSX is similar to HTML, but not exactly the same. See JSX gotchas for some key differences.

    +

    JSX is similar to HTML, but not exactly the same. See JSX gotchas for some key differences.

    diff --git a/docs/more-about-refs.html b/docs/more-about-refs.html index ea95593c40..000e9f2d32 100644 --- a/docs/more-about-refs.html +++ b/docs/more-about-refs.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    diff --git a/docs/multiple-components.html b/docs/multiple-components.html index d85d8f4476..7ac452c2b3 100644 --- a/docs/multiple-components.html +++ b/docs/multiple-components.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -418,7 +432,7 @@

    A Note on Performance #

    You may be thinking that it's expensive to react to changing data if there are a large number of nodes under an owner. The good news is that JavaScript is fast and render() methods tend to be quite simple, so in most applications this is extremely fast. Additionally, the bottleneck is almost always the DOM mutation and not JS execution and React will optimize this for you using batching and change detection.

    -

    However, sometimes you really want to have fine-grained control over your performance. In that case, simply override shouldComponentUpdate() to return false when you want React to skip processing of a subtree. See the React reference docs for more information.

    +

    However, sometimes you really want to have fine-grained control over your performance. In that case, simply override shouldComponentUpdate() to return false when you want React to skip processing of a subtree. See the React reference docs for more information.

    Note:

    diff --git a/docs/reconciliation.html b/docs/reconciliation.html new file mode 100644 index 0000000000..46f20e91b9 --- /dev/null +++ b/docs/reconciliation.html @@ -0,0 +1,460 @@ + + + + + + React | Reconciliation + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + +
    +

    Reconciliation

    +
    +

    React key design decision is to make the API seem like it re-renders the whole app on every update. This makes writing applications a lot easier but is also an incredible challenge to make it tractable. This article explains how with powerful heuristics we managed to turn a O(n3) problem into a O(n) one.

    +

    Motivation #

    +

    Generating the minimum number of operations to transform one tree into another is a complex and well-studied problem. The state of the art algorithms have a complexity in the order of O(n3) where n is the number of nodes in the tree.

    + +

    This means that displaying 1000 nodes would require in the order of one billion comparisons. This is far too expensive for our use case. To put this number in perspective, CPUs nowadays execute roughly 3 billion instruction per second. So even with the most performant implementation, we wouldn't be able to compute that diff in less than a second.

    + +

    Since an optimal algorithm is not tractable, we implement a non-optimal O(n) algorithm using heuristics based on two assumptions:

    + +
      +
    1. Two components of the same class will generate similar trees and two components of different classes will generate different trees.
    2. +
    3. It is possible to provide a unique key for elements that is stable across different renders.
    4. +
    + +

    In practice, these assumptions are ridiculously fast for almost all practical use cases.

    +

    Pair-wise diff #

    +

    In order to do a tree diff, we first need to be able to diff two nodes. There are three different cases being handled.

    +

    Different Node Types #

    +

    If the node type is different, React is going to treat them as two different sub-trees, throw away the first one and build/insert the second one.

    +
    renderA: <div />
    +renderB: <span />
    +=> [removeNode <div />], [insertNode <span />]
    +
    +

    The same logic is used for custom components. If they are not of the same type, React is not going to even try at matching what they render. It is just going to remove the first one from the DOM and insert the second one.

    +
    renderA: <Header />
    +renderB: <Content />
    +=> [removeNode <Header />], [insertNode <Content />]
    +
    +

    Having this high level knowledge is a very important aspect of why React diff algorithm is both fast and precise. It provides a good heuristic to quickly prune big parts of the tree and focus on parts likely to be similar.

    + +

    It is very unlikely that a <Header> element is going generate a DOM that is going to look like what a <Content> would generate. Instead of spending time trying to match those two structures, React just re-builds the tree from scratch.

    + +

    As a corollary, if there is a <Header> element at the same position in two consecutive renders, you would expect to see a very similar structure and it is worth exploring it.

    +

    DOM Nodes #

    +

    When comparing two DOM nodes, we look at the attributes of both and can decide which of them changed in linear time.

    +
    renderA: <div id="before" />
    +renderB: <div id="after" />
    +=> [replaceAttribute id "after"]
    +
    +

    Instead of treating style as an opaque string, a key-value object is used instead. This lets us update only the properties that changed.

    +
    renderA: <div style={{color: 'red'}} />
    +renderB: <div style={{fontWeight: 'bold'}} />
    +=> [removeStyle color], [addStyle font-weight 'bold']
    +
    +

    After the attributes have been updated, we recurse on all the children.

    +

    Custom Components #

    +

    We decided that the two custom components are the same. Since components are stateful, we cannot just use the new component and call it a day. React takes all the attributes from the new component and call component[Will/Did]ReceiveProps() on the previous one.

    + +

    The previous component is now operational. Its render() method is called and the diff algorithm restarts with the new result and the previous result.

    +

    List-wise diff #

    Problematic Case #

    +

    In order to do children reconciliation, React adopts a very naive approach. It goes over the list of children both at the same time and whenever there's a difference generates a mutation.

    + +

    For example if you add an element at the end:

    +
    renderA: <div><span>first</span></div>
    +renderB: <div><span>first</span><span>second</span></div>
    +=> [insertNode <span>second</span>]
    +
    +

    Inserting an element at the beginning is problematic. React is going to see that both nodes are spans and therefore run into a mutation mode.

    +
    renderA: <div><span>first</span></div>
    +renderB: <div><span>second</span><span>first</span></div>
    +=> [replaceAttribute textContent 'second'], [insertNode <span>first</span>]
    +
    +

    There are many algorithms that attempt to find the minimum sets of operations to transform a list of elements. Levenshtein distance can find the minimum using single element insertion, deletion and substitution in O(n2). Even if we were to use Levenshtein, this doesn't find when a node has moved into another position and algorithms to do that have a much worst complexity.

    +

    Keys #

    +

    In order to solve this seemingly intractable issue, an optional attribute has been introduced. You can provide for each child a key that is going to be used to do the matching. If you specify a key, React is now able to find insertion, deletion, substitution and moves in O(n) using a hash table.

    +
    renderA: <div><span key="first">first</span></div>
    +renderB: <div><span key="second">second</span><span key="first">first</span></div>
    +=> [insertNode <span>second</span>]
    +
    +

    In practice, finding a key is not really hard. Most of the time, the element you are going to display already have a unique id. When it is not the case, you can hash some parts of the content to generate an id. Remember that the id only has to be unique among its sibling, not globally unique.

    +

    Trade-offs #

    +

    It is important to remember that the reconciliation algorithm is an implementation detail. React could re-render the whole app on every action, the end-result would be the same. We are regularly refining the heuristics in order to make common use cases faster.

    + +

    In the current implementation, you can express the fact that a sub-tree has been moved between siblings, but you cannot tell that it has moved somewhere else. The algorithm will re-render that full sub-tree.

    + +

    Because we rely on two heuristics, if the assumptions behind them are not met, performance will suffer.

    + +
      +
    1. The algorithm will not try to match sub-trees of different components classes. If you see yourself alternating between two components classes with very similar output, you may want to make it the same class. In practice, we haven't found this to be an issue.

    2. +
    3. If you don't provide stable keys (by using Math.random() for example), all the sub-trees are going to be re-rendered every single time. By giving the users the choice to chose the key, they have the ability to shoot themselves in the foot.

    4. +
    + + +
    + + ← Prev + + +
    + +
    +
    +
    + + +
    +
    A Facebook & Instagram collaboration.
    +
    © 2013 Facebook Inc.
    +
    +
    +
    + + + + diff --git a/docs/reusable-components.html b/docs/reusable-components.html index 2a72d951da..7356be974a 100644 --- a/docs/reusable-components.html +++ b/docs/reusable-components.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -391,7 +405,7 @@

    Mixins #

    Components are the best way to reuse code in React, but sometimes very different components may share some common functionality. These are sometimes called cross-cutting concerns. React provides mixins to solve this problem.

    -

    One common use case is a component wanting to update itself on a time interval. It's easy to use setInterval(), but it's important to cancel your interval when you don't need it anymore to save memory. React provides lifecycle methods that let you know when a component is about to be created or destroyed. Let's create a simple mixin that uses these methods to provide an easy setInterval() function that will automatically get cleaned up when your component is destroyed.

    +

    One common use case is a component wanting to update itself on a time interval. It's easy to use setInterval(), but it's important to cancel your interval when you don't need it anymore to save memory. React provides lifecycle methods that let you know when a component is about to be created or destroyed. Let's create a simple mixin that uses these methods to provide an easy setInterval() function that will automatically get cleaned up when your component is destroyed.

    /** @jsx React.DOM */
     
     var SetIntervalMixin = {
    diff --git a/docs/special-non-dom-attributes.html b/docs/special-non-dom-attributes.html
    new file mode 100644
    index 0000000000..71cd8ca47d
    --- /dev/null
    +++ b/docs/special-non-dom-attributes.html
    @@ -0,0 +1,389 @@
    +
    +
    +
    +  
    +  
    +  React | Special Non-DOM Attributes
    +  
    +  
    +  
    +  
    +  
    +  
    +  
    +
    +  
    +  
    +
    +  
    +  
    +  
    +
    +  
    +  
    +
    +  
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +  
    + + + + + +
    + + + +
    +

    Special Non-DOM Attributes

    +
    +

    Beside DOM differences, React offers some attributes that simply don't exist in DOM.

    + +
      +
    • key: an optional, unique identifier. When your component shuffles around during render passes, it might be destroyed and recreated due to the diff algorithm. Assigning it a key that persists makes sure the component stays. See more here.
    • +
    • ref: see here.
    • +
    • dangerouslySetInnerHTML: takes an object with the key __html and a DOM string as value. This is mainly for cooperating with DOM string manipulation libraries. Refer to the last example on the front page.
    • +
    + + +
    + + ← Prev + + + Next → + +
    + +
    +
    +
    + + +
    +
    A Facebook & Instagram collaboration.
    +
    © 2013 Facebook Inc.
    +
    +
    +
    + + + + diff --git a/docs/tags-and-attributes.html b/docs/tags-and-attributes.html index 255a86c9c8..c5681d01ab 100644 --- a/docs/tags-and-attributes.html +++ b/docs/tags-and-attributes.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    @@ -345,7 +359,7 @@ title tr track u ul var video wbr

    All attributes are camel-cased and the attributes class and for are className and htmlFor, respectively, to match the DOM API specification.

    -

    For a list of events, see Supported Events.

    +

    For a list of events, see Supported Events.

    HTML Attributes #

    accept accessKey action allowFullScreen allowTransparency alt autoCapitalize
     autoComplete autoFocus autoPlay cellPadding cellSpacing charSet checked
     className colSpan content contentEditable contextMenu controls data dateTime
    @@ -355,7 +369,9 @@ pattern placeholder poster preload radioGroup readOnly rel required role
     rowSpan scrollLeft scrollTop selected size spellCheck src step style tabIndex
     target title type value width wmode
     
    -

    In addition, the non-standard autoCapitalize attribute is supported for Mobile Safari.

    +

    The non-standard autoCapitalize attribute is supported for Mobile Safari.

    + +

    In addition, there is the React-specific attribute dangerouslySetInnerHTML (more here), used for directly inserting DOM strings into a component.

    SVG Attributes #

    cx cy d fill fx fy gradientTransform gradientUnits offset points r rx ry
     spreadMethod stopColor stopOpacity stroke strokeLinecap strokeWidth transform
     version viewBox x1 x2 x y1 y2 y
    diff --git a/docs/tooling-integration.html b/docs/tooling-integration.html
    index 85495bc9dd..a7077a2819 100644
    --- a/docs/tooling-integration.html
    +++ b/docs/tooling-integration.html
    @@ -248,6 +248,20 @@
                 
               
             
    +          
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    diff --git a/docs/top-level-api.html b/docs/top-level-api.html index 40f190e536..2fa3fb7055 100644 --- a/docs/top-level-api.html +++ b/docs/top-level-api.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -334,7 +348,7 @@

    Creates a component given a specification. A component implements a render method which returns one single child. That child may have an arbitrarily deep child structure. One thing that makes components different than standard prototypal classes is that you don't need to call new on them. They are convenience wrappers that construct backing instances (via new) for you.

    -

    For more information about the specification object, see Component Specs and Lifecycle.

    +

    For more information about the specification object, see Component Specs and Lifecycle.

    React.renderComponent #

    ReactComponent renderComponent(
       ReactComponent component,
       DOMElement container,
    diff --git a/docs/tutorial.html b/docs/tutorial.html
    index f2594a0f0e..7eb0667e13 100644
    --- a/docs/tutorial.html
    +++ b/docs/tutorial.html
    @@ -248,6 +248,20 @@
                 
               
             
    +          
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    @@ -350,6 +364,7 @@ <title>Hello React</title> <script src="http://fb.me/react-0.8.0.js"></script> <script src="http://fb.me/JSXTransformer-0.8.0.js"></script> + <script src="http://code.jquery.com/jquery-1.10.0.min.js"></script> </head> <body> <div id="content"></div> @@ -403,7 +418,7 @@ document.getElementById('content') ); -

    Its use is optional but we've found JSX syntax easier to use than plain JavaScript. Read more on the JSX Syntax article.

    +

    Its use is optional but we've found JSX syntax easier to use than plain JavaScript. Read more on the JSX Syntax article.

    What's going on #

    We pass some methods in a JavaScript object to React.createClass() to create a new React component. The most important of these methods is called render which returns a tree of React components that will eventually render to HTML.

    @@ -580,9 +595,9 @@

    Fetching from the server #

    Let's replace the hard-coded data with some dynamic data from the server. We will remove the data prop and replace it with a URL to fetch:

    // tutorial11.js
    -React.renderComponent(
    -  <CommentBox url="comments.json" />,
    -  document.getElementById('content')
    +React.renderComponent(
    +  <CommentBox url="comments.json" />,
    +  document.getElementById('content')
     );
     

    This component is different from the prior components because it will have to re-render itself. The component won't have any data until the request from the server comes back, at which point the component may need to render some new comments.

    @@ -630,8 +645,8 @@ }.bind(this) }); return {data: []}; - }, - render: function() { + }, + render: function() { return ( <div className="commentBox"> <h1>Comments</h1> @@ -656,15 +671,15 @@ getInitialState: function() { return {data: []}; }, - componentWillMount: function() { - this.loadCommentsFromServer(); - setInterval(this.loadCommentsFromServer, this.props.pollInterval); - }, - render: function() { - return ( - <div className="commentBox"> - <h1>Comments</h1> - <CommentList data={this.state.data} /> + componentWillMount: function() { + this.loadCommentsFromServer(); + setInterval(this.loadCommentsFromServer, this.props.pollInterval); + }, + render: function() { + return ( + <div className="commentBox"> + <h1>Comments</h1> + <CommentList data={this.state.data} /> <CommentForm /> </div> ); @@ -672,8 +687,8 @@ }); React.renderComponent( - <CommentBox url="comments.json" pollInterval={2000} />, - document.getElementById('content') + <CommentBox url="comments.json" pollInterval={2000} />, + document.getElementById('content') );

    All we have done here is move the AJAX call to a separate method and call it when the component is first loaded and every 2 seconds after that. Try running this in your browser and changing the comments.json file; within 2 seconds, the changes will show!

    @@ -709,8 +724,8 @@ render: function() { return ( <form className="commentForm" onSubmit={this.handleSubmit}> - <input type="text" placeholder="Your name" ref="author" /> - <input + <input type="text" placeholder="Your name" ref="author" /> + <input type="text" placeholder="Say something..." ref="text" @@ -740,12 +755,12 @@ }.bind(this) }); }, - handleCommentSubmit: function(comment) { - // TODO: submit to the server and refresh the list - }, - getInitialState: function() { - return {data: []}; - }, + handleCommentSubmit: function(comment) { + // TODO: submit to the server and refresh the list + }, + getInitialState: function() { + return {data: []}; + }, componentWillMount: function() { this.loadCommentsFromServer(); setInterval(this.loadCommentsFromServer, this.props.pollInterval); @@ -756,13 +771,13 @@ <h1>Comments</h1> <CommentList data={this.state.data} /> <CommentForm - onCommentSubmit={this.handleCommentSubmit} - /> + onCommentSubmit={this.handleCommentSubmit} + /> </div> ); } -}); - +}); +

    Let's call the callback from the CommentForm when the user submits the form:

    // tutorial18.js
     var CommentForm = React.createClass({
    @@ -801,18 +816,18 @@
         });
       },
       handleCommentSubmit: function(comment) {
    -    $.ajax({
    -      url: this.props.url,
    -      type: 'POST',
    +    $.ajax({
    +      url: this.props.url,
    +      type: 'POST',
           data: comment,
           success: function(data) {
             this.setState({data: data});
           }.bind(this)
         });
    -  },
    -  getInitialState: function() {
    -    return {data: []};
       },
    +  getInitialState: function() {
    +    return {data: []};
    +  },
       componentWillMount: function() {
         this.loadCommentsFromServer();
         setInterval(this.loadCommentsFromServer, this.props.pollInterval);
    @@ -842,12 +857,12 @@
         });
       },
       handleCommentSubmit: function(comment) {
    -    var comments = this.state.data;
    -    var newComments = comments.concat([comment]);
    -    this.setState({data: newComments});
    -    $.ajax({
    -      url: this.props.url,
    -      type: 'POST',
    +    var comments = this.state.data;
    +    var newComments = comments.concat([comment]);
    +    this.setState({data: newComments});
    +    $.ajax({
    +      url: this.props.url,
    +      type: 'POST',
           data: comment,
           success: function(data) {
             this.setState({data: data});
    @@ -874,7 +889,7 @@
       }
     });
     

    Congrats! #

    -

    You have just built a comment box in a few simple steps. Learn more about why to use React, or dive into the API reference and start hacking! Good luck!

    +

    You have just built a comment box in a few simple steps. Learn more about why to use React, or dive into the API reference and start hacking! Good luck!

    diff --git a/docs/two-way-binding-helpers.html b/docs/two-way-binding-helpers.html index da6c8d251c..52885f6b16 100644 --- a/docs/two-way-binding-helpers.html +++ b/docs/two-way-binding-helpers.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    @@ -335,7 +349,7 @@

    However, there are lots of applications that require you to read some data and flow it back into your program. For example, when developing forms, you'll often want to update some React state when you receive user input. Or perhaps you want to perform layout in JavaScript and react to changes in some DOM element size.

    -

    In React, you would implement this by listening to a "change" event, read from your data source (usually the DOM) and call setState() on one of your components. "Closing the data flow loop" explicitly leads to more understandable and easier-to-maintain programs. See our forms documentation for more information.

    +

    In React, you would implement this by listening to a "change" event, read from your data source (usually the DOM) and call setState() on one of your components. "Closing the data flow loop" explicitly leads to more understandable and easier-to-maintain programs. See our forms documentation for more information.

    Two-way binding -- implicitly enforcing that some value in the DOM is always consistent with some React state -- is concise and supports a wide variety of applications. We've provided ReactLink: syntactic sugar for setting up the common data flow loop pattern described above, or "linking" some data source to React state.

    @@ -374,7 +388,7 @@
    } }); -

    LinkedStateMixin adds a method ot your React component called linkState(). linkState() returns a ReactLink object which contains the current value of the React state and a callback to change it.

    +

    LinkedStateMixin adds a method to your React component called linkState(). linkState() returns a ReactLink object which contains the current value of the React state and a callback to change it.

    ReactLink objects can be passed up and down the tree as props, so it's easy (and explicit) to set up two-way binding between a component deep in the hierarchy and state that lives higher in the hierarchy.

    Under the Hood #

    diff --git a/docs/why-react.html b/docs/why-react.html index 27f31b51d2..1243fa2f68 100644 --- a/docs/why-react.html +++ b/docs/why-react.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/docs/working-with-the-browser.html b/docs/working-with-the-browser.html index b6ce34029d..1af37817a7 100644 --- a/docs/working-with-the-browser.html +++ b/docs/working-with-the-browser.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -368,7 +382,7 @@ document.getElementById('example') );

    More About Refs #

    -

    To learn more about refs, including ways to use them effectively, see our more about refs documentation.

    +

    To learn more about refs, including ways to use them effectively, see our more about refs documentation.

    Component Lifecycle #

    Components have three main parts of their lifecycle:

    diff --git a/feed.xml b/feed.xml index 99a5f0266a..5112c1ec52 100644 --- a/feed.xml +++ b/feed.xml @@ -50,7 +50,7 @@ <li>Upgraded browserify, which reduced file size by ~65KB (16KB gzipped)</li> </ul> - 2013-12-19T00:00:00-08:00 + 2013-12-19T00:00:00+01:00 http://facebook.github.io/react/blog/2013/12/19/react-v0.8.0.html http://facebook.github.io/react/blog/2013/12/19/react-v0.8.0.html @@ -69,7 +69,7 @@ <p>You can learn more about the vulnerability discussed here: <a href="https://groups.google.com/forum/#!topic/reactjs/OIqxlB2aGfU">CVE-2013-7035</a>.</p> - 2013-12-18T00:00:00-08:00 + 2013-12-18T00:00:00+01:00 http://facebook.github.io/react/blog/2013/12/18/react-v0.5.2-v0.4.2.html http://facebook.github.io/react/blog/2013/12/18/react-v0.5.2-v0.4.2.html @@ -146,7 +146,7 @@ Is this some sort of template language? Specifically no. This might have been th <p><img src="/react/img/blog/steve_reverse.gif" style="float: right;" /> <div style="width: 320px;"><blockquote class="twitter-tweet"><p>I think this reversed gif of Steve Urkel best describes my changing emotions towards the React Lib <a href="http://t.co/JoX0XqSXX3">http://t.co/JoX0XqSXX3</a></p>&mdash; Ryan Seddon (@ryanseddon) <a href="https://twitter.com/ryanseddon/statuses/398572848802852864">November 7, 2013</a></blockquote></div></p> - 2013-11-18T00:00:00-08:00 + 2013-11-18T00:00:00+01:00 http://facebook.github.io/react/blog/2013/11/18/community-roundup-11.html http://facebook.github.io/react/blog/2013/11/18/community-roundup-11.html @@ -262,7 +262,7 @@ Is this some sort of template language? Specifically no. This might have been th <blockquote class="twitter-tweet"><p>This weekend <a href="https://twitter.com/search?q=%23angular&amp;src=hash">#angular</a> died for me. Meet new king <a href="https://twitter.com/search?q=%23reactjs&amp;src=hash">#reactjs</a></p>&mdash; Eldar Djafarov &#x30C3; (@edjafarov) <a href="https://twitter.com/edjafarov/statuses/397033796710961152">November 3, 2013</a></blockquote> - 2013-11-06T00:00:00-08:00 + 2013-11-06T00:00:00+01:00 http://facebook.github.io/react/blog/2013/11/06/community-roundup-10.html http://facebook.github.io/react/blog/2013/11/06/community-roundup-10.html @@ -408,7 +408,7 @@ Is this some sort of template language? Specifically no. This might have been th <h2><a class="anchor" name="and-thats-it"></a>And that&#39;s it <a class="hash-link" href="#and-thats-it">#</a></h2> <p>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&#39;re used to, remember that code is read far more than it&#39;s written, and it&#39;s extremely easy to read this modular, explicit code. As you start to build large libraries of components you&#39;ll appreciate this explicitness and modularity, and with code reuse your lines of code will start to shrink :)</p> - 2013-11-05T00:00:00-08:00 + 2013-11-05T00:00:00+01:00 http://facebook.github.io/react/blog/2013/11/05/thinking-in-react.html http://facebook.github.io/react/blog/2013/11/05/thinking-in-react.html @@ -428,7 +428,7 @@ Is this some sort of template language? Specifically no. This might have been th <li>Fixed bug with transition and animation event detection.</li> </ul> - 2013-10-29T00:00:00-07:00 + 2013-10-29T00:00:00+01:00 http://facebook.github.io/react/blog/2013/10/29/react-v0-5-1.html http://facebook.github.io/react/blog/2013/10/29/react-v0-5-1.html @@ -477,7 +477,7 @@ Is this some sort of template language? Specifically no. This might have been th <li>Improved support for maintaining line numbers when transforming.</li> </ul> - 2013-10-16T00:00:00-07:00 + 2013-10-16T00:00:00+02:00 http://facebook.github.io/react/blog/2013/10/16/react-v0.5.0.html http://facebook.github.io/react/blog/2013/10/16/react-v0.5.0.html @@ -562,7 +562,7 @@ Is this some sort of template language? Specifically no. This might have been th <p><a href="http://www.phpied.com/reactive-table/">Read the full article...</a></p> </blockquote> - 2013-10-03T00:00:00-07:00 + 2013-10-03T00:00:00+02:00 http://facebook.github.io/react/blog/2013/10/03/community-roundup-9.html http://facebook.github.io/react/blog/2013/10/03/community-roundup-9.html @@ -623,7 +623,7 @@ Is this some sort of template language? Specifically no. This might have been th <iframe width="100%" height="300" src="http://jsfiddle.net/vjeux/QL9tz/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe> - 2013-09-24T00:00:00-07:00 + 2013-09-24T00:00:00+02:00 http://facebook.github.io/react/blog/2013/09/24/community-roundup-8.html http://facebook.github.io/react/blog/2013/09/24/community-roundup-8.html @@ -704,7 +704,7 @@ Is this some sort of template language? Specifically no. This might have been th <p><a href="https://github.com/facebook/react-page/">Try it out ...</a></p> </blockquote> - 2013-08-26T00:00:00-07:00 + 2013-08-26T00:00:00+02:00 http://facebook.github.io/react/blog/2013/08/26/community-roundup-7.html http://facebook.github.io/react/blog/2013/08/26/community-roundup-7.html diff --git a/html-jsx.html b/html-jsx.html new file mode 100644 index 0000000000..0a11397b93 --- /dev/null +++ b/html-jsx.html @@ -0,0 +1,89 @@ + + + + + + React | HTML to JSX + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    HTML to JSX Compiler

    +
    + + +
    + + +
    +
    A Facebook & Instagram collaboration.
    +
    © 2013 Facebook Inc.
    +
    +
    +
    + + + + diff --git a/index.html b/index.html index 6f0868b290..8ee6c47264 100644 --- a/index.html +++ b/index.html @@ -145,6 +145,12 @@
    +
    +

    Presentation

    +

    Watch this 30-minute presentation followed by a Q&A to learn more about React.

    +
    +
    +
    Get Started diff --git a/js/html-jsx-lib.js b/js/html-jsx-lib.js new file mode 100644 index 0000000000..9029c53ac2 --- /dev/null +++ b/js/html-jsx-lib.js @@ -0,0 +1,482 @@ +/** + * Copyright 2013 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. + */ + +/** + * This is a very simple HTML to JSX converter. It turns out that browsers + * have good HTML parsers (who would have thought?) so we utilise this by + * inserting the HTML into a temporary DOM node, and then do a breadth-first + * traversal of the resulting DOM tree. + */ +;(function(global) { + 'use strict'; + + // https://developer.mozilla.org/en-US/docs/Web/API/Node.nodeType + var NODE_TYPE = { + ELEMENT: 1, + TEXT: 3, + COMMENT: 8 + }; + var ATTRIBUTE_MAPPING = { + 'for': 'htmlFor', + 'class': 'className' + }; + + /** + * Repeats a string a certain number of times. + * Also: the future is bright and consists of native string repetition: + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat + * + * @param {string} string String to repeat + * @param {number} times Number of times to repeat string. Integer. + * @see http://jsperf.com/string-repeater/2 + */ + function repeatString(string, times) { + if (times === 1) { + return string; + } + if (times < 0) { throw new Error(); } + var repeated = ''; + while (times) { + if (times & 1) { + repeated += string; + } + if (times >>= 1) { + string += string; + } + } + return repeated; + } + + /** + * Determine if the string ends with the specified substring. + * + * @param {string} haystack String to search in + * @param {string} needle String to search for + * @return {boolean} + */ + function endsWith(haystack, needle) { + return haystack.slice(-needle.length) === needle; + } + + /** + * Trim the specified substring off the string. If the string does not end + * with the specified substring, this is a no-op. + * + * @param {string} haystack String to search in + * @param {string} needle String to search for + * @return {string} + */ + function trimEnd(haystack, needle) { + return endsWith(haystack, needle) + ? haystack.slice(0, -needle.length) + : haystack; + } + + /** + * Convert a hyphenated string to camelCase. + */ + function hyphenToCamelCase(string) { + return string.replace(/-(.)/g, function(match, chr) { + return chr.toUpperCase(); + }); + } + + /** + * Determines if the specified string consists entirely of whitespace. + */ + function isEmpty(string) { + return !/[^\s]/.test(string); + } + + /** + * Determines if the specified string consists entirely of numeric characters. + */ + function isNumeric(input) { + return input !== undefined + && input !== null + && (typeof input === 'number' || parseInt(input, 10) == input); + } + + var HTMLtoJSX = function(config) { + this.config = config || {}; + + if (this.config.createClass === undefined) { + this.config.createClass = true; + } + if (!this.config.indent) { + this.config.indent = ' '; + } + if (!this.config.outputClassName) { + this.config.outputClassName = 'NewComponent'; + } + }; + HTMLtoJSX.prototype = { + /** + * Reset the internal state of the converter + */ + reset: function() { + this.output = ''; + this.level = 0; + }, + /** + * Main entry point to the converter. Given the specified HTML, returns a + * JSX object representing it. + * @param {string} html HTML to convert + * @return {string} JSX + */ + convert: function(html) { + this.reset(); + + // It turns out browsers have good HTML parsers (imagine that). + // Let's take advantage of it. + var containerEl = document.createElement('div'); + containerEl.innerHTML = '\n' + this._cleanInput(html) + '\n'; + + if (this.config.createClass) { + if (this.config.outputClassName) { + this.output = 'var ' + this.config.outputClassName + ' = React.createClass({\n'; + } else { + this.output = 'React.createClass({\n'; + } + this.output += this.config.indent + 'render: function() {' + "\n"; + this.output += this.config.indent + this.config.indent + 'return (\n'; + } + + if (this._onlyOneTopLevel(containerEl)) { + // Only one top-level element, the component can return it directly + // No need to actually visit the container element + this._traverse(containerEl); + } else { + // More than one top-level element, need to wrap the whole thing in a + // container. + this.output += this.config.indent + this.config.indent + this.config.indent; + this.level++; + this._visit(containerEl); + } + this.output = this.output.trim() + '\n'; + if (this.config.createClass) { + this.output += this.config.indent + this.config.indent + ');\n'; + this.output += this.config.indent + '}\n'; + this.output += '});'; + } + return this.output; + }, + + /** + * Cleans up the specified HTML so it's in a format acceptable for + * converting. + * + * @param {string} html HTML to clean + * @return {string} Cleaned HTML + */ + _cleanInput: function(html) { + // Remove unnecessary whitespace + html = html.trim(); + // Ugly method to strip script tags. They can wreak havoc on the DOM nodes + // so let's not even put them in the DOM. + html = html.replace(//g, ''); + return html; + }, + + /** + * Determines if there's only one top-level node in the DOM tree. That is, + * all the HTML is wrapped by a single HTML tag. + * + * @param {DOMElement} containerEl Container element + * @return {boolean} + */ + _onlyOneTopLevel: function(containerEl) { + // Only a single child element + if ( + containerEl.childNodes.length === 1 + && containerEl.childNodes[0].nodeType === NODE_TYPE.ELEMENT + ) { + return true; + } + // Only one element, and all other children are whitespace + var foundElement = false; + for (var i = 0, count = containerEl.childNodes.length; i < count; i++) { + var child = containerEl.childNodes[i]; + if (child.nodeType === NODE_TYPE.ELEMENT) { + if (foundElement) { + // Encountered an element after already encountering another one + // Therefore, more than one element at root level + return false; + } else { + foundElement = true; + } + } else if (child.nodeType === NODE_TYPE.TEXT && !isEmpty(child.textContent)) { + // Contains text content + return false; + } + } + return true; + }, + + /** + * Gets a newline followed by the correct indentation for the current + * nesting level + * + * @return {string} + */ + _getIndentedNewline: function() { + return '\n' + repeatString(this.config.indent, this.level + 2); + }, + + /** + * Handles processing the specified node + * + * @param {Node} node + */ + _visit: function(node) { + this._beginVisit(node); + this._traverse(node); + this._endVisit(node); + }, + + /** + * Traverses all the children of the specified node + * + * @param {Node} node + */ + _traverse: function(node) { + this.level++; + for (var i = 0, count = node.childNodes.length; i < count; i++) { + this._visit(node.childNodes[i]); + } + this.level--; + }, + + /** + * Handle pre-visit behaviour for the specified node. + * + * @param {Node} node + */ + _beginVisit: function(node) { + switch (node.nodeType) { + case NODE_TYPE.ELEMENT: + this._beginVisitElement(node); + break; + + case NODE_TYPE.TEXT: + this._visitText(node); + break; + + case NODE_TYPE.COMMENT: + this._visitComment(node); + break; + + default: + console.warn('Unrecognised node type: ' + node.nodeType); + } + }, + + /** + * Handles post-visit behaviour for the specified node. + * + * @param {Node} node + */ + _endVisit: function(node) { + switch (node.nodeType) { + case NODE_TYPE.ELEMENT: + this._endVisitElement(node); + break; + // No ending tags required for these types + case NODE_TYPE.TEXT: + case NODE_TYPE.COMMENT: + break; + } + }, + + /** + * Handles pre-visit behaviour for the specified element node + * + * @param {DOMElement} node + */ + _beginVisitElement: function(node) { + var tagName = node.tagName.toLowerCase(); + var attributes = []; + for (var i = 0, count = node.attributes.length; i < count; i++) { + attributes.push(this._getElementAttribute(node, node.attributes[i])); + } + + this.output += '<' + tagName; + if (attributes.length > 0) { + this.output += ' ' + attributes.join(' '); + } + if (node.firstChild) { + this.output += '>'; + } + }, + + /** + * Handles post-visit behaviour for the specified element node + * + * @param {Node} node + */ + _endVisitElement: function(node) { + // De-indent a bit + // TODO: It's inefficient to do it this way :/ + this.output = trimEnd(this.output, this.config.indent); + if (node.firstChild) { + this.output += ''; + } else { + this.output += ' />'; + } + }, + + /** + * Handles processing of the specified text node + * + * @param {TextNode} node + */ + _visitText: function(node) { + var text = node.textContent; + // If there's a newline in the text, adjust the indent level + if (text.indexOf('\n') > -1) { + text = node.textContent.replace(/\n\s*/g, this._getIndentedNewline()); + } + this.output += text; + }, + + /** + * Handles processing of the specified text node + * + * @param {Text} node + */ + _visitComment: function(node) { + // Do not render the comment + // Since we remove comments, we also need to remove the next line break so we + // don't end up with extra whitespace after every comment + //if (node.nextSibling && node.nextSibling.nodeType === NODE_TYPE.TEXT) { + // node.nextSibling.textContent = node.nextSibling.textContent.replace(/\n\s*/, ''); + //} + this.output += '{/*' + node.textContent.replace('*/', '* /') + '*/}'; + }, + + /** + * Gets a JSX formatted version of the specified attribute from the node + * + * @param {DOMElement} node + * @param {object} attribute + * @return {string} + */ + _getElementAttribute: function(node, attribute) { + switch (attribute.name) { + case 'style': + return this._getStyleAttribute(attribute.value); + default: + var name = ATTRIBUTE_MAPPING[attribute.name] || attribute.name; + var result = name + '='; + // Numeric values should be output as {123} not "123" + if (isNumeric(attribute.value)) { + result += '{' + attribute.value + '}'; + } else { + result += '"' + attribute.value.replace('"', '"') + '"'; + } + return result; + } + }, + + /** + * Gets a JSX formatted version of the specified element styles + * + * @param {string} styles + * @return {string} + */ + _getStyleAttribute: function(styles) { + var jsxStyles = new StyleParser(styles).toJSXString(); + return 'style={{' + jsxStyles + '}}'; + } + }; + + /** + * Handles parsing of inline styles + * + * @param {string} rawStyle Raw style attribute + * @constructor + */ + var StyleParser = function(rawStyle) { + this.parse(rawStyle); + }; + StyleParser.prototype = { + /** + * Parse the specified inline style attribute value + * @param {string} rawStyle Raw style attribute + */ + parse: function(rawStyle) { + this.styles = {}; + rawStyle.split(';').forEach(function(style) { + style = style.trim(); + var firstColon = style.indexOf(':'); + var key = style.substr(0, firstColon); + var value = style.substr(firstColon + 1).trim(); + if (key !== '') { + this.styles[key] = value; + } + }, this); + }, + + /** + * Convert the style information represented by this parser into a JSX + * string + * + * @return {string} + */ + toJSXString: function() { + var output = []; + for (var key in this.styles) { + if (!this.styles.hasOwnProperty(key)) { + continue; + } + output.push(this.toJSXKey(key) + ': ' + this.toJSXValue(this.styles[key])); + } + return output.join(', '); + }, + + /** + * Convert the CSS style key to a JSX style key + * + * @param {string} key CSS style key + * @return {string} JSX style key + */ + toJSXKey: function(key) { + return hyphenToCamelCase(key); + }, + + /** + * Convert the CSS style value to a JSX style value + * + * @param {string} value CSS style value + * @return {string} JSX style value + */ + toJSXValue: function(value) { + if (isNumeric(value)) { + // If numeric, no quotes + return value; + } else if (endsWith(value, 'px')) { + // "500px" -> 500 + return trimEnd(value, 'px'); + } else { + // Proably a string, wrap it in quotes + return '\'' + value.replace(/'/g, '"') + '\''; + } + } + }; + + // Expose public API + global.HTMLtoJSX = HTMLtoJSX; +}(window)); \ No newline at end of file diff --git a/js/html-jsx.js b/js/html-jsx.js new file mode 100644 index 0000000000..501a8f28ca --- /dev/null +++ b/js/html-jsx.js @@ -0,0 +1,89 @@ +/** + * Copyright 2013 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. + * + * @jsx React.DOM + */ + +/** + * This is a web interface for the HTML to JSX converter contained in + * `html-jsx-lib.js`. + */ +;(function() { + +var HELLO_COMPONENT = "\ +\n\ +
    \n\ + \n\ + \n\ +
    \n\ +

    Enter your HTML here

    \ +"; + + var HTMLtoJSXComponent = React.createClass({displayName: 'HTMLtoJSXComponent', + getInitialState: function() { + return { + outputClassName: 'NewComponent', + createClass: true + }; + }, + onReactClassNameChange: function(evt) { + this.setState({ outputClassName: evt.target.value }); + }, + onCreateClassChange: function(evt) { + this.setState({ createClass: evt.target.checked }); + }, + setInput: function(input) { + this.setState({ input: input }); + this.convertToJsx(); + }, + convertToJSX: function(input) { + var converter = new HTMLtoJSX({ + outputClassName: this.state.outputClassName, + createClass: this.state.createClass + }); + return converter.convert(input); + }, + render: function() { + return ( + React.DOM.div(null, + React.DOM.div( {id:"options"}, + React.DOM.label(null, + React.DOM.input( + {type:"checkbox", + checked:this.state.createClass, + onChange:this.onCreateClassChange} ), + " Create class " + ), + React.DOM.label( {style:{display: this.state.createClass ? '' : 'none'}}, + " · "+ + "Class name: ", + React.DOM.input( + {type:"text", + value:this.state.outputClassName, + onChange:this.onReactClassNameChange} ) + ) + ), + ReactPlayground( + {codeText:HELLO_COMPONENT, + renderCode:true, + transformer:this.convertToJSX} + ) + ) + ); + } + }); + + React.renderComponent(HTMLtoJSXComponent(null ), document.getElementById('jsxCompiler')); +}()); \ No newline at end of file diff --git a/tips/children-props-type.html b/tips/children-props-type.html index 0a15d78b8e..6c2cfa9c81 100644 --- a/tips/children-props-type.html +++ b/tips/children-props-type.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    diff --git a/tips/communicate-between-components.html b/tips/communicate-between-components.html index 5f9c9071c9..741b73e1dc 100644 --- a/tips/communicate-between-components.html +++ b/tips/communicate-between-components.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/tips/componentWillReceiveProps-not-triggered-after-mounting.html b/tips/componentWillReceiveProps-not-triggered-after-mounting.html index daf6e05e53..08037fa349 100644 --- a/tips/componentWillReceiveProps-not-triggered-after-mounting.html +++ b/tips/componentWillReceiveProps-not-triggered-after-mounting.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/tips/controlled-input-null-value.html b/tips/controlled-input-null-value.html index cad2a7ad9d..e7ec2a91a1 100644 --- a/tips/controlled-input-null-value.html +++ b/tips/controlled-input-null-value.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/tips/dom-event-listeners.html b/tips/dom-event-listeners.html index 24b5d760f2..4cfb73ce51 100644 --- a/tips/dom-event-listeners.html +++ b/tips/dom-event-listeners.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -358,7 +372,7 @@

    componentDidMount is called after the component is mounted and has a DOM representation. This is often a place where you would attach generic DOM events.

    -

    Notice that the event callback is bound to the react component and not the original element. React automatically binds methods to the current component instance for you through a process of autobinding.

    +

    Notice that the event callback is bound to the react component and not the original element. React automatically binds methods to the current component instance for you through a process of autobinding.

    diff --git a/tips/false-in-jsx.html b/tips/false-in-jsx.html index 1a747a3dff..20163dd6b3 100644 --- a/tips/false-in-jsx.html +++ b/tips/false-in-jsx.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    diff --git a/tips/if-else-in-JSX.html b/tips/if-else-in-JSX.html index 037bbf52ce..bf2416df71 100644 --- a/tips/if-else-in-JSX.html +++ b/tips/if-else-in-JSX.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/tips/initial-ajax.html b/tips/initial-ajax.html index 2c829a6ff7..18858b4134 100644 --- a/tips/initial-ajax.html +++ b/tips/initial-ajax.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/tips/inline-styles.html b/tips/inline-styles.html index 92e32fd58e..86958db678 100644 --- a/tips/inline-styles.html +++ b/tips/inline-styles.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/tips/introduction.html b/tips/introduction.html index 31fd3c08d2..3678ced458 100644 --- a/tips/introduction.html +++ b/tips/introduction.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/tips/maximum-number-of-jsx-root-nodes.html b/tips/maximum-number-of-jsx-root-nodes.html index f6273c818a..b4d075166f 100644 --- a/tips/maximum-number-of-jsx-root-nodes.html +++ b/tips/maximum-number-of-jsx-root-nodes.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + diff --git a/tips/props-in-getInitialState-as-anti-pattern.html b/tips/props-in-getInitialState-as-anti-pattern.html index 1d05852ca3..3759767d07 100644 --- a/tips/props-in-getInitialState-as-anti-pattern.html +++ b/tips/props-in-getInitialState-as-anti-pattern.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • + @@ -331,7 +345,7 @@

    Using props, passed down from parent, to generate state in getInitialState often leads to duplication of "source of truth", i.e. where the real data is. Whenever possible, compute values on-the-fly to ensure that they don't get out of sync later on and cause maintenance trouble.

    -

    Bad example:

    +

    Bad example:

    /** @jsx React.DOM */
     
     var MessageBox = React.createClass({
    @@ -369,6 +383,25 @@
     });
     
     React.renderComponent(<MessageBox name="Rogers"/>, mountNode);
    +
    +

    However, it's not an anti-pattern if you intentionally make it clear that synchronization's not the goal here:

    +
    /** @jsx React.DOM */
    +
    +var Counter = React.createClass({
    +  getInitialState: function() {
    +    // naming it initialX clearly indicates that the only purpose
    +    // of the passed down prop is to initialize something internal
    +    return {count: this.props.initialCount};
    +  },
    +  handleClick: function() {
    +    this.setState({count: this.state.count + 1});
    +  },
    +  render: function() {
    +    return <div onClick={this.handleClick}>{this.state.count}</div>;
    +  }
    +});
    +
    +React.renderComponent(<Counter initialCount={7}/>, mountNode);
     
    diff --git a/tips/self-closing-tag.html b/tips/self-closing-tag.html index 1bad64a06e..8c7a6987c2 100644 --- a/tips/self-closing-tag.html +++ b/tips/self-closing-tag.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +
    diff --git a/tips/style-props-value-px.html b/tips/style-props-value-px.html index 529615aecf..457418f620 100644 --- a/tips/style-props-value-px.html +++ b/tips/style-props-value-px.html @@ -248,6 +248,20 @@ +
  • + + Special Non-DOM attributes + + +
  • + +
  • + + Reconciliation + + +
  • +